NavMeshを使ってキャラクタを動かす方法を紹介します。
NavMeshを使えば、簡単に移動できる範囲を指定でき、
指定した経路にそって自動的にキャラクタを動かすことが出来ます。
目次は次のとおりです
- NavMeshを設定する
- キャラクタにNavMeshAgentをアタッチする
- ゴール地点を指定する
- Off Mesh Linkで飛び地の間を移動する
- Off Mesh Link上の動きを改善する
- まとめ
NavMeshを設定する
まずはキャラクタが移動できるエリアを指定します。経路に指定したいオブジェクトを選択し、インスペクタから「Navigation Static」を選択します。
メニューバーから「Window」→「Navigation」を選択します。
インスペクタの部分にNavigationタブが開くので、右下の「Bake」ボタンを押して下さい。
ボタンを押すと画面上に経路が水色で表示されます。
キャラクタにNavMeshAgentをアタッチする
次に経路にそって歩かせるキャラクタ(ここではSDユニティちゃん)を配置します。
ユニティちゃんが配置できたら、経路に沿って動かすためにNavMesh Agentコンポーネントをアタッチします。
ヒエラルキービューからユニティちゃんのオブジェクトを選択し、インスペクタから「Add Component」→「Navigation」→「Nav Mesh Agent」を選択します。
ゴール地点を指定する
最後にスクリプトからゴール地点を指定します。
プロジェクトビューで「Create」→「C# Script」を選択し、「CharacterController」にリネームします。
次のスクリプトを入力して下さい。
using UnityEngine; using UnityEngine.AI; using System.Collections; public class CharacterController : MonoBehaviour { public Transform target; NavMeshAgent agent; void Start () { agent = GetComponent<NavMeshAgent>(); } void Update () { agent.SetDestination(target.position); } }
スクリプトを保存したら、ヒエラルキービューのユニティちゃんオブジェクトにドラッグ&ドロップしてアタッチします。
最後に、ゴール地点となるオブジェクトを指定します。先ほどアタッチしたUnityChanControllerスクリプトのTargetのスロットにゴール地点になるオブジェクトをドラッグ&ドロップします。
実行するとユニティちゃんが自動的にゴール地点まで移動します。
Off Mesh Linkで飛び地の間を移動する
作成したステージが階段状になったり、飛び地になる場合があります。
このような飛び地で普通にNavMesh使うと、ゴールまでのルートが検出されずに
途中で止まってしまいます(下の例だと、下段に降りられない)
そこで、こういう場合はOff Mesh Linkという機能を利用します。
Off Mesh Linkは飛び地になったNavMeshの経路を繋げる仕組みです。
Off Mesh Linkの使い方は、NavMeshを繋ぎたい両端にオブジェクト(ここではシリンダー)を配置します。
シリンダーのひとつを選択して、インスペクタからAdd Component→Navigation→Off Mesh Linkコンポーネントをアタッチします。このOff Mesh LinkコンポーネントのStart欄とEnd欄にそれぞれシリンダーをドラッグ&ドロップします。
これで2つのNavMeshが接続できました。実行してプレイヤがゴールまでたどり着くことを確認してください。
Off Mesh Link上の動きを改善する
このままだとOff Mesh Linkの上を通るときに、キャラクターが加速する&直線的な動きになってしまい、少し不自然です。そこで、スクリプトを修正してジャンプするように修正しましょう。
先ほど作成したスクリプトを次のように書き換えてください。
using UnityEngine; using UnityEngine.AI; using System.Collections; public class CharacterController : MonoBehaviour { public Transform target; NavMeshAgent agent; void Start() { agent = GetComponent<NavMeshAgent>(); agent.SetDestination(target.position); StartCoroutine(Jump(agent)); } IEnumerator Jump(NavMeshAgent agent) { agent.autoTraverseOffMeshLink = false; while (true) { // Off Mesh Linkに乗るまで待機 yield return new WaitWhile(() => agent.isOnOffMeshLink == false); // NavMeshによる移動を停止 agent.Stop(); // 自前のジャンプ処理 float jumpGravity = -25f; Vector3 src = transform.position; Vector3 dst = agent.currentOffMeshLinkData.endPos; Vector3 v = dst - src; float jumpTime = 0.7f; float v0 = (v.y - jumpGravity * 0.5f * jumpTime * jumpTime)/jumpTime; for (float t = 0; t < jumpTime; t += Time.deltaTime ) { Vector3 p = Vector3.Lerp(src, dst, t/ jumpTime); p.y = src.y + v0 * t + 0.5f * jumpGravity * t * t; transform.position = p; yield return null; } // NavMeshによる移動を再開 transform.position = dst; agent.CompleteOffMeshLink(); agent.Resume(); } } }
このスクリプトではプレイヤーがOff Mesh Linkに乗るまで待機します。Off Mesh Linkに乗ったら一旦NavMeshを使った移動を停止して、自前でジャンプの動作を行います。
ジャンプ動作はジャンプ開始地点と着地点が分かっているので、
y = 初速度 * 時間 + 0.5 * 重力加速度 * 時間^2
として計算しています。この時、ジャンプ終了時点で「シリンダーの高低差」ぶん移動していることを考えて、初速度v0は上記の式にこれを代入し
v0 = (高低差 - 0.5 * 重力加速度 * 滞空時間^2) / 滞空時間
として計算します。
ジャンプが終了したら、再びNavMeshによる移動を有効にします。これで実行すると、次のようにNavMesh間の移動が自然に表現できます。
まとめ
NavMeshを使ってキャラクタを動かす方法を紹介しました。
また、Off Mesh Linkを使うと飛び地になったNavMeshを
つなげて考えることが出来ることを説明しました!
「Unityの教科書」を書きました。よろしくお願いします!(^^)/