【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_3

#前回までの記事
[【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_2](http://qiita.com/chino_tweet/items/449e54581ebb9bd5ae31)では、給水機が取りうる全ての状態に共通する機能が実装された`DispenserState`クラスを定義しました。

#この記事について
本記事では、ステートとステートマシンの実装を行います。

給水機が変化しうるそれぞれの状態（ステート）を表現するクラスを定義します。
給水機は以下の状態に変化します。

- 満タン（FullState）
- 水あり（PartiallyFullState）
- カラ （EmptyState）
- 補給中（RefillingState）
- 給水中（ServeState）

定義されたステートは、ステートマシンによって管理・操作します。
ステートマシンを使うことで、給水機そのものを操作するよりも効率的に給水機を扱うことができます。

#手順
1. `FullState`クラスを定義
2. ステートマシンを定義

##満タン状態を表現するクラスを定義する
メニューバーから「File > New > File... > iOS > Swift File」を選択します。
ファイル名は`FullState.swift`にしておきます。
`SpriteKit`と`GameplayKit`フレームワークをインポートして、`DispenserState`クラスを継承した`FullState`クラスを定義します。

```ruby:FullState.swift
import SpriteKit
import GameplayKit

class FullState: DispenserState {
    
}
```

親クラスに定義されているイニシャライザを利用して、必須イニシャライザを定義します。
また、`didEnter(from:)`メソッドと`willExit(to:)`メソッドをオーバーライドします。

```ruby:FullState.swift
class FullState: DispenserState {
    
    init(game: GameScene) {
        super.init(game: game, associatedNodeName: "FullState")
    }
    
    override func didEnter(from previousState: GKState?) {
        super.didEnter(from: previousState)

        let green = SKColor.green
        changeIndicatorLightToColor(green)
    }
    
    override func willExit(to nextState: GKState) {
        super.willExit(to: nextState)
        
        let black = SKColor.black
        changeIndicatorLightToColor(black)
    }
    
}
```

`didEnter(from:)`メソッドと`willExit(to:)`メソッドでは、給水機の表示ランプの色を「満タンになったらグリーン・満タンではなくなったらブラック」に変化させています。


##ステートマシンを定義する
ステートマシンは各種状態への遷移を管理する役割を持ち、`GKStateMachine`クラスによって表現されます。
ゲームシーンに、`GKStateMachine`クラスのオブジェクトを定義します。

まず、`GameScene.swift`ファイルに`GameplayKit`フレームワークをインポートしておきます。
メンバワイズプロパティに`stateMachine`プロパティを暗黙アンラップなオプショナルで定義します。
`stateMachine`プロパティは、`didMove(to:)`メソッドで初期化します。引数には、`GKState`型オブジェクトの配列を渡します。

```ruby:GameScene.swift
import SpriteKit
import GameplayKit

class GameScene: SKScene {
    
    var stateMachine: GKStateMachine!
    
    override func didMove(to view: SKView) {
        let fullState = FullState(game: self)
        stateMachine = GKStateMachine(states: [fullState])
        
        stateMachine.enter(FullState.self)
    }
    
    override func didChangeSize(_ oldSize: CGSize) {
        let dispenser = childNode(withName: "dispenser")
        dispenser?.position.x = size.width / 2
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let locationInView = touches.first?.location(in: view)
        let locationInScene = convertPoint(fromView: locationInView!)
        
        let refillButton = childNode(withName: "//refillButton")
        let location = locationInScene
        
        if atPoint(location) == refillButton {
            print("attempt to refill.")
        } else {
            print("attempt to dispense")
        }
    }
}
```

`didMove(to:)`メソッドで、給水機の初期状態が`FullState`になるように記述しています。

###ビルド
シミュレータでビルドしてみます。
給水機の表示ランプ`indicator`がグリーンになっていれば、状態が`FullState`になっていることになります。また、状態遷移パネルにある「Full」がハイライトされていることも確認しておきましょう。

図. 満タン状態の給水機
<img width="680" alt="スクリーンショット 2016-12-24 15.59.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/3331b522-2740-97e2-e9e3-3a49896c4896.png">

図. 満タン状態ではない給水機
<img width="680" alt="スクリーンショット 2016-12-24 15.57.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/70476/99f8e58b-c804-b65a-6da4-d6d9c5e4adea.png">


#次回
ステートマシンを実現する`GKStateMachine`クラスを使って、状態`GKState`オブジェクトを管理することができました。
ステートマシンでは、`GKState`型のインスタンスを操作せずにクラスそのものを扱っていることに注目できます。
次回[【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_4](http://qiita.com/chino_tweet/items/35c62dc17e93e5b8b335)では、他の状態オブジェクトを定義して、状態から状態への繊維を実装します。