#この記事について
GameplayKitフレームワークが提供するStatemachineアーキテクチャを使います。
詳細に関しては、前回の記事を参照してください。
#前回の投稿
【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_1が前回の記事です。
プロジェクト作成~ゲームシーンの構築までを行いました。
#動作環境
Xcode 8.2
Swift 3.01
iOS 10.2
#手順
大まかな流れは以下のようになります。
- タッチを検出する
- リフィルボタンのタップを識別する
- 給水機の状態を表現する
DispenserState
を定義する
##タッチを検出する
GameScene
クラスのtouchesEnded(_:with:)
メソッドをオーバーライドします。
タッチ座標を検出してコンソールに出力しておきます。
class GameScene: SKScene {
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!)
print("Point in scene: ", locationInScene)
}
}
###ビルド
シミュレータでビルドしたら、画面の適当な場所をタップしてコンソールを確認します。
座標系は、画面左下が原点になっています。
##リフィルボタンのタップを識別する
リフィルボタンをタップされたら、補給(リフィル)しなければいけません。
また、タップされた座標がリフィルボタン以外の場所であれば給水(ディスペンス)しましょう。
touchesEnded(_:with:)
メソッドに編集します。
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")
}
}
atPoint()
メソッドは、引数の座標オブジェクトにある最も手前のSKNode
オブジェクトを返します。
###ビルド
シミュレータ上の画面をタップして、その座標にボタンがあるかないかで給水機の動作が変化するかをコンソールで確認します。
ボタンをタップした場合だけ「attempt to refill」と表示されれば問題ありません。
##給水機の状態DispenserState
を定義する
ここから、各種状態(state)を表現するクラスを定義していきます。状態を表現する全てのクラスはGKState
クラスを継承します。
まずは、給水機が変化しうる全ての状態の親クラスとなるDispenserState
クラスを定義します。
このDispenserState
クラスには、全Stateに共通する以下のようなプロパティ・機能を実装します。
- プロパティ
ゲームシーン
自身が従属する状態名 - 機能
自身の状態に突入した時に、スプライトをハイライト
自身の状態から脱出した時には、スプライトをハイライト終了
メニューバーから「File > New > File... > iOS > Cocoa Touch Class」を選択する
GKState
のサブクラスで、ファイル名はDispenserState.swift
としておきます。
import SpriteKit
import GameplayKit
class DispenserState: GKState {
}
SpriteKit
とGameplayKit
をインポートしておきます。
###プロパティとイニシャライザを実装
ゲームシーンにある状態遷移図から、変化しうる状態名を取得できるようにします。
class DispenserState: GKState {
let game: GameScene
let associatedNodeName: String
init(game: GameScene, associatedNodeName: String) {
self.game = game
self.associatedNodeName = associatedNodeName
}
}
初期値がないメンバワイズプロパティは、イニシャライザで初期化する必要があります。
続いて、機能を実装します。
状態の突入時と脱出時の振る舞いはそれぞれ、didEnter(from:)
メソッドとwillExit(to:)
メソッドをオーバーライドします。
associatedNodeName
プロパティには、イニシャライザで状態クラスが生成された時に自身がどの状態名であるかが格納されます。
class DispenserState: GKState {
let game: GameScene
let associatedNodeName: String
init(game: GameScene, associatedNodeName: String) {
self.game = game
self.associatedNodeName = associatedNodeName
}
override func didEnter(from previousState: GKState?) {
let associatedNode = game.childNode(withName: "//\(associatedNodeName)") as? SKSpriteNode
guard let node = associatedNode else { return }
node.color = SKColor.lightGray
}
override func willExit(to nextState: GKState) {
let associatedNode = game.childNode(withName: "//\(associatedNodeName)") as? SKSpriteNode
guard let node = associatedNode else { return }
node.color = SKColor.darkGray
}
func changeIndicatorLightToColor(_ color: SKColor) {
let indicator = game.childNode(withName: "//indicator") as! SKSpriteNode
indicator.color = color
}
}
イニシャライザで取得しておいたゲームシーンや状態名から、操作したいスプライトをその都度取得しています。
changeIndicatorLightToColor()
メソッドは、給水機が動作中にその表示ランプを任意の色に変化させます。
##次回
状態を表現するGKState
クラスを継承して、給水機が変化しうる全ての状態で実装すべき機能を持った親クラスを定義しました。
GKState
クラスには、状態が変化するごとに呼ばれるdidEnter(from:)
メソッドやwillExit(to:)
メソッドがあることがわかります。
【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_3
給水機の状態を表現するDispenserState
クラスを継承して、各状態のクラスを定義していきます。