検索
連載

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

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

PC用表示 関連情報
Share
Tweet
LINE
Hatena

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.

ページトップに戻る