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