WWDC 2017 の SceneKit サンプル Fox 2 を調べる その18
前回に続き BaseComponent.swift の前にそこで使用している GameplayKit の「Agents, Goals, and Behaviors」について見てゆく。
Agents, Goals, and Behaviors とは?
エージェントに対して、なんらかの振る舞いと目標までのゴールを設定すると、エージェントはゴールに対してその動作を行うという機能。
Fox2 ではプレイヤーキャラの Max が近づくと敵が接近、離れたり、Max が遠ざかると一定の場所の周りを動いたりする。
この機能の計算結果から敵の移動座標値を取得しノードへ座標を渡す。
使用するエージェントは X, Y 軸の座標を使用する 2D と X, Y, Z 軸の座標を使用する 3D があり、 Fox2 では敵が高低差のない所にいるため 2D を使用している。
実装の流れ
- GKAgent でエージェントをつくる
- ひとつまたは複数の GKGoal でゴールまでの振る舞いを設定する
- ゴールまで何かをさせたい GKAgent に対して GKBehavior で GKGoal とゴールとなる GKAgent を設定する。
ゴールへの振る舞いの種類
一応、振る舞いのまとめ。
GKGoal のイニシャライズ時に設定する。
一般的な動きの振る舞い
init メソッド | 説明 |
---|---|
init(toSeekAgent: GKAgent) | 指定したエージェントの位置に向かって移動する |
init(toFleeAgent: GKAgent) | 指定したエージェントの位置に離れるように移動する |
init(toReachTargetSpeed: Float) | 指定した値の速度まで加速や減速する |
init(toWander: Float) | 指定した値の速度で、さまよったり、前進やランダムにまわったりする |
回避や追跡の振る舞い
init メソッド | 説明 |
---|---|
init(toAvoid: [GKAgent], maxPredictionTime: TimeInterval) | 他のエージェントの動作を考慮して、指定されたエージェントと衝突しないようなゴールを設定する |
init(toAvoid: [GKObstacle], maxPredictionTime: TimeInterval) | エージェントが指定された静的障害(GKObstacle)と衝突しないようにするためのゴールを設定する |
init(toInterceptAgent: GKAgent, maxPredictionTime: TimeInterval) | ターゲットの動きを考慮して、エージェントが指定された他のエージェントを追いかけることを目的としたゴールを設定する |
群れの振る舞い
init メソッド | 説明 |
---|---|
init(toSeparateFrom: [GKAgent], maxDistance: Float, maxAngle: Float) | 他のエージェントのグループから指定された距離を維持するゴールを設定する |
init(toAlignWith: [GKAgent], maxDistance: Float, maxAngle: Float) | 他のエージェントのグループから指定された方向を整列させるゴールを設定する |
init(toCohereWith: [GKAgent], maxDistance: Float, maxAngle: Float) | 他のエージェントのグループから指定された位置の近くに留まるゴールを設定する |
パスに沿って動く振る舞い
init メソッド | 説明 |
---|---|
init(toStayOn: GKPath, maxPredictionTime: TimeInterval) | 指定されたパス内でエージェントの位置を維持するゴールを設定する |
init(toFollow: GKPath, maxPredictionTime: TimeInterval, forward: Bool) | 指定されたパスに沿うようにエージェントの位置をゴールとして設定する |
簡単なサンプルつくってみる
SceneKit のテンプレートの宇宙船を半径 20 で回転。
球(SCNShpere)に GKGoal の toSeekAgent を設定し宇宙船を追いかける。
初期設定
新規で SceneKit テンプレート のプロジェクトを作成。 前回の方法で GamaplayKit をインポートする。
ship.scn の修正
ship と shipMesh の間の階層に空のノードを追加し、X座標を -20 にする。
GameViewController の修正
GameplayKit と SCNSceneRenderer をいじるのため、インポートとGameViewController クラス宣言の UIViewController の横にイカを追加。
import GameplayKit
class GameViewController: UIViewController, SCNSceneRendererDelegate { ... }
メンバ変数
追いかける元となる宇宙船のエージェントと agent1 と、追いかける球のエージェント agent2、shipNode 宇宙船の移動位置を参照するノードと球ののノードをクラス内で参照できるようにメンバ変数にする。 差分の時間を保持するため TimeInterval も設定する。
var agent1 = GKAgent2D() var agent2 = GKAgent2D() var shipNode:SCNNode! var shipNode:SCNNode! var previousUpdateTime: TimeInterval = 0
カメラ位置変更
宇宙船が半径 20 出回るためカメラの位置と回転を変更
cameraNode.position = SCNVector3(x: 0, y: 10, z: 60) cameraNode.eulerAngles = SCNVector3(-0.125,0,0)
宇宙船のアニメーション変更
宇宙船のアニメーションが速すぎるため、duration の 1 を 3 に変更。
ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 3)))
ノードの初期設定
ship.runAction の下に ship のシーンファイルで設定した shipNode を設定する。
shipNode = ship.childNode(withName: "shipNode", recursively: true)
追いかける球の設定。
ballNode = SCNNode(geometry: SCNSphere(radius: 3)) ballNode.position = SCNVector3(0,0,0) scene.rootNode.addChildNode(ballNode)
エージェント初期設定
agent1 は宇宙船の位置を保持するエージェントの設定をする。
agent1.position = float2(shipNode.worldPosition.x, shipNode.worldPosition.z)
agent2 は宇宙船を元に追いかけるエージェントの設定をする。
agent2.position = float2(0, 0) agent2.mass = 0.3 agent2.maxAcceleration = 16 agent2.maxSpeed = 30 agent2.behavior = GKBehavior(goal: GKGoal(toSeekAgent: agent1), weight: 1)
SCNSceneRendererDelegate
再生毎に処理する SCNSceneRendererDelegate を設定する。
ひとまず、SCNView で delegate を設定。
scnView.delegate = self
viewDidLoad の設定は終了。
カッコを抜け renderer の関数を作成。
その内で calcTime で経過時間を設定。 agent1 に shipNode の X、Z 座標を設定し、agent2 で update 関数を呼ぶと追跡している位置が更新される。
その後、ballNode に agent2 の位置を渡し移動させる
最後に現在の時間を previousUpdateTime に渡す。
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { var calcTime: TimeInterval = 0 if previousUpdateTime != 0 { calcTime = time - previousUpdateTime } agent1.position = float2(shipNode.worldPosition.x, shipNode.worldPosition.z) agent2.update(deltaTime: calcTime) ballNode.position = SCNVector3(agent2.position.x, 0, agent2.position.y) print("agent1: \(agent1.position)") print("agent2: \(agent2.position)") previousUpdateTime = time }
ビルドすると、球体が宇宙船を追っかける
サンプルファイル
まとめ
ひとまず、GKAgent2D を軽く動かすサンプルを作成してみた。
このレベルだと、SCNAccelerationConstraint と SCNDistanceConstraint を使用した方がつくるのは速いのだが、 Fox2 のように複数の振る舞いを遷移させたり、ミックスさせたりするのに便利だし、GKConponent と絡めると流用ができる。
次回は BaseComponent.swift を見てゆく。