#前回までの記事
【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_3では、ステートおよびステートマシンの定義と実装をしました。
#この記事について
以下の方法によるステートの遷移を実装していきます。
- ユーザからのインタラクションによるステート遷移
- ステートに実装された機能による自動的なステート遷移
#手順
- 給水中状態
ServeState
の定義 - 満タン状態
Full
から給水中状態ServeState
への遷移 - 水あり状態
PartiallyFullState
の定義 - 給水中
ServeState
から水ありPartiallyFullState
に自動的なステート遷移
##水あり状態の定義
メニューバーから「File > New > File... > iOS > Swift File」を選択します。
ファイル名はServeState.swift
とします。
GameplayKit
フレームワークをインポートして、DispenserState
クラスを継承したServeState
クラスを定義します。
import GameplayKit
class ServeState: DispenserState {
init(game: GameScene) {
super.init(game: game, associatedNodeName: "ServeState")
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
print("now is Serve...")
}
}
イニシャライザでは、親クラスのイニシャライザを利用します。
didEnter(from:)
メソッドも同様に、親クラスのメソッドを利用します。
##満タン状態から給水中状態への遷移
ゲームシーンをFullState
からServeState
へ遷移させます。
まずは、ステートマシンにステートを登録します。
didMove(to:)
メソッドで、stateMachine
オブジェクト初期化時に記述します。
override func didMove(to view: SKView) {
let fullState = FullState(game: self)
let serveState = ServeState(game: self)
stateMachine = GKStateMachine(states: [fullState, serveState])
stateMachine.enter(FullState.self)
}
続いて、toucheEnded(with:)
メソッドで、リフィルボタン以外の場所がタップされた場合だけステートマシンをServeState
に遷移させます。
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")
stateMachine.enter(ServeState.self)
}
}
###ビルド
シミュレータで実行します。
画面の適当な場所をタップすると、給水機の表示ランプがグリーン→ブラックに変化します。ただし、リフィルボタンをタップしても変化しません。
つまり、表示ランプがブラックになっていれば状態はServeState
に遷移していることがわかります。
ここで、安全に状態遷移するようにして、ステートマシンの堅牢性を確保します。
GKState
クラスのisValidNextState(_:)
メソッドで、遷移先ステートをチェックします。
import SpriteKit
import GameplayKit
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)
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is ServeState.Type
}
}
給水機の状態が満タンである場合、その遷移先となる状態は給水中ServeState
の他にはありえません。
isValidNextState(_:)
メソッドがfalse
を返す場合は、ステートマシンは状態を遷移しません。
##水あり状態の定義
メニューバーから「File > New > File... > iOS > Swift Class」を選択してPartiallyFullState.swift
ファイルを作成します。
GameplayKit
フレームワークをインポートして、DispenserState
クラスを継承したPartiallyFullState
クラスを定義します。
import GameplayKit
class PartiallyFullState: DispenserState {
init(game: GameScene) {
super.init(game: game, associatedNodeName: "PartiallyFullState")
}
}
イニシャライザでは、親クラスのイニシャライザを利用します。
##自動的なステート遷移
給水中は一定時間で次の状態(水あり)に遷移すべきです。
画面のタップなど、ユーザからのインタラクションなしで自動的にステートマシンの状態を遷移させます。
まずは、ステートマシンに水あり状態 PartiallyFullState
を登録します。
override func didMove(to view: SKView) {
let fullState = FullState(game: self)
let serveState = ServeState(game: self)
let partiallyFullState = PartiallyFullState(game: self)
stateMachine = GKStateMachine(states: [fullState,
serveState,
partiallyFullState])
stateMachine.enter(FullState.self)
}
ステートマシンの堅牢性を確保するため、ServeState
からはPartiallyFullState
にしか遷移できないようにしておきます。
class ServeState: DispenserState {
init(game: GameScene) {
super.init(game: game, associatedNodeName: "ServeState")
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
print("now is serve...")
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is PartiallyFullState.Type
}
}
###ステートにステート遷移機能を実装する
続いて、ServeState
に遷移した1秒後に自動でPartiallyFullState
にステートマシンが遷移するようにします。
自動的な遷移までの時間は、メンバワイズプロパティとして定義したtimeScale
プロパティで設定します。
自動遷移の機能は、waitAndExit()
メソッドで実装しています。
class ServeState: DispenserState {
static let timeScale = 1.0
init(game: GameScene) {
super.init(game: game, associatedNodeName: "ServeState")
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
print("now is serve...")
waitAndExit()
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is PartiallyFullState.Type
}
func waitAndExit() {
let waitAction = SKAction.wait(forDuration: ServeState.timeScale)
game.scene?.run(waitAction, completion: {
self.stateMachine?.enter(PartiallyFullState.self)
})
}
}
###ビルド
シミュレータで実行してみましょう。
画面上のリフィルボタン以外の場所をタップすると、遷移パネルのハイライトが「Full ~ Serve ~ Partially Full」へと自動的に遷移していきます。
##次回
ステートの自動遷移を実装しました。
次回【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_5では、より複雑なSpriteKit
のスプライトアニメーションと組み合わせます。また、条件によってステート遷移先を分岐させます。