今回はUnityのナビゲーション機能でゲーム実行中にナビメッシュをベイクしてみたいと思います。
ナビゲーション機能については
や「ナビゲーション機能」カテゴリの中にもいくつか記事があるのでそちらを参照してください。
ゲーム実行中にナビメッシュが出来ると、イベントをクリアしたら別のフィールドへの道が表示され、そのフィールドを動的にナビメッシュしてNavMeshAgentで移動させる事が出来ます。
NavMeshComponentsアセットのインポート
動的なナビメッシュをする為にはNavMeshComponentsというアセットが必要ですが、NavMeshComponentsアセットは追加アセットなので
↑からZIPファイル等でダウンロードしてください。
ZIPファイルをダウンロードした人はファイルを解凍し、フォルダの階層を辿ってNavMesComponentsフォルダをUnityのAssetsフォルダにドラッグ&ドロップします。
これで準備が出来ました。
ナビゲーション機能が新しくなってる!?
ついこの間までUnity5.3.4f1だったものでUnity2017にしたらナビゲーションのインスペクタが変わっていますね。
そこで、Navigationウインドウについて少し見ていきます。
NavigationウインドウのAgentsタブを選択すると、
↑のようにAgent Typesを追加する事が出来ます。
Agent TypeはNavMeshAgentで選択する事が出来、エージェントのタイプによってベイクするナビメッシュを変更できます。
BakeタブではNavigation Staticにチェックを入れたゲームオブジェクトのベイクがされますが、Agent Type毎にベイクを分ける事は出来ないようです。
NavMeshComponentsアセットのNavMeshSurfaceコンポーネントを使うとAgent Typeに合わせたベイクが出来ます。
NavMeshSurface
NavMeshSurfaceコンポーネントを使うと指定したAgent Typeのナビメッシュをベイクする事が出来ます。
NavMeshSurfaceはUnityメニューのGameObject→AI→NavMesh Surfaceを選択して作成するか、空のゲームオブジェクト等を作成しインスペクタのAdd ComponentからNavigation→NavMeshSurfaceを取り付けます。
今回は空のゲームオブジェクトを作成し、名前をNavMesh Surfaceという名前にし2つのNavMeshSurfaceコンポーネントを取り付けます。
2つ取り付けるのはAgent Type毎にナビメッシュをベイクする為です。
あらかじめNavigationウインドウでAgent Typeを2つ作っておきます。
NavMeshSurfaceの項目を見ていきます。
Agent Typeはエージェントのタイプ。
Collect Objectはベイクする対象のゲームオブジェクトを指定します。
Allはヒエラルキー上の全てのゲームオブジェクト
Volumeはコライダのような範囲を設定し、その中にあるゲームオブジェクト
ChildrenはNavMeshSurfaceが取り付けられたゲームオブジェクトの子要素のゲームオブジェクト
になります。
Include Layersはナビメッシュに含めるレイヤーの指定です。
Use GeometoryでRender Meshesはメッシュ、Physics Collidersはコライダをベイクします。
Default Areaはデフォルトのナビメッシュのエリアを指定します(例えばJumpを指定すると通常のナビメッシュエリアがJumpになりコストが上がります)。
設定としてはこんな感じで、先ほどの画像のような設定をしておきます。
Agent TypeをHumanoidにしたものはCollect ObjectsがChildrenなので、NavMeshSurfaceのベイクをすると子要素のゲームオブジェクトを使ってナビメッシュを作成します。
Agent typeをMonsterにしたものはCollect ObjectsがAllなので、NavMeshSurfaceのベイクをするとヒエラルキー上の全てのゲームオブジェクトを使ってナビメッシュを作成します。
どちらもStaticにチェックを入れなくてもベイクが出来ます。
次にNavMesh Surfaceゲームオブジェクトの子要素にいくつか3D Objectを配置し、Scale等を変更します。
実際に配置したゲームオブジェクトは
↑のような感じになります(キャラクターは関係ありません)。
NavMeshModifier
左上のCubeで階段のような物を作っていますが、上の階のゲームオブジェクトにAdd ComponentからNavigation→NavMeshModifierを取り付けます。
NavMeshModifierコンポーネントはナビメッシュする時のこのゲームオブジェクトの動作を設定する事が出来ます。
Ignore Buildにチェックを入れるとベイクされません。
Override Areaにチェックを入れると、このゲームオブジェクトのArea Typeを上書き出来ます。
Affected Agentsは影響を及ぼすエージェントの指定です。
NavMeshModifierVolume
下の階のゲームオブジェクトにAdd ComponentからNavigation→NavMeshModifierVolumeを取り付けます。
NavMeshModifierVolumeは範囲を指定してエリアを設定できます。
これで個別のゲームオブジェクトのエリア設定が出来たので、NavMesh SurfaceゲームオブジェクトのNavMeshSurfaceコンポーネントでAgent TypeがHumanoidのもののBakeボタンを押してベイクしてみます。
↑のように階段のようにしたCubeのエリアが変わっているのがわかります。
NavMeshLink
次はNavMeshLinkコンポーネントについて見ていきましょう。
空のゲームオブジェクトを作成し、名前をNavMeshLinkとします。
NavMeshLinkの子要素にCubeを作成し、Scaleを変更して今いるフィールドと少し離れた場所に移動させます。
NavMeshLinkにはAdd ComponentからNavigation→NavMeshSurfaceを取り付けCollect ObjectsをChildrenに変更して、Bakeボタンを押します。
ベイクすると、
↑のように今いるフィールドと少し離れたところにNavMeshLinkゲームオブジェクトがあり、ナビメッシュがベイクされています。
このままの状態だと今いるフィールドからNavMeshLinkゲームオブジェクトのフィールドに移動出来ません。
別々にベイクされたナビメッシュを移動出来るようにするのがNavMeshLinkコンポーネントです。
NavMeshLinkゲームオブジェクトのインスペクタでAdd ComponentからNavigation→NavMeshLinkコンポーネントを取り付けます。
Bidirectionalにチェックを入れると相互に移動出来ます。
チェックを入れないとスタート位置からエンド位置の方向のみ移動出来ます。
ここら辺はオフメッシュリンクと同じですね。
シーンビューに茶色い四角が表示されます。
これがスタート位置とエンド位置でクリックするとそれを移動させる事が出来るので、今のフィールドとNavMeshLinkゲームオブジェクトのフィールドを繋げるように移動させます。
位置の移動はNavMeshLinkコンポーネントで数値を指定する事でも出来ます。
うまく配置すると
↑のようにフィールドとフィールドが繋がります。
同じフィールドではOffMeshLinkで繋がりますが、別のナビメッシュを繋ぐにはNavMeshLinkを使うという感じですね。
ただこのままだと1点と1点が繋がっているだけなので幅を持たせたいと思います。
NavMeshLinkコンポーネントのWidthの値を増やすと、スタート位置とエンド位置に幅が出来、移動出来る範囲が広がります。
キャラクターを動かしてフィールドを移動させてみる
キャラクターにNavMeshAgentを取り付け、移動スクリプトを取り付けてフィールドを渡れるかどうか試してみます。
今回はマウスクリックした位置にキャラクターを動かすようにします。
詳しくは
を参照してください。
スクリプトは
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class NavigationChara : MonoBehaviour { // アニメータコントローラ private Animator animator; // レイを飛ばす距離 private float rayRange; // 移動する位置 private Vector3 targetPosition; // ナビゲーションエージェント private NavMeshAgent agent; void Start () { animator = GetComponent<Animator>(); rayRange = 100f; targetPosition = transform.position; agent = GetComponent<NavMeshAgent>(); agent.SetDestination(targetPosition); } void Update () { // マウスクリックまたはmouseDownModeがOffの時マウスの位置を移動する位置にする if(Input.GetButtonDown("Fire1")) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if(Physics.Raycast(ray, out hit, rayRange, LayerMask.GetMask ("Field"))) { targetPosition = hit.point; agent.SetDestination(targetPosition); } } // 目的地に近付いたら走るアニメーションをやめる if(agent.remainingDistance < 0.1f){ animator.SetFloat("Speed", 0f); agent.isStopped = true; } else { animator.SetFloat("Speed", agent.desiredVelocity.magnitude); agent.isStopped = false; } } } |
を使います。
移動出来る地面にはFieldという名前のレイヤーを設定しておく必要があります。
試してみると
↑のような感じで移動が出来ました。
動的にナビメッシュしたサンプルの作成
NavMeshComponentsについて見てきました。
これらを使って動的にナビメッシュをベイクしてみましょう。
動的にナビメッシュをベイクというのは実はNavMeshSurfaceの命令で簡単に出来てしまいます。
NavMeshSurfaceコンポーネントの参照をnavMeshSurfaceに入れていたとすると、
1 2 3 | navMeshSurface.BuildNavMesh (); |
と、動的にナビメッシュしたい箇所に記述するだけです。
簡単ですね(^^)/
ほんと、動的にナビメッシュする方法は数行で解説終わりですよ・・・・(‘◇’)ゞ
これだけだとあれなんで・・・・、動的にナビメッシュするサンプルを作成してみたいと思います。
概要としては今のフィールドから別のフィールド近くに移動すると別のフィールドから徐々に道が伸びてきて、伸びきったら動的にナビメッシュをベイクし、キャラクターが別のフィールドに行けるようになる。
という感じのものを作りたいと思います。
別のフィールドを作成
まずは空のゲームオブジェクトを作り、名前をAnother NavMesh Surfaceとします。
Another NavMesh SurfaceゲームオブジェクトにはNavMeshSurfaceとNavMeshLinkコンポーネントを取り付けます。
NavMeshLinkのStart PointとEnd Pointはこの後作成する道と今のフィールドの辺りに移動させます。
Another NavMesh Surfaceの子要素に空のゲームオブジェクトを作成し、名前をRoadとします。
RoadにはShowTheWayスクリプトを作成し取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class ShowTheWay : MonoBehaviour { // 道を表示しているかどうか private bool isShow; [SerializeField] private float durationTime = 0.5f; // NavMeshSurfaceコンポーネント private NavMeshSurface navMeshSurface; void Start() { navMeshSurface = transform.parent.GetComponent<NavMeshSurface> (); } // 道を表示する public void Show() { StartCoroutine(BakeNavMesh()); isShow = true; } // 道を表示した後、動的にナビメッシュする IEnumerator BakeNavMesh() { for (int i = 0; i < transform.childCount; i++) { transform.GetChild (i).gameObject.SetActive (true); yield return new WaitForSeconds (durationTime); } navMeshSurface.BuildNavMesh (); } // 道を表示しているかどうかを返す public bool IsShow() { return isShow; } } |
Showメソッドが呼ばれたらコルーチンを使ってRoadゲームオブジェクトの子要素のゲームオブジェクトを順番にアクティブにし、それが終わったら動的にナビメッシュをベイクします。
Roadの子要素にはCubeを3つ作成し、順番に別のフィールドの方から並べます。
階層の順番が肝心でRoadの子要素の1番目が別のフィールドと接触し、子要素の3番目が今のフィールドと接触する位置に配置します。
実際に出来たのが、
↑のような別のフィールドと道です。
Roadの子要素のCube達はキャラクターが近づくまでは表示させないのでインスペクタの名前の横のチェックを外し非アクティブにしておきます。
Another NavMesh Surfaceゲームオブジェクトに取り付けたShowTheWayスクリプトのShowメソッドは、キャラクターが今のフィールドにある検知エリアに侵入した時に実行するようにします。
今のフィールドにキャラクターの検知エリアを作成する
NavMesh Surfaceゲームオブジェクトの子要素にCubeを作成し名前をShowRoadAreaとしてScaleを調整して検知範囲として使います。
Box ColliderのIs Triggerにチェックを入れ物理的に当たらないようにします。
ここで、移動出来る地面はFieldレイヤーを設定するとしましたが、ShowRoadAreaはただの検知範囲として使います。
ShowRoadAreaのレイヤーはデフォルトのdefaultレイヤーのままにしておきます。
NavMesh SurfaceゲームオブジェクトのAgent TypeがHumanoidのNavMeshSurfaceコンポーネントのInclude Layersでdefaultのチェックを外し、defaultレイヤーが設定されたゲームオブジェクトはナビメッシュの対象外にしておきます。
これでShowRoadAreaはナビメッシュの対象から外れました。
ShowRoadAreaにはShowRoadAreaスクリプトを取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class ShowRoadArea : MonoBehaviour { // ShowTheWayスクリプト [SerializeField] private ShowTheWay showTheWay; void OnTriggerEnter(Collider col) { if (col.tag == "Player") { if (!showTheWay.IsShow ()) { showTheWay.Show (); } } } } |
キャラクターにはPlayerタグを設定しておき、キャラクターを検知したらShowTheWayスクリプトのShowメソッドを実行します。
検知エリアでキャラクターを検知するにはキャラクターにCharacterControllerコンポーネントが付いているか、コライダ+Rigidbodyが付いている必要があります。
そこでキャラクターにRigidbodyとCapsuleColliderを取り付けておきます。
RigidbodyのIsKinematicにチェックとCapsuleColliderのIsTriggerにチェックを入れます。
これで機能が完成しました。
動的なナビメッシュ機能のサンプルの確認
機能が出来たので動的なナビメッシュのベイクが出来るかどうかと道が徐々に表示されるかどうかを確認してみます。
↑のようになりました。
シーンビューの方で道が繋がった後でナビメッシュがベイクされているのがわかりますね。