【HoloLens2 MRTKv2.5】NavMeshを使ったキャラクターの移動方法の実装

## HoloLenSでのNavMesh
HoloToolKitでのNavMeshの記事は見かけたことがあるのですが、MRTKv2になってから
ネームスペースなどが大幅に変更されたことに伴って設定箇所が異なる様になっていたので
その備忘録を含めて投稿します。

また本記事はUnityのNavMesh公式リファレンスを参考に進めさせていただきます。
以下のリンクをお読みの上進めていただけると作業が捗ると思われます。
https://docs.unity3d.com/ja/2019.3/Manual/class-NavMeshAgent.html

## 完成例

![ezgif.com-gif-maker (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/d5b205a8-2717-2e7b-3fd4-9b2bca97086d.gif)


今回作成した環境はGitHubにて公開しています。
https://github.com/TakuyaKinoshita/NavMesh

## 作業環境

- windows10 pro
- Unity2019.4.4f1
- VisualStudio2019 version 16.6.0
- windows10 SDK 10.0.18362.0

## 導入手順

予めHoloLensの開発環境がととのっているかの確認をお願いします。
https://hololabinc.github.io/MixedRealityToolkit-Unity/Documentation.ja/GettingStartedWithTheMRTK.html

1. プロジェクトの作成
2. MRTKのインストール(UPM経由)
3. SpatialAwareness(Editor)
4. NavMeshの作成
5. デプロイ(準備中)

## プロジェクトの作成
公式のリファレンスを参考にシンプルなUnityProjectを作成します。
[> -- 公式のリファレンス -- ](https://docs.microsoft.com/ja-jp/windows/mixed-reality/develop/unity/tutorials/mr-learning-base-02)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/8ecff000-ee3e-6268-3ce4-ef3eefcfa9bb.png)

ビルド設定から、TargetPlatformをUWPに変更しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/13112ab6-2f71-02f9-7ad1-9a8c5d9c3a30.png)

上部のバーに下記の画像のようになっていれば問題ないです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/65011d57-4148-1c9f-7e8f-f7a8efd55afb.png)

## MRTKのインストール(UPM経由)

今回は、せっかくUPM経由でMRTKのインポートが利用できるので早速利用意したいと思います。
まずは、Visual StudioやVSCodeなどで、以下のファイルを開いてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/c645cc53-df23-ed1a-9046-456a47791ee6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/9316e653-ecc8-7ca1-d350-1dac4cccdb93.png)

上記のmanifest.jsonを以下のように変更してください。

```manifest.json
{
    "scopedRegistries": [
        {
            "name": "Microsoft Mixed Reality",
            "url": "https://pkgs.dev.azure.com/aipmr/MixedReality-Unity-Packages/_packaging/Unity-packages/npm/registry/",
            "scopes": ["com.microsoft.mixedreality", "com.microsoft.spatialaudio"]
        }
    ],
    "dependencies": {
        "com.microsoft.mixedreality.toolkit.foundation": "2.5.0",
        "com.microsoft.mixedreality.toolkit.tools": "2.5.0",
        "com.microsoft.mixedreality.toolkit.examples": "2.5.0",
        "com.microsoft.mixedreality.toolkit.extensions": "2.5.0",
        "com.microsoft.mixedreality.toolkit.testutilities": "2.5.0",
        "com.unity.collab-proxy": "1.2.16",
        "com.unity.ide.rider": "1.1.4",
        "com.unity.ide.vscode": "1.2.2",
        "com.unity.render-pipelines.lightweight": "7.4.1",
        "com.unity.test-framework": "1.1.14",
        "com.unity.textmeshpro": "2.0.1",
        "com.unity.timeline": "1.2.15",
        "com.unity.ugui": "1.0.0",
        "com.unity.xr.windowsmr.metro": "4.2.3",
        "com.unity.modules.ai": "1.0.0",
        "com.unity.modules.androidjni": "1.0.0",
        "com.unity.modules.animation": "1.0.0",
        "com.unity.modules.assetbundle": "1.0.0",
        "com.unity.modules.audio": "1.0.0",
        "com.unity.modules.cloth": "1.0.0",
        "com.unity.modules.director": "1.0.0",
        "com.unity.modules.imageconversion": "1.0.0",
        "com.unity.modules.imgui": "1.0.0",
        "com.unity.modules.jsonserialize": "1.0.0",
        "com.unity.modules.particlesystem": "1.0.0",
        "com.unity.modules.physics": "1.0.0",
        "com.unity.modules.physics2d": "1.0.0",
        "com.unity.modules.screencapture": "1.0.0",
        "com.unity.modules.terrain": "1.0.0",
        "com.unity.modules.terrainphysics": "1.0.0",
        "com.unity.modules.tilemap": "1.0.0",
        "com.unity.modules.ui": "1.0.0",
        "com.unity.modules.uielements": "1.0.0",
        "com.unity.modules.umbra": "1.0.0",
        "com.unity.modules.unityanalytics": "1.0.0",
        "com.unity.modules.unitywebrequest": "1.0.0",
        "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
        "com.unity.modules.unitywebrequestaudio": "1.0.0",
        "com.unity.modules.unitywebrequesttexture": "1.0.0",
        "com.unity.modules.unitywebrequestwww": "1.0.0",
        "com.unity.modules.vehicles": "1.0.0",
        "com.unity.modules.video": "1.0.0",
        "com.unity.modules.vr": "1.0.0",
        "com.unity.modules.wind": "1.0.0",
        "com.unity.modules.xr": "1.0.0"
    }
}
```
再度Unityの画面に移ると、勝手にインポートしてきてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/67a0dea1-38b5-7571-6f8e-56179518c4c1.png)

インポート完了後、以下のような画面が開きますので、「Apply」を押してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/b0b182d1-4201-5ff7-993d-eec65825c059.png)

ここで、XRの設定を変更しておきます。
Project SettingsからPlayer Settingを開き、XR Settingsの項目を開き、「Windows Mixed Reality」を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/e058081d-4b02-56a4-2f3d-006dfdbc5bef.png)

次に、シーンにMixedRealityToolkitを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/89700f0d-1dbd-b125-9ba0-b758a47ce1ee.png)

ヒエラルキーが以下のようになっていれば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/b073f0f6-819b-ea30-dc24-6be97138987f.png)

## Spatial Awareness (Editor)

今回は、Editor上でもNavMeshの動作を確認したいので、少しProfileを変更します。
まずは、MixedRealityToolkitオブジェクトにアタッチされている、MixedRealityToolkitコンポーネントを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/ab0c482f-9e89-97f3-321e-f636087446e7.png)

最初にProfileを変更可能にするために、「DefaultMixedRealityToolkitConfigurationProfile」をCloneします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/2f223c17-f61b-1a22-a5be-7c2bd322e23f.png)
すると、下記のように編集が可能な状態になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/e1d434b8-47fa-f207-e006-b82084c7e829.png)

次にお目当てのSpatial Awarenessの項目を開いきます。
そして、新しくEditor上でも疑似的に空間マッピングの機能を試すためのオブザーバーと呼ばれるものを追加します。
またここでも、SpatialAwarenessのProfileを変更可能な状態にするために、ProfileをCloneします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/e20605de-bcc1-176a-1c04-ae13070e66f5.png)

次に、新しくオブザーバーを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/79cf4da7-5afa-f7fb-12b1-22fabeedb1e5.png)
Typeの項目で、「SpatialObjectMeshObserve」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/09c4828a-7339-157e-53ca-0e246760e8b4.png)

この状態でシーンを実行すると、シーン上にサンプルのメッシュデータが置かれた状態でスタートします。
![navmesh1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/23362b0e-aab7-5f92-8624-54131bb3b4c0.gif)

## NavMeshの設定

下記のサイトから必要なスクリプトを入手します。

- [LocalNavMeshBuilder](https://raw.githubusercontent.com/Unity-Technologies/NavMeshComponents/master/Assets/Examples/Scripts/LocalNavMeshBuilder.cs)
- [NavMeshSourceTag](https://raw.githubusercontent.com/Unity-Technologies/NavMeshComponents/master/Assets/Examples/Scripts/NavMeshSourceTag.cs)

上記のスクリプトを自分のUnityProjectに入れます。
**<font color="green">※　右クリック -> 「名前を付けてリンク先を保存」で簡単にインストール可能です。</font>**

つぎにインストールした「NavMeshSourceTag」を先ほどのSpatialAwarenessで生成されるメッシュデータにアタッチする処理を記載します。
以下のファイルを作成して下さい。

```CustomSpatialMappinngNavMesh.cs
using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections.Generic;

// MRTK namespace
using Microsoft.MixedReality.Toolkit;
using Microsoft.MixedReality.Toolkit.SpatialAwareness;

public class CustomSpatialMappinngNavMesh : MonoBehaviour, IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject>
{
    /// <summary>
    /// Collection that tracks the IDs and count of updates for each active spatial awareness mesh.
    /// </summary>
    private Dictionary<int, uint> meshUpdateData = new Dictionary<int, uint>();

    /// <summary>
    /// Value indicating whether or not this script has registered for spatial awareness events.
    /// </summary>

    private void OnEnable()
    {
        // Register component to listen for Mesh Observation events, typically done in OnEnable()
        CoreServices.SpatialAwarenessSystem.RegisterHandler<IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject>>(this);
    }

    private void OnDisable()
    {
        // Unregister component from Mesh Observation events, typically done in OnDisable()
        CoreServices.SpatialAwarenessSystem.UnregisterHandler<IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject>>(this);
    }

    public virtual void OnObservationAdded(MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> eventData)
    {
        // A new mesh has been added.
        if (!meshUpdateData.ContainsKey(eventData.Id))
        {
            meshUpdateData.Add(eventData.Id, 0);
            eventData.SpatialObject.GameObject.AddComponent<NavMeshSourceTag>();
        }
    }

    public virtual void OnObservationUpdated(MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> eventData)
    {
        uint updateCount = 0;

        // A mesh has been updated. Find it and increment the update count.
        if (meshUpdateData.TryGetValue(eventData.Id, out updateCount))
        {
            // Set the new update count.
            meshUpdateData[eventData.Id] = ++updateCount;

            // Debug.Log($"Mesh {eventData.Id} has been updated {updateCount} times.");
            eventData.SpatialObject.GameObject.AddComponent<NavMeshSourceTag>();
        }
    }

    public virtual void OnObservationRemoved(MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> eventData)
    {
        // A mesh has been removed. We no longer need to track the count of updates.
        if (meshUpdateData.ContainsKey(eventData.Id))
        {
            // Debug.Log($"No longer tracking mesh {eventData.Id}.");
            meshUpdateData.Remove(eventData.Id);
        }
    }
}
```

上記のコードで、MixedRealityToolkitが保持するオブザーバーからメッシュ生成時などのInterfaceにイベントを追加し「navMeshSourceTag」を追加しています。

次にインスペクターに、「NavMeshManager」を追加して「LocalNavMeshBuilder」、「CustomSpatialMappinngNavMesh」コンポーネントをアタッチしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/280ac08b-6de0-c96d-3ecd-80cb7c9648b1.png)



ここで一度Unity側で実行してみると、PlaySpaceオブジェクト以下にある、Meshデータに「NavmeshSourceTag」コンポーネントがアタッチされていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/9eff0625-ac5c-3bcd-3b02-dd13d648f767.png)

ここまでくれば後は、NavMeshの設定をするだけです。
今回は、「Sphere」と「Capsule」を使って、「sphere」を追従する「Capsule」を作りたいと思います。
まずはシーンにCapsuleとSphereを作成します。

- Sphere設定（追跡される側）

sizeの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/0ba70ded-2845-2384-0155-746fcd11d58e.png)


自由に移動位置を指定したいので、今回は「ObjectManipulator」コンポーネントを使って自由に移動できるようにさせます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/7ba5a304-a02e-c5f0-b5cc-49fae0c1b7db.png)

On Manipulation Endedイベント時に、NavMeshAgent側に目的地を伝えるスクリプトを後に用意します。

- Capsule設定（追跡する側）

sizeの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/73905b56-ab3e-3467-fde6-0514e2d87eaf.png)
※ yの値に「-0.5」を適用しているのは、NavMeshの生成エリアから離れすぎているとNavMeshがうまく起動しないので、できるだけ床に近いところで待機させる必要があるからです。

NavMeshAgentの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/a93da29d-367b-4ed8-2749-5429df5c84b4.png)
NavMeshの半径はできるだけ小さくしないとエリアが狭くなるのでお気を付けください。生成されないなどの時はここが原因の場合があります。

window -> AI -> Navgationからエリアの設定を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/f437183f-0a20-18d5-3402-2ad740356d0d.png)

下記のスクリプトを作成しアタッチしてください。

```TargetTracking.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class TargetTracking : MonoBehaviour
{
    public NavMeshAgent agent;
    public GameObject target;
    // Start is called before the first frame update
    void Start()
    {
        agent = transform.GetComponent<NavMeshAgent>();
    }

    public void SetDestination()
    {
        var endPoint = new Vector3(target.transform.position.x, transform.position.y, target.transform.position.z);
        agent.destination = endPoint;
    }
}
```

ここで注意していただきたいのが、新しくDestinationを設定するときに、y座標に自身の座標を適用するということです。先ほども伝えた通り、NavMeshはあまりにも離れた位置には移動することができません。ですのでなるべく移動オブジェクトの近くを設定させないとうまく動いてくれないです。
「Sphere」オブジェクトに追加したManipulationのイベントに先ほど作成したTargetTracking.csの「SetDestination」メソッドを登録してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/6f000aee-8e9a-709a-5adc-9c7d8d125cbf.png)


試しにy座標を0で指定したデータで行った場合以下のようにまったく動いてくれません。
![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/5adf0aca-e202-e349-a94c-d570f9730338.gif)

成功例
![ezgif.com-gif-maker (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/19ae877c-ec53-5156-0916-99c77648bd5f.gif)

## デプロイ

**準備中です**

## 最後に

HoloLens2でももちろんNavMeshは使えますので、これを使ってさらにより良いコンテンツが生まれてくれることを切に願います。
また、この記事が同じHoloLens開発者の方のために少しでも役に立てればと思っております。
文中にお菓子な個所などございましたら、コメントいただけるとすぐに修正・加筆させていただきます。
今回はここまでとさせていただきます。ありがとうございました。

## 参考URL
https://simplestar-tech.hatenablog.com/entry/2019/01/05/193136

https://docs.unity3d.com/ja/2019.3/Manual/class-OffMeshLink.html
![ezgif.com-gif-maker (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363822/a49b6235-3458-f677-3880-9b7062aba1b8.gif)