連載
» 2016年03月24日 05時00分 UPDATE

iOS SDKとSwiftで始めるゲーム作成入門(5):ゲームのState管理を簡単にするiOS 9 GameplayKitのクラスとは (1/4)

iPhoneゲームをSwift言語で作成してみたいという初心者向けにiOSのゲームフレームワークを使った作り方を一から解説する入門連載。今回は、GoFデザインパターンの1つである「Stateパターン」やiOS 9から登場した「GKStateMachine」クラスを取り入れることで、今後の開発に役立つ知見を共有します。

[杉本裕樹,マネーフォワード]

 本連載「iOS SDKとSwiftで始めるゲーム作成入門」は、iPhone向けのゲーム開発の入門連載です。タワーディフェンスを題材に、「SpriteKit」というゲーム開発フレームワークの解説やゲームの開発手法について書いています。

 実装に入る前に本連載で作るアプリの完成形を確認しておきます。本連載では、下記6つのルールを満たすタワーディフェンスを作っていきます。

  1. プレーヤーは開始前に与えられた所持金を元に、敵を攻撃するユニットを設置する
  2. ゲームは「Wave(ウエーブ)」という単位で行われる。基本的には1つのWaveで登場する敵の種類は1種類のみとなっている。Waveが始まると敵は入口から登場し、目的地に向かって行進する。プレーヤーが設置したユニットは攻撃可能範囲に入ると自動的に攻撃を行う
  3. 1Waveの敵を全て全滅させるとWaveクリアとなる。プレーヤーは次のWaveが始まるまでにユニットの増強(新設・アップグレード・売却など)を行う
  4. 以上の2と3を繰り返して行う
  5. 敵が目的地に到達すると、自分が所持しているライフが減少する。全て失うとゲームオーバーとなる
  6. ライフを全て失う前に、最終Waveの敵を全て全滅させることができればクリアとなる

 前回の『デザインパターン「ファーストクラスコレクション」でSwiftコードの保守性・可読性を上げる方法をゲームのコードから学ぶ』では、敵が何体も登場して、全てを倒すとゲームクリアになるように実装しました。

 今回の記事では自機の動きを改善していきます。それと平行してGoFデザインパターンの1つである「Stateパターン」やiOS 9から登場した「GKStateMachine」クラスを取り入れることで、今後の開発に役立つ知見を共有できればと思います。

自機の近くにいる敵を攻撃させる

 今は自機と敵が接触したときに敵が消滅する仕様になっています。それを実際のタワーディフェンスのように敵が自機近くに来ると攻撃をするように改善しようと思います。

 ソースコードは、こちらをベースに改善していきます。

 まずはGameScene.swiftを修正して自機が敵の進路方向に入らないようにします。

class GameScene: SKScene, SKPhysicsContactDelegate {
    // 略...
        // char.position = CGPoint(x:250, y:300)
        char.position = CGPoint(x:270, y:280)
    // 略...
}

 次にEnemyList.swiftへ自機付近の敵一覧を取得するメソッドを追加します。

class EnemyList {
    // 略...
    func enemiesCloseToPoint(point: CGPoint, distance: Double) -> [SKSpriteNode] {
        return enemies.filter {
            let dx = Double(point.x - $0.frame.origin.x)
            let dy = Double(point.y - $0.frame.origin.y)
            let distanceFromEnemy = sqrt(dx*dx + dy*dy)
 
            return distanceFromEnemy < distance
        }
    }
    // 略...
}

 最後にGameScene.swiftに自機付近の敵を攻撃する処理を追加します。近くの敵を攻撃して敵の削除をする処理と、全ての敵が消えたらゲームクリアにする処理を追加します。

class GameScene: SKScene, SKPhysicsContactDelegate {
    // 略...
    override func update(currentTime: NSTimeInterval) {
        enemyList.enemiesCloseToPoint(char.frame.origin, distance: 50).forEach {
            $0.physicsBody?.node?.removeFromParent()
            $0.physicsBody?.node?.removeAllActions()
 
            if enemyList.isAllEnemyRemoved() {
                state = .GameClear
 
                let myLabel = SKLabelNode(fontNamed: "HiraginoSans-W6")
                myLabel.text = "ゲームクリア"
                myLabel.fontSize = 45
                myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) - 20)
                addChild(myLabel)
            }
        }
    }
    // 略...
}

 これで攻撃範囲に入った敵を攻撃するようになりました。

 今まで使っていた、自機と敵が衝突したときの処理は不要なので削除します。

// これらの処理を丸ごと削除する
//    func didBeginContact(contact: SKPhysicsContact) {
//        [contact.bodyA, contact.bodyB].forEach {
//            if $0.node?.name == "enemy" {
//                $0.node?.removeFromParent()
//                $0.node?.removeAllActions()
//            }
//        }
//
//        if enemyList.isAllEnemyRemoved() {
//            state = .GameClear
//
//            let myLabel = SKLabelNode(fontNamed: "HiraginoSans-W6")
//            myLabel.text = "ゲームクリア"
//            myLabel.fontSize = 45
//            myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) - 20)
//            addChild(myLabel)
//        }
//    }
       1|2|3|4 次のページへ

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。