ゲームのState管理を簡単にするiOS 9 GameplayKitのクラスとは:iOS SDKとSwiftで始めるゲーム作成入門(5)(3/4 ページ)
iPhoneゲームをSwift言語で作成してみたいという初心者向けにiOSのゲームフレームワークを使った作り方を一から解説する入門連載。今回は、GoFデザインパターンの1つである「Stateパターン」やiOS 9から登場した「GKStateMachine」クラスを取り入れることで、今後の開発に役立つ知見を共有します。
Stateパターンを使って変更に強い実装にする
ここからはStateパターンを使って変更に強いプログラムにしていきます。
StateパターンとはGoFの23個のデザインパターンの1つで各Stateでの処理を対応するStateクラスに持たせるデザインパターンです。
今回のような複数Stateがあるときは「このStateのときはこの処理をしてこのStateのときは別の処理をする」というようにStateごとの処理を1カ所にまとめて書くことが多いかと思います。しかし1カ所に集約するとStateが増えるたびにコードが長くなり、だんだんと保守性が下がってきます。
Stateパターンでは「このStateのときは、この処理」という部分を各Stateクラスに持たせるので、処理が追加されても特定箇所のコードが長くなりにくく保守性を維持できます。
説明だけでは分かりにくいと思うので実装してみましょう。
2つのStateクラスを作成
まずはChar.swiftを以下のように書き換えます。「AttackState」「StayState」という2つのStateクラスを作成してenableToAttackメソッドとupdateメソッドを持たせました。
class Char: SKSpriteNode { class CharState { weak var char: Char? var power: Int { return char?.power ?? 0 } init(char: Char?) { self.char = char } func update() {} func enableToAttack() -> Bool { return false } } class AttackState: CharState { override func enableToAttack() -> Bool { return true } } class StayState: CharState { var enteredDate = NSDate() override func update() { if NSDate().timeIntervalSinceDate(enteredDate) > 0.2 { char?.state = AttackState(char: char) } } override func enableToAttack() -> Bool { return false } } lazy var state: CharState = { return AttackState(char: self) }() var power = 1 }
併せてGameScene.swiftのupdateメソッドを修正します。
class GameScene: SKScene, SKPhysicsContactDelegate { // 略... override func update(currentTime: NSTimeInterval) { char.state.update() // 攻撃可能かはenableToAttackメソッドを参照 if char.state.enableToAttack() { enemyList.enemiesCloseToPoint(char.frame.origin, distance: 50).forEach { // lifeはchar.state.powerの分減らす $0.life -= char.state.power char.state = Char.StayState(char: char) if $0.life <= 0 { $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 changeCharStateToAttack() { // char.state = .Attack // } // 略... }
これでStateパターンを使う形への置き換えは完了です。今回はStayStateからAttackStateへ戻る処理と攻撃可能かの条件を各Stateクラスに持たせることができました。しかし今の状態だと処理を移しただけなので、Stateパターンがどう役立ったかが分かりにくいかと思います。
そこで次項で攻撃力2倍状態というStateの追加をすることでStateパターンによってどう良くなったかを確かめようかと思います。
攻撃力2倍状態を作る
実際の修正はChar.swiftにAngryStateを追加するだけです。後は今後攻撃力2倍アイテムを実装したときにstateをAngryStateにする処理を書けば終わりです。
class Char: SKSpriteNode { // 略... class AngryState: CharState { var enteredDate = NSDate() override var power: Int { return (char?.power ?? 0) * 2 } override func enableToAttack() -> Bool { return true } override func update() { if NSDate().timeIntervalSinceDate(enteredDate) > 10.0 { char?.state = AttackState(char: char) } } } // 略... }
「もしStateパターンにする前にAngryStateを追加したら、どうなるか」を考えてみます。
まず「攻撃可能かどうか」の処理は下のように実装するかと思います。Stateが増えるたびに修正が必要なので抜け漏れが発生しそうです。
if char.state == .Attack || char.state == .Angry { // 略... }
敵にダメージを与える箇所は下のようになるかと思います。ここもStateが増えると分岐が増えて可読性が落ちそうです。
if char.state == .Attack { $0.life -= char.power } else if char.state == .Angry { $0.life -= char.power * 2 }
このようにStateパターンで実装しておけばStateの追加に強くなります。
Stateパターンをゲーム開発で使うメリット
実際の現場でのタワーディフェンスゲーム作成では「自機のスピード2倍にするアイテムを追加してほしい」「自機の攻撃力を半分にするような敵を作ってほしい」といった自機にさまざまなStateを追加することになるかと思います。
そういったときにStateパターンで実装したことで既存コードへの修正が最小限に済み、スピード感ある改善が行えます。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- 開発者向けiOS 9、WatchOS 2、Swift 2、Xcode 7の新機能と新しいApple Developer Programの参考情報まとめ
iOS 9、WatchOS 2、Swift 2、Xcode 7の新機能や、Apple Developer Programについて、その概要とアップルが公開している参考情報をまとめて紹介します。 - Apple WatchやiPhoneのアプリを作ろう! Playgroundで学ぶSwiftの基礎―変数、定数、型、演算
本連載では、これからプログラミングやiPhoneアプリ開発を始めてみたい方を対象に、開発に必要な知識を基礎から解説していきます。今回は、Swiftの変数、定数、型、演算などについてサンプルプログラムを交えて解説します。 - iOSアプリ開発でObjective-CからSwiftに移行するための手順、注意点まとめ〜言語仕様の違いは? 連携時の呼び出し方は?
開発生産性や品質を向上させたいiOSアプリ開発者のためにObjective-CからSwiftへ移行するメリットや手順、注意点など勘所をまとめて紹介します。