LoginSignup
1
2

More than 5 years have passed since last update.

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

Posted at

前回

【GameplayKit】iOSゲームアプリにおけるStateパターン実践 Part_6は、給水機の表示ランプ点滅をレンダリングで実装しました。

この記事について

給水機の状態を管理するステートマシンを完成させます。

手順

補給状態を定義
補給アニメーションと自動的なステート遷移
水あり状態からの補給

補給状態(RefillingState)を定義

タンクが空になったら、水を補給できるようにします。
補給中の状態を表現するステートRefillingStateを定義します。
メニューバーから「File > New > File... > iOS > Swift File」を選択する。(RefillingState.swift)
GameplayKitフレームワークをインポートして、DispenserStateクラスを継承したRefillingStateクラスを定義します。

RefillingState.swift
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()メソッドに実装します。

RefillingState.swift
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を登録

ゲームシーンのステートマシンに補給状態を登録します。

GameScene.swift
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クラスのみに遷移するようにしておきます。

EmptyState
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()メソッドで、ServeStateRefillingStateに遷移できるように記述します。

PartiallyFullState.swift
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

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2