Unityでざっくり人流シミュレーション[NavMeshAgentの利用]その1 ~シーンのベース構築とNavMeshAgentの導入編~


はじめに

 本記事では、BIMモデルを利用してUnity上で簡単な人流シミュレーションを行うまでの流れをまとめたシリーズです。
 建物の設計業務を行う際の、部屋の配置や動線計画などのラフな判断材料としての利用、あるいはゲーム開発を行う際の手助けになれば幸いです。
 なお、「簡単な」とあるように、学術的に提唱されている手法や懸案事項については触れていないため、精度云々についてはご指摘いただいても対応しかねます。

実行環境と利用するモデル

利用したツールとバージョン

ツール名 バージョン 用途
Autodesk Revit 2020 建物モデルの作成
Unity 2019.3.10f1 UI作成やエージェント操作

建物のモデル

今回は計画検討段階レベルの小学校の建物モデルを利用します。
「シミュレーションができればいい」レベルで作成したためかなり簡素なものです。

Unity Sceneのベース構築

 ここからは、シミュレーションで動かす人とその人が動き回る建物をUnity上に構築していきます。

BIMモデルの書き出し

まずは作成したBIMモデルをUnityにロードします。
3次元形状をそのまま書き出しても構いませんが、今回想定しているシミュレーションツールのUIは平面表示がベースとなっているため、各フロアごとでの書き出しを行います。

Revit上で3Dビューを表示し、「ビューキューブ下の▽」>「ビューで方向指定」>「平面図」で書き出したいフロアを選択します(下図は1FLを指定)。
* Unityに渡したくないモデルが表示されている場合は、この時点で非表示にしておきましょう。

UnityでRevitのモデルを利用する場合はFBXモデルを使います。
「ファイル」タブ>「書き出し」>「FBX」を選択し、任意のファイル名、保存場所に保存してください。

BIMモデルの読み込み

 次に、先ほど書き出したBIMモデル(fbx形式)をUnityに読み込みます。
 UnityのAssetsフォルダの構成は以下です(それぞれの管理方法があるかと思いますので、参考程度に)。
 フォルダ内のModesフォルダに書き出したfbxファイルを格納し(Revitのファイルは任意)、「Hierarchy」にも追加してシーン上に配置しましょう。
 モデルの位置については利用するモデルによっても異なるので、現状は重ならないように配置していれば問題ありません。

図面を利用した視認性向上

図面の書き出し

 読み込んだFBXモデルは、マテリアルの情報が抜けているため非常に視認性に乏しい状態となっています。
 通常であればここからマテリアルの割り当て等を行いますが、今回はBIMで作成した平面図(カラースキーム)を利用して手軽に視認性を向上させていきます。
 Revitでのカラースキームの設定方法についてはここでは割愛しますが、図面が作成できたら「ファイル」タブ>「書き出し」>「イメージおよびアニメーション」>「イメージ」を選択し、画像として図面を出力します(この際画像サイズは大きめにして出力してください)。

モデルと図面の画像を重ね合わせる

 書き出した画像ファイルをAssetsに格納します。
 Unityで画像を表示するには、UIとして表示する方法と、直接画像をシーンに配置する方法の2つがありますが、今回はシーン上に直接画像を配置する方法で表示していきます。
 書き出した画像はそのままではシーン上に配置できないため、Assets内の画像の設定を変更します。
 画像を選択したら、インスペクター上の「Texture Type」を「Default」から「Sprite(2D and UI)」に変更します。

 変更後、Sceneウィンドウの適当な位置をクリックすると、以下のようなメッセージが表示されるため、「Apply」を選択して下さい。

 ここまでができたら画像をシーンに配置し、インスペクター上でサイズ等の設定を行って下さい。(読み込み初期はXY平面に画像が正対しているため、X軸の回転を90にしてください。)。

複数フロアを対象にする場合は、同様に調整を行って下さい。

エージェントモデルの作成

 ここまでで人流シミュレーションを行う建物が配置できたので、ここからは建物場を動く人物モデル(エージェント)を作成していきます。
 最終的な成果物を考慮すると、相当量のエージェントが行き来するためできるだけ簡素なモデルを作成していきます。

簡単な形状を作成

 今回は平面上でのシミュレーションを行いますので、上から見て丸いアイコンが動けば十分かと思います。
 シーン上にSylinderを作成し、名前や形状の設定を行います(小学生くらいの大きさを想定してサイズは調整しています。)

NavMeshAgentコンポーネントを追加

 作成したSylinderにNavMeshAgentコンポーネントを追加します。インスペクターの最下部にある「Add Component」をクリックし、検索窓に「nav」と入れると候補にNavMeshAgentが提示されますので、選択して下さい。

 色々と設定できるようですが、今回は特に必要ありません。

エージェントを動かしてみる

 それでは作成したエージェントを動かす準備をしていきましょう。

目的地の作成

 まずはエージェントが向かう目的地となるアノテーションを作成します。
 シーン上にSphereを作成し、名前を目的地の名前(今回は教室名)に変更します。
 またこのオブジェクトは目立っても仕方がないので、小さくしておきましょう。

移動経路の設定

 次に、移動経路を設定していきます。
 NavMeshAgentは、移動可能な領域上で目的地までの最短ルートを移動します。
 シーン上のどこが移動可能で、どこが移動不可能なのかを設定することで、あとのルート計算はNavMeshAgentが自動で行ってくれます。

Navigationウィンドウの表示

 移動経路の設定は、Navigationウィンドウで行います。
 「window」タブ>「AI」>「Navigation」を選択すると、Navigationウィンドウが表示されます。

 フローティングウィンドウのままでは邪魔になりがちなので、どこかお好みの場所にフィックスしておきましょう。私はインスペクターの隣に配置しました。
 Navigationウィンドウの中にはさらに4つのタブが用意されています。ここでは全ての説明は省き、「Bake」と「Object」の2項目について解説していきます。

Objectタブ

 まず先にObjectタブについて説明します。エージェントに移動させたい領域(基本的には床)を選択してください。
 すると、Objectタブ上でも床のナビゲーション要素(ここではエージェントの移動に影響する要素として定義します)に関する設定情報が表示されます。
 デフォルトではナビゲーションに関する設定は何もされていない状態ですので、「Navigation Static」にチェックを入れ、「Navigation Area」が「Walkable」になっていることを確認してください。
 これで、床が歩行可能なナビゲーション要素として設定されました。

Bakeタブ

 Bakeタブでは、Objectタブで設定したナビゲーション要素同士の位置関係などから歩行可能な領域を計算し、シーン上に歩行可能エリアを定義します。
 ここではまだ「Walkable」しか設定していないのでイメージをつかみづらいかもしれませんが、「Not Walkable」などを指定すると、そのオブジェクト上(またはその周辺)が歩行できなくなります。大量のオブジェクトにナビゲーション設定を行う場合、都度演算をしていると作業が進まないので、「一通り設定してから最後にシーンに設定を適用する」といった理解で十分かと思います。

 少々前置きが長くなりましたが、Bakeタブで行う操作は「Bake」ボタンを押すだけです。
 処理が終わると、対象箇所が水色っぽくなっているのが分かるかと思います。この水路の部分が「NavMesh」であり、エージェントはこの上を歩くことができます。

NavMeshAgentを動かすスクリプトの作成

 詳細な設定は次回で行っていくとして、今回はまずエージェントを動かすところまでやってみましょう。
 ここからは、C#で記述していきます。

スクリプトの作成

 まずはC#スクリプトを作成しましょう(名前は任意で構いません、AgentController.csなど)。
 作成したスクリプトをエージェントに割り当てましょう。

目的地を指定

 以下コードを記述して、エージェントに目的地を教えてあげましょう。

AgentController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class AgentController : MonoBehaviour
{
    NavMeshAgent agent;  // NavMeshAgentコンポーネントを格納
    GameObject destination;  // 目的地のSphereを格納

    void Start()
    {
        // NavMeshAgentコンポーネントと目的地のオブジェクトを取得
        agent = GetComponent<NavMeshAgent>();
        destination = GameObject.Find("1-1");  // 目的地に設定した部屋名を指定

        if (agent.pathStatus != NavMeshPathStatus.PathInvalid)
        {
            // 目的地を指定(目的地のオブジェクトの位置情報を渡す)
            agent.SetDestination(destination.transform.position);
        }
    }

}

NavMeshAgentを利用する場合は、UnityEngine.AIを読み込む必要があります。
ここでは、エージェント(自身)の持つNavMeshAgentを取得し、NavMeshAgentが持つSetDestinationメソッドに、目的地となるオブジェクト(「1-1」と名前を付けたSphere)の位置情報を渡しています。

実行

それでは実際に動かしてみましょう。
以下のようにエージェントをNavMesh上のどこかに配置し、実行ボタンを押しましょう。

無事動いていますね。
ここではまだ壁をすり抜けて最短距離で行ってしまっています。
今回は記事が長くなってしまうのでここまでにしますが、次回は壁がある場所は歩けないように設定し、エージェントごとに向かう目的地(教室)が変わるように調整していこうと思います。

最後までご閲覧いただきありがとうございました。
次回もお楽しみに。