iOS
Swift

Swiftでパズルゲーム

More than 3 years have passed since last update.


はじめに

パズルゲーム作れたら楽しいだろうなと思い作ってみました

パズルの仕様としてはパズドラを思い浮かべてもらえれば良いと思います。

参考 Swiftris


ソース

https://github.com/konpeto000/tilepuzzle


イメージ

*(タイルの配置はランダムですので起動後3色以上並んでいても消えません)

tilepuzzle.gif


反省

ノードの管理が未だによくわかっておりません。。

アクション実行中にタッチ操作を受け付けてしまうので

これをどうにかしたいのですがどなたかわかる方がいらっしゃったら教えてください。。

今回は強引にアクション実行中にタッチ操作を受け付けないようにしています


解説

タイルの初期化

func randomColor()->UIColor{

//0:red,1:green,2:blue,3:yellow
let rnd = arc4random()%4
var color:UIColor!
switch rnd{
case 0:
color = UIColor.redColor()
case 1:
color = UIColor.greenColor()
case 2:
color = UIColor.blueColor()
case 3:
color = UIColor.yellowColor()
default:
break
}
return color
}

func initMakeTile(){

for(var i:Int = 0;i < NumColumns;i++){
for(var j:Int = 0;j < NumRows;j++){
let sprite = makeTileOne()

sprite.position = CGPointMake(CGFloat(i)*TileSize,-CGFloat(j)*TileSize)
tileArrayPos[i][j] = sprite.position

board.addChild(sprite)
}
}

}

func makeTileOne()->SKSpriteNode{
let sprite = SKSpriteNode()
sprite.anchorPoint = CGPointMake(0, 1.0)
sprite.alpha *= 0.8
sprite.color = randomColor()
sprite.size = CGSizeMake(TileSize-1, TileSize-1)

return sprite
}

この一連で描画するタイルを作成し

タイルの位置をtileArrayPos[][]に保存する

後々nodeAtPointを使い位置からノードを参照できるようにするため


タッチ操作

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {

/* Called when a touch begins */

for touch in touches {
let location = touch.locationInNode(self)
touchedNode = self.nodeAtPoint(location)
for node in self.board.children{
if(touchedNode == node as NSObject && !moveActionFlag){
touchedNode.alpha *= 0.5
}
}

}
}

override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location = touch.locationInNode(self)
let passagedNode = self.nodeAtPoint(location)
for node in self.board.children{
if(passagedNode == node as NSObject && touchedNode != passagedNode && !moveActionFlag){
moveTile(passagedNode)
}
}
}
}

override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location = touch.locationInNode(self)
let endedNode = self.nodeAtPoint(location)
for node in self.board.children{
if(endedNode == node as NSObject && !moveActionFlag){
moveActionFlag = true
deleteAndDropTile()
touchedNode.alpha *= 2
}
}
}
}

began関数

ここで選択したノードをtouchedNodeに保存する

そしてそのノードのアルファ値を半分にするそうすることによって

選択したノードをわかりやすくした

moved関数

ここで選択されるノードをpassageNodeに保存し

それぞれの条件に当てはめるそしてmoveTile(passagedNode)にとばす

ended関数

ここでタイルの消去と落ちる処理を行うdeleteAndDropTile()にとばす

またtouchedNodeのアルファ値を元に戻す

どの関数にもアクション実行中は実行しないというmoveActionFlagを条件に持たせる

もっとうまいやり方があると思うのだが思いつかなかった。。。


タイルの移動

func moveTile(node:SKNode){

let touchedAction = SKAction.moveTo(node.position, duration: 0.08)
let passeagedAction = SKAction.moveTo(touchedNode.position, duration: 0.08)

moveActionFlag = true
touchedNode.runAction(touchedAction, completion: {self.moveActionFlag = false})
node.runAction(passeagedAction, completion: {self.moveActionFlag = false})
}

タッチしたタイルが他のタイルと接触したときに呼ばれ

そのノードの位置をアクションによって交換する関数

アクション実行中はmoveActionFlagtrueにして

それぞれのtouch関数の処理を呼び出せないようにする

アクションが終了したらfalseとする


タイルの消去と落ちる処理

func deleteAndDropTile(){

var deleteTileArray = Array(arrayLiteral:SKNode())
deleteTileArray.removeAll()
deleteColumns(&deleteTileArray)
deleteRows(&deleteTileArray)

if(deleteTileArray.isEmpty){
moveActionFlag = false
return
}

board.removeChildrenInArray(deleteTileArray)

dropTile(){
self.makeTileInEmpty(){
self.deleteAndDropTile()
}
}

}

消すタイルをそれぞれ行と列で分けdeleteTileArrayに入れる

もし入っていなければmoveActionFlagを折り再帰を終了する

dropTile()に飛ばしアクション終了後makeTileInEmpty()deleteAndDropTile()に飛ばす


列行の削除

func deleteColumns(inout array:[SKNode]){

var deleteColumnsArray = Array(arrayLiteral:SKNode())

for(var i:Int = 0;i < NumColumns;i++){
var color = searchNode(tileArrayPos[i][0]).color
var count = 0

for(var j:Int = 0;j < NumRows;j++){
var node = searchNode(tileArrayPos[i][j])
if(color == node.color){
deleteColumnsArray.append(node)
count++

}else{
if(count >= 3){
insertArray(&array, arrayB: deleteColumnsArray)
}
deleteColumnsArray.removeAll()
deleteColumnsArray.append(node)
color = node.color
count = 1
}

}

if(count >= 3){
insertArray(&array, arrayB: deleteColumnsArray)
}
deleteColumnsArray.removeAll()
}
}

(行は列の処理とほぼかわらないので割愛)

選択したノードを原点とし探索を開始する

次のノードの色が原点と同じならばカウント(同じ色の数)を増加しdeleteColumnsArrayに入れる

またそうでなく同じ色の数が3以上ならばその配列をarrayに入れるinoutなのでdeleteTileArrayに入れると同じこと

そして同じ色の数が3未満ならばdeleteColumnsArrayの要素をすべて捨てる


ノードの探索

func searchNode(pos:CGPoint)->SKSpriteNode{

let node = self.nodeAtPoint(CGPointMake(pos.x+TileSize,pos.y-4*TileSize))
return node as SKSpriteNode
}

指定した場所のノードをSKSpriteNodeとして返す

nodeAtPointでやるのだがいまいちわかっておらず

xにタイル1つ分yにタイル4つ分ずらさないとうまく行かない


タイルの落ちる処理

func dropTile(completion:()->()){

for(var i:Int = 0;i < NumColumns;i++){
for(var j:Int = 0;j < NumRows;j++){
let k = searchEmpty(searchNode(tileArrayPos[i][j]))
if(k != 0){
let sprite = searchNode(tileArrayPos[i][j])
let action = SKAction.moveTo(tileArrayPos[i][j+k], duration: 0.1)
sprite.runAction(action)
}
}
}

runAction(SKAction.waitForDuration(0.2), completion: completion)
}

とあるノードの下にある空白の個数をsearchEmptyで探し

その個数分だけそのノードをアクションで下げる


空白の探索

func searchEmpty(node:SKSpriteNode)-> Int{

var count = 0
if(node.name != "board"){
for(var k:Int = Int(-1*node.position.y/TileSize);k < NumRows;k++){
let sprite = searchNode(tileArrayPos[Int(node.position.x/TileSize)][k])
if(sprite.name == "board"){
count++
}
}
}

return count
}

とあるノードの下の空白の個数を数えるのだが

Int(-1*node.position.y/TileSize)はそのノードのj番目という意味である

Int(-1*node.position.x/TileSize)はi番目

空白とはいわゆるboard自身なのでそのノードの名前がboardつまり空白のときカウントしその数を返す


空白にタイルを入れる

func makeTileInEmpty(completion:()->()){

    var emptyList = Array(count:NumColumns,repeatedValue:0)

for(var i:Int = 0;i < NumColumns;i++){
var count = 0
for(var j:Int = 0;j < NumRows;j++){
if(searchNode(tileArrayPos[i][j]).name == "board"){
count++
scorePoint += 100
}
}
emptyList[i] = count
}
var i = 0
for k in emptyList{
for(var j:Int = 0;j < k;j++){
let sprite = makeTileOne()
board.addChild(sprite)
let action = SKAction.moveTo(tileArrayPos[i][j], duration: 0.1)
sprite.runAction(action)
}
i++
}
runAction(SKAction.waitForDuration(0.2), completion: completion)
}

タイルがすべて下に落ちているので

その列の空白を数え各列ごとにemptyListに保存し

要素分だけタイルを作成しboardに追加していく