テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】NavMeshのデータを実行時にロードしたい

NavMeshの地形データをランタイムにロードする

 Sceneに紐付いているNavMeshではなく、動的にNavMeshの経路探索に使用するデータをロード・アンロードする方法について考えてみます。

 主な用途としては以下の2つです。自分の場合は前者に当たりました。

  • SceneとnavMeshを紐付けたくない場合
  • 地形が変化するが動的なNavMesh計算はしたくない場合

NavMeshをベイクする

 まずSceneに配置したメッシュの情報を利用してNavMeshデータを構築します。

 もしNavMeshComponentsを導入している場合、NavMeshSurfaceをシーン内に配置しBakeボタンを押します。これでSceneファイル以下にNavMeshDataが構築されます。これにNavMeshのデータが格納されています。

f:id:tsubaki_t1:20191103232738j:plain
NavmeshSurfaceを使用する場合

 一方、NavMeshComponentを使用しない場合、エディターAPIを使ってNavMeshを構築します。使用するAPINavMeshBuilder.CollectSourcesInStageNavMeshBuilder.BuildNavMeshDataです。今回の場合、動的にロードすることが目的なのでResourcesに突っ込んでいます。実際にはAddressableなりAssetBundleなりに格納する形になると思います。

  1. 下のコードを導入
  2. NavMeshを作りたいステージの親オブジェクトにBuildNavMeshを追加
  3. コンテキストメニューからCreateNavmeshを選択
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class BuildNavMesh : MonoBehaviour
{
#if UNITY_EDITOR

    [ContextMenu("CreateNavmesh")]
    void Create()
    {
        var sources = new List<NavMeshBuildSource>();
        var markups = new List<NavMeshBuildMarkup>();
        var settings = NavMesh.GetSettingsByID(0);
        var bounds = new Bounds(Vector3.zero, 1000.0f * Vector3.one);

        // Navmeshをビルドする対象を収集する
        UnityEditor.AI.NavMeshBuilder.CollectSourcesInStage(
            root: transform,                                // nullの場合Scene全体が含まれます。transformを指定した場合はルートとその子のみを考慮します。
            includedLayerMask: ~0,                          // クェリに含めるレイヤー
            geometry: NavMeshCollectGeometry.RenderMeshes,  // 収集するジオメトリを選択します。レンダラーかコライダー
            defaultArea: 0,                                 // 割り当てるエリアタイプ
            markups: markups,                               // 収集方法についてのマークアップリスト(含めないエリアとか色々)
            stageProxy: gameObject.scene,                   // 所属するシーン
            results: sources);                              // ベイクに使用するジオメトリのリスト(out)

        // 実際にビルドする
        var navmesh = NavMeshBuilder.BuildNavMeshData(
            buildSettings: settings,                    // ベイク処理の設定
            sources: sources,                           // ベイクに使用するジオメトリのリスト
            localBounds: bounds,                        // NavMeshを構築する範囲
            position: transform.position,               // NavMeshの原点
            rotation: transform.rotation);              // NavMeshの向き


        var exportPath = $"Assets/Resources/{gameObject.name}.asset";
        UnityEditor.AssetDatabase.CreateAsset(navmesh, exportPath);
        UnityEditor.AssetDatabase.Refresh();
    }

#endif

}

f:id:tsubaki_t1:20191104000122j:plain
コンテキストメニューを利用

f:id:tsubaki_t1:20191103235908j:plain
NavMeshDataが出力できた

NavMeshをロードする

 最後にNavMeshDataを現在のシーンに追加します。NavMeshの追加自体はNavMesh.AddNavMeshData(NavMeshData)でOKです。この時に取得するハンドルは後でNavMeshDataを破棄する際に使用されるので大切に保存しましょう。

using UnityEngine;
using UnityEngine.AI;

public class LoadNavmesh : MonoBehaviour
{
    [SerializeField] string assetname = "GameObject";

    private NavMeshDataInstance instance;

    void OnEnable()
    {
        // NavMeshの登録
        var data = Resources.Load<NavMeshData>(assetname);
        instance = NavMesh.AddNavMeshData(data);
    }

    void OnDisable()
    {
        // NavMeshの破棄
        NavMesh.RemoveNavMeshData(instance);
    }
}

f:id:tsubaki_t1:20191104000530j:plain
動的にNavMeshを追加できた

補足

 NavMeshの動的なロードを行う場合、NavMeshのロードが完了したかを判断するフラグを必ず設定しておくと良いです。NavMeshDataのロード前にNavMesh関連の処理が走るとエラーになります。