2020/11/09
すごく今更ながら、最近Unityの「NavMesh」を触ってみました。
以前私の作ったゲームでは後ろからアザラシがついてきますが、「障害物に詰まり続ける」「前に海 (落下するとダメージ) があっても突撃する」など、その挙動には大きな問題があります。それをNavMeshで解決できないか考えました。
しかしNavMeshAgentの位置はTransformでもRigidbodyでもない、言わば「第3の位置情報」のようなもので、私が今まで作っていた処理との組み合わせがやりにくいです。
そこでNavMeshAgentが計算して出した経路だけを利用できないか考えてみたのが今回のお話です。
やったこと
普通にNavMeshAgentを使うとエージェントがオブジェクトを動かしてしまうので、パラメータを弄って動かないようにします。
ここを変えても特に経路に影響はないようなので安心です。他のパラメータはゲームやキャラクターに合わせてお好みで。
経路を参照して利用する
NavMeshAgent.SetDestination()
でターゲットを設定するとNavMeshAgent.path.corner
に経路が設定されるので、それを参照します。
私のアザラシの場合「キャラクターのいる位置を向いてtransform.forward
方向に前進」というロジックなので、キャラクター位置の部分を差し替えました。以下に該当部分のスクリプトを記載します。
protected Rigidbody rb; protected NavMeshAgent navMeshAgent; void Start() { rb = this.GetComponent<Rigidbody>(); navMeshAgent = this.GetComponent<NavMeshAgent>(); } void Update() { // ここでDoMoveを呼び出す処理 } private void DoMove(Vector3 targetPosition) { if (navMeshAgent && navMeshAgent.enabled) { navMeshAgent.SetDestination(targetPosition); foreach (var pos in navMeshAgent.path.corners) { var diff2d = new Vector2( Mathf.Abs(pos.x - transform.position.x), Mathf.Abs(pos.z - transform.position.z) ); if (0.1f <= diff2d.magnitude) { targetPosition = pos; break; } } Debug.DrawLine(transform.position, targetPosition, Color.red); } Quaternion moveRotation = Quaternion.LookRotation(targetPosition - transform.position, Vector3.up); moveRotation.z = 0; moveRotation.x = 0; transform.rotation = Quaternion.Lerp(transform.rotation, moveRotation, 0.1f); float forward_x = transform.forward.x * moveSpeed; float forward_z = transform.forward.z * moveSpeed; rb.velocity = new Vector3(forward_x, rb.velocity.y, forward_z); }
Update()
からDoMove()
を呼び出す処理はターゲット切り替えの兼ね合いから複雑なので割愛します。プレイヤーに追従する場合はプレイヤーのposition、敵キャラクター攻撃中は敵キャラのpositionを入れています。
corner
にはターゲットまでの全経路がVector3型の配列で入っています。NavMeshAgentが都度再計算するので、最初の要素だけを参照すれば十分です。
ただしオブジェクトの中心点によっては最初のcorner
位置が真下の地面になることがあるので、X軸とZ軸が現在位置とほとんど変わらない場合は無視しました。スクリプト上のdiff2d.magnitude
を比較している部分がそれですね。
Bakeして実行
あとは通常のNavMeshと同様、Bakeして移動範囲を作ってから実行します。
障害物を迂回して追従してくるのが分かりますね。
デバッグ用に経路をGizmoで表示する
開発時はNavMeshAgentがどのような経路を算出しているかを画面に表示すると、デバッグが捗るのでおすすめです。
以下はOnDrawGizmos()
を使う場合のスクリプトです。
void OnDrawGizmos() { if (navMeshAgent && navMeshAgent.enabled) { Gizmos.color = Color.red; var prefPos = transform.position; foreach (var pos in navMeshAgent.path.corners) { Gizmos.DrawLine(prefPos, pos); prefPos = pos; } } }
ビュー右上の「Gizmos」をクリックしてGizmoを表示させます。
この状態で再生すると、経路が赤い線で表示されます。
オブジェクトが大きかったり地形が入り組んでいる場合、ビュー左上の「Shading Mode」を「Wireframe」にしてみましょう。
こちらだと先ほどお話した「最初のcorner
位置が真下の地面になることがある」が分かりやすいです。