Apple Engine

Apple, iPhone, iOS, その周辺のことについて

WWDC 2017 の SceneKit サンプル Fox 2 を調べる その21

今回は、ScaredComponent.swift を見てゆく。
中身的には ChaserComponent.swift とほぼ同じ。

主な変更は GKGoal がプレイヤーキャラの Max を追いかけるのではなく、逃げる(遠ざかる)振る舞いをする。

f:id:x67x6fx74x6f:20180417170257p:plain

 

ScaredComponent の中身

import

こちらは SIMD の計算が行われない。

import GameplayKit

 

遠ざかる敵キャラの状態

遠ざかる敵キャラの状態を以下の enum で設定している。

  • 周りをぶらつく状態
  • Max から逃げる状態
  • 倒されてしまった状態
enum ScaredState: Int {
    case wander
    case flee
    case dead
}

 

ScaredComponent クラスの中身

BaseComponent を継承した ScaredComponent クラス。 以下の中身を見てゆく。

class ScaredComponent: BaseComponent {
    ...
}

 

GKInspectable

scene.scn の enemy2 の Components でも設定変更可能な変数。
scene.scn で設定している値が優先されるが、デフォルト値は同じようになっている。

hitDistance はなく handleEnemyResponse に直接書かれている。

@GKInspectable var fleeDistance: Float = 2.0
@GKInspectable var fleeSpeed: Float = 5.0
@GKInspectable var wanderSpeed: Float = 1.0
@GKInspectable var mass: Float = 0.326
@GKInspectable var maxAcceleration: Float = 2.534

 

player 変数

PlayerComponent がセットされると、ChaserComponent の GKAgent の各設定がされる。

toSeekAgent が toFleeAgent を使用した fleeGoal に変更されているだけど内容は ChaserComponent と同じ。

var player: PlayerComponent? {
    didSet {
        agent.mass = mass
        agent.maxAcceleration = maxAcceleration
        fleeGoal = GKGoal(toFleeAgent: player!.agent)
        wanderGoal = GKGoal(toWander:wanderSpeed)

        let centers: [float2] = [
            [-1, 9],
            [1, 9],
            [1, 11],
            [-1, 11]
        ]

        let path = GKPath( points: centers, radius: Float(0.5), cyclical: true )
        centerGoal = GKGoal(toStayOn: path, maxPredictionTime: 1)
        behavior = GKBehavior(goals: [fleeGoal!, wanderGoal!, centerGoal!])
        agent.behavior = behavior
        startWandering()
    }
}

 

メンバ変数

ChaserComponent とほぼ同じ。 state が ScaredState で設定されており、speed がなくなっている。 

private var state = ScaredState(rawValue: 0)!
private var fleeGoal: GKGoal?
private var wanderGoal: GKGoal?
private var centerGoal: GKGoal?
private var behavior: GKBehavior?

 

 startWandering()、startFleeing()

ChaserComponent とほぼ同じ。 startWandering() は chaseGoal が fleeGoal に変更。

startChasing() は startFleeing() になり、 startWandering() 同様に chaseGoal が fleeGoal に変わり、state が flee になっている。

func startWandering() {
    guard let behavior = behavior else { return }

    behavior.setWeight(1, for: wanderGoal!)
    behavior.setWeight(0, for: fleeGoal!)
    behavior.setWeight(0.3, for: centerGoal!)
    state = .wander
}

func startFleeing() {
    guard let behavior = behavior else { return }

    behavior.setWeight(0, for: wanderGoal!)
    behavior.setWeight(1, for: fleeGoal!)
    behavior.setWeight(0.4, for: centerGoal!)
    state = .flee
}

 

isDead()

ChaserComponent と同じであるため割愛。

 

override func update(deltaTime seconds: TimeInterval)

ChaserComponent と同様に
BaseComponent の update 関数をオーバーライドして処理を変えている。

switch の state の分岐が .chase から .flee に変更し、 if 文の変数が chaseDistance から fleeDistance に変わっている。

また、speed の変数がないため、該当部分の1行がない。

override func update(deltaTime seconds: TimeInterval) {
    if state == .dead {
        return
    }

    guard let character = player?.character else { return }
    guard let playerComponent = (player?.entity?.component(ofType: GKSCNNodeComponent.self)) else { return }
    guard let nodeComponent = entity?.component(ofType: GKSCNNodeComponent.self) else { return }

    let playerNode = playerComponent.node
    let enemyNode = nodeComponent.node
    let distance = simd_distance(enemyNode.simdWorldPosition, playerNode.simdWorldPosition)

    switch state {
        case .wander:
            if distance < fleeDistance {
                startFleeing()
            }
        case .flee:
            if distance > fleeDistance {
                startWandering()
            }
        case .dead:
            break
    }

    handleEnemyResponse(character, enemy: enemyNode)

    super.update(deltaTime: seconds)
}

 

private func handleEnemyResponse(_ character: Character, enemy: SCNNode)

hitDistance が 0.5 と書かれているだけで ChaserComponent と変更はない

private func handleEnemyResponse(_ character: Character, enemy: SCNNode) {
    let direction = enemy.simdWorldPosition - character.node.simdWorldPosition
    if simd_length(direction) < 0.5 {
        if character.isAttacking {
            state = .dead

            character.didHitEnemy()

            performEnemyDieWithExplosion( enemy, direction: direction)
        } else {
            character.wasTouchedByEnemy()
        }
    }
}

次回に続く。