14
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swiftでパズルゲーム

Last updated at Posted at 2014-11-11

#はじめに
パズルゲーム作れたら楽しいだろうなと思い作ってみました
パズルの仕様としてはパズドラを思い浮かべてもらえれば良いと思います。

参考 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に追加していく

14
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?