simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Unity:動的NavMeshの確認

ゲームの3大AI(人工知能)の一つはナビゲーションAI

ゲームAIを大きく分類すると次の三つがあることに気づけます

  • キャラクターAI
    • キャラの振る舞いを決定
  • ナビゲーションAI
    • 目的地への経路を決定
  • メタAI
    • ゲームの進行を決定

今回強く関係しているのはナビゲーションAIです。

キャラクターが行動できる範囲を Walkable (歩行可能) エリアと呼びますが、次の動画の水色の半透明な領域が計算によって求めた Walkable エリアです。

f:id:simplestar_tech:20190105172931g:plain
動的NavMeshの動作の様子
f:id:simplestar_tech:20190105173305g:plain
動的NavMeshの動作の様子2

ゲームプレイ中に動的に Walkable エリアが更新されていることが確認できました!
これ本当にすごいことを目の当たりにしている瞬間でして、長いこと Unity では静的にしか Walkable エリアを計算できませんでした。
要するに、ゲーム実行前のビルドの段階で Walkable エリアを作り込むことしか選択肢がなかったのです。

計算が非常に複雑な経路計算がゲーム実行中にできる
それは単にナビゲーションが動的に作成した地形に適用できるという話では収まりません
あらゆる知的計算は経路探索に置き換えることができます。
例えば、今日この記事を書いている私の頭の中は、様々な行動プランを選択できる状態の中、この記事を書き始めるべきだと経路を作って、その経路に従って行動しています。
つまり、キャラクターAIのプランニング機能を動的 NavMesh 生成処理で実現できます!
(そんな気づきを得て興奮できる人がいるかは別として…)

Unity での具体的な実装方法

まずは次の Unity マニュアルを最後まで読む
docs.unity3d.com

実装手順だけ簡単に示します。
ドキュメントに書いてある通りこの動的 NavMesh 機能は標準の Unity エディターインストーラーには 含まれていません

次の GitHub のページを Git Clone して、Assets 以下の NavMeshComponents フォルダをあなたの Unity プロジェクトの Assets 以下へ配置します。
github.com

NavMeshSurface コンポーネントが利用可能になるので、シーン内の空オブジェクトに次のように追加します。

f:id:simplestar_tech:20190105191205j:plain
NavMeshSurface コンポーネントを追加した GameObject のインスペクタ

もう一つ、ユーザースクリプトがゲームオブジェクトに割当たっていますが、こちらの実装は定期的に Bake ボタンを押すというものです。
以下の実装になっています。

NavMeshSurfaceBaker.cs

using System.Collections;
using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshSurface))]
public class NavMeshSurfaceBaker : MonoBehaviour
{
    void Start()
    {
        _surface = GetComponent<NavMeshSurface>();
        StartCoroutine(TimeUpdate());
    }

    IEnumerator TimeUpdate()
    {
        while (true)
        {
            _surface.BuildNavMesh();

            yield return new WaitForSeconds(5.0f);
        }
    }

    NavMeshSurface _surface;
}

実装の意味は、単に 5秒ごとに NavMesh を焼き直すというもの

計算に利用するCollier メッシュオブジェクトの集め方はいくつか用意されていて
All ですべてのオブジェクト、Volume で指定した範囲内のオブジェクトだけ、Child で Transform の子オブジェクトだけ、を使って焼き直しが走ります。
レイヤーマスクも指定できるので、なるべく簡素なメッシュを用意してそれに割り当てたレイヤーを利用すると 5秒に一回の計算量のスパイクを低くできます。

エージェントの操作

動的 NavMesh が作れても、経路計算の方法がわからなければ、宝の持ち腐れですね。
NavMeshAgent クラスを活用して経路計算を行ないますが、そちらの知識を確認したい場合はこちらの記事が分かりやすいのでどうぞ
nopitech.com

エージェントが宙に浮いちゃった!どうすれば良い?

f:id:simplestar_tech:20190105192908j:plain
NavMeshSurface を利用すると中空を歩いてしまう…

私の対処としては AgentBaseHeightCorrector.cs を次の実装で作り、キャラクターオブジェクトに適用しました。

using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
public class AgentBaseHeightCorrector : MonoBehaviour
{
    void Start ()
    {
        _nav = GetComponent<NavMeshAgent>();
    }

    void Update()
    {
        CorrectBaseHeight();
    }

    private void CorrectBaseHeight()
    {
        NavMeshHit navhit;
        if (NavMesh.SamplePosition(transform.position, out navhit, 10f, NavMesh.AllAreas))
        {
            Ray r = new Ray(navhit.position, Vector3.down);
            RaycastHit hit;
            if (Physics.Raycast(r, out hit, 10f, LayerMask.GetMask("Level")))
            {
                _nav.baseOffset = -hit.distance;
            }
        }
    }

    NavMeshAgent _nav;
}

※実装ヒントは次の討論からもらいました。
https://forum.unity.com/threads/navmesh-surface-bakes-slightly-above-the-surface.508532/


毎フレーム処理が CPU リソース的に勿体ないなら、上の TimeUpdate の妙技を使ってみるのもありだと思います。

おわりに

うわぁ、ここまで書いてから類似するテラシュールブログさんの記事の存在に気付く…
ていうか自分スター押してる…忘れていたが正解でした!

tsubakit1.hateblo.jp

ただ、読み直した感じ、いくつか現在の実装とは異なる部分が見られたので
本記事は新しい NavMeshComponents の動作確認を行ったという意味があったかも