ここからはiOS 9から登場したGameplayKitの「GKStateMachine」クラスを利用してState管理を改善します。
GKStateMachineとはStateの管理をしてくれるクラスで、Stateの保持やState変更時のコールバックメソッドを呼び出してくれます。
Char.swiftをGKStateMachineを使った形に修正します。
class Char: SKSpriteNode { // stateMachineプロパティーがstateを保持する lazy var stateMachine: GKStateMachine = { let stateMachine = GKStateMachine(states: [ AttackState(char: self), StayState(char: self) ]) stateMachine.enterState(AttackState) return stateMachine }() // GKStateMachineが保持できるようにCharStateはGKStateのサブクラスにする class CharState: GKState { weak var char: Char? var power: Int { return char?.power ?? 0 } init(char: Char?) { self.char = char } func enableToAttack() -> Bool { return false } } class AttackState: CharState { override func enableToAttack() -> Bool { return true } } class AngryState: CharState { var enteredDate = NSDate() override var power: Int { return char?.power ?? 0 } override func enableToAttack() -> Bool { return true } override func didEnterWithPreviousState(previousState: GKState?) { enteredDate = NSDate() } override func updateWithDeltaTime(seconds: NSTimeInterval) { if NSDate().timeIntervalSinceDate(enteredDate) > 10 { // stateの変更はenterStateメソッドを利用する char?.stateMachine.enterState(AttackState) } } } class StayState: CharState { var enteredDate = NSDate() override func didEnterWithPreviousState(previousState: GKState?) { enteredDate = NSDate() } override func updateWithDeltaTime(seconds: NSTimeInterval) { if NSDate().timeIntervalSinceDate(enteredDate) > 0.2 { char?.stateMachine.enterState(AttackState) } } override func enableToAttack() -> Bool { return false } } var power = 1 }
併せてGameScene.swiftのupdateメソッドを書き換えます。
class GameScene: SKScene, SKPhysicsContactDelegate { // 略... override func update(currentTime: NSTimeInterval) { char.stateMachine.updateWithDeltaTime(currentTime) if let charState = char.stateMachine.currentState as? Char.CharState where charState.enableToAttack() { enemyList.enemiesCloseToPoint(char.frame.origin, distance: 50).forEach { $0.life -= charState.power char.stateMachine.enterState(Char.StayState.self) 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) } } } } } // 略... }
大体既存コードと同じなのですが、違いとしてAngryStateに「didEnterWithPreviousState」メソッドが追加されました。これはStateが他のものからAngryStateになったときに呼ばれるコールバックメソッドです。逆にAngryStateから別のStateになるときは「willExitWithNextState」メソッドが呼び出されます。
他にもGKStateMachineには別Stateへ変更可能かをチェックする「isValidNextState」など便利な機能が付いています。
今回はStateパターンとStateMachineを利用することで保守性の高い実装をしました。本連載は新規実装になるのでメリットが伝わりにくいのですが、実際にゲームを開発して保守をすることになれば、これらの機能によるメリットは大きいかと思います。
次回は自機の動きをさらに改善して、プレイヤーが自由に自機を配置したりレベルアップしたりすることができるようにしていきます。
田町のベンチャーで働くエンジニア。
仕事ではiPhoneアプリの開発やRailsを使ったWebサービス開発を行っている。最近のマイブームはUnityを使った3Dゲーム開発。
Copyright © ITmedia, Inc. All Rights Reserved.