前回
【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_6は、給水機の表示ランプ点滅をレンダリングで実装しました。
この記事について
給水機の状態を管理するステートマシンを完成させます。
手順
補給状態を定義
補給アニメーションと自動的なステート遷移
水あり状態からの補給
補給状態(RefillingState)を定義
タンクが空になったら、水を補給できるようにします。
補給中の状態を表現するステートRefillingState
を定義します。
メニューバーから「File > New > File... > iOS > Swift File」を選択する。(RefillingState.swift)
GameplayKit
フレームワークをインポートして、DispenserState
クラスを継承したRefillingState
クラスを定義します。
import GameplayKit
class RefillingState: DispenserState {
static let waterNodeHeight: CGFloat = 280
init(game: GameScene) {
super.init(game: game, associatedNodeName: "RefillingState")
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return (stateClass is FullState.Type)
}
}
必要となるイニシャライザとイベントメソッドを実装しておきます。
RefillingState
の次のステートはFullState
のみ遷移できるようにします。
waterNodeHeight
プロパティは、満タン時のタンク水量です。
補給のアニメーションと自動的なステート遷移
リフィル(補給)のアニメーション後、自動的にフル状態FullState
へ遷移するようにします。
上記の機能をplayRefillAnimationThenExit()
メソッドに実装します。
import GameplayKit
class RefillingState: DispenserState {
static let waterNodeHeight: CGFloat = 280
init(game: GameScene) {
super.init(game: game, associatedNodeName: "RefillingState")
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return (stateClass is FullState.Type)
}
func playRefillAnimationThenExit() {
let waterNode = game.scene?.childNode(withName: "//water") as! SKSpriteNode
let consumptionOfwater = RefillingState.waterNodeHeight - waterNode.size.height
let waterRatio = consumptionOfwater / RefillingState.waterNodeHeight
let refillTime = TimeInterval(waterRatio)
let refillAction = SKAction(named: "refillDispenser", duration: refillTime)!
waterNode.run(refillAction, completion: {
self.stateMachine?.enter(FullState.self)
})
}
}
ステートマシンにRefillingState
を登録
ゲームシーンのステートマシンに補給状態を登録します。
class GameScene: SKScene {
var stateMachine: GKStateMachine!
override func didMove(to view: SKView) {
let fullState = FullState(game: self)
let serveState = ServeState(game: self)
let partiallyFullState = PartiallyFullState(game: self)
let emptyState = EmptyState(game: self)
let refillingState = RefillingState(game: self)
stateMachine = GKStateMachine(states: [fullState,
serveState,
partiallyFullState,
emptyState,
refillingState])
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.")
refillButton?.run(SKAction(named: "buttonPressed", duration: 0.6)!)
stateMachine.enter(RefillingState.self)
} else {
print("attempt to dispense")
stateMachine.enter(ServeState.self)
}
}
}
touchedEnded
メソッドも修正しています。
リフィルボタンの場所がタップされていれば、buttonPressed
アクションを見せた後にステートマシンはRefillingState
に遷移します。
続いて、EmptyState
クラスのisValidNextState
メソッドで、RefillingState
クラスのみに遷移するようにしておきます。
class EmptyState: DispenserState {
init(game: GameScene) {
super.init(game: game, associatedNodeName: "EmptyState")
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
let red = SKColor.red
changeIndicatorLightToColor(red)
}
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 RefillingState.Type)
}
}
水あり状態PartiallyFullState
からの補給
タンクがカラになる前、水が残っている状態PartiallyFullState
でも補給できるようにします。
isValidNextState()
メソッドで、ServeState
かRefillingState
に遷移できるように記述します。
class PartiallyFullState: DispenserState {
init(game: GameScene) {
super.init(game: game, associatedNodeName: "PartiallyFullState")
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
print("water is partially full...")
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass {
case is ServeState.Type, is RefillingState.Type:
return true
default:
return false
}
}
}
完成
7回にわたってGameplayKit
フレームワークのStateパターン実装を解説しました。
給水機の状態はとてもわかりやすい教材でした。
Stateパターンを駆使すれば、野球やサッカーのようなゲームも開発しやすくなるのかもしれません。
野球なら攻撃状態と守備状態・サッカーならボールホルダーとオフザボールプレイヤーを表現できます。
Entity-Componentパターンと組み合わせれば、より本格的なゲームも効率よく開発できそうですね。
【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_1
【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_2
【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_3
【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_4
【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_5
【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_6
【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Final