WWDC 2017 の SceneKit サンプル Fox 2 を調べる その21
今回は、ScaredComponent.swift を見てゆく。
中身的には ChaserComponent.swift とほぼ同じ。
主な変更は GKGoal がプレイヤーキャラの Max を追いかけるのではなく、逃げる(遠ざかる)振る舞いをする。

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()
}
}
}
次回に続く。