A*の情報が少ないので自分で書く
- BTA(Kelorin Jo)
- 2021年5月10日
- 読了時間: 8分
更新日:2021年6月4日
長く苦しめられてきたパフォーマンスの問題は、結局前回の記事の締めの通り、
ステージ中のオブジェクトをエリアごとに分けて、プレイヤーにだけ反応するトリガーコライダーを配置、プレイヤーがエリアに差し掛かったらエリアごと表示を切り替える。
こうすることで一度に処理するオブジェクト数が劇的に減って、安定して30FPでるようになりました。おそらくこれ以上のソリューションはないように思えますが。
プレイヤーはそれで良くても、動いてるAIにとっては歩いてる地形が消えたら動けなくなります。
NPCの移動のために、セールで半額だったときにA*(有料版)を導入してみました。
A*と書いてアスターと読む。
地形がStaticでなくて動いてても、リアルタイムでナビメッシュが作れるとかいうんで。
次回作の売りとして、建物エディットを実装して、NPCがナビメッシュ更新して入ってこれたらいいなと考えたからですが。
なぜか動的ナビメッシュに関する情報が全く見つからず、AIのアルゴリズムの記事ばっかし。
●どうやって使うの?
公式サイトとマニュアルがこちらになります…
・シーンへの入れ方
新しいGameObjectを作成し、「A* 」とでも名前をつけます
メニューバー–>コンポーネント–>PathFinding>PathFinder もしくはコンポーネント直打ちPathFinder で、PathFinderコンポーネントを追加します

Graphsを押すと、いくつかの種類の「グラフ」を選んで実装します。
Unity標準のナビメッシュにあたるものは、A*では「グラフ」と称してまして、
シーンの地形をモザイクめいたマス目に近似した「グリッドグラフ」と、ポリゴン板をナビメッシュとして使う「リキャストグラフ」などを作れます。
・グリッドグラフ

シーンを大まかなマス目に切ってモザイク状のナビメッシュを作ります。
なので、カーブした道路、細い道などの細かい地形に対応しきれません。細かくすれば対応できるけど当然ポリ数が増えて計算が遅くなるでしょう。
AIキャラの周辺だけグラフを更新できます。当然その設定だと更新が早く動的ナビメッシュが可能になります。
立体交差に対応できます。
ただ、AIが調整不足だとかなりの確率で地形にはまるデメリットがあります。
・リキャストグラフ

「Navmeshグラフ」ではナビメッシュを自作=モデリングして持ち込めます。
当然地形にピッタリフィットさせる必要があって、動的ナビと程遠い仕様で、箱庭マップ用を手動で作れとか無茶言うなとツッコミどころ多数ですが、
有料版では上位互換の「リキャストグラフで」、自動生成できます。
自動生成したNavmeshグラフがリキャストグラフと称してます。
自動生成は平坦なところではローポリに、凸凹したところでは微細にするので、細かい地形に対処できます。リキャストグラフが一番普通のナビメッシュに近い見た目です。しかし動的ナビはほぼ無理です。
このグラフはAIエージェントがジグザクに動いてしまいます。が、そのジグザグ移動がいい感じに障害物回避に役立ってて、一番引っかからず安定した挙動をします。有料版では対策したAIエージェントが使えます。
立体交差に対応できないかも。
・ポイントグラフ
クラシックなグラフで、手動で植えたウェイポイント(とタグとレイヤー)だけで生成します。
※画像はウェイポイントに自作のギズモをつけて視認しやすくしてあります

一番単純で、手間で制約も多い代わりに堅実そうな、そうでもないような。
障害物を自動で避けるのが不可能なほどクソ狭い入り組んだ地形で、絶対ルートから外れてほしくないって状況だったら、使えるかも。
これらのグラフは、Scanを押すとナビメッシュが可視化されますが、範囲が小さかったり粗かったら、スキャン範囲や解像度、レイヤーマスクやタグで障害物や地面を検知して避けたり設定し直して再スキャンします。
これらをレベルデザインによって使い分けます。(キャラごとに個別に持てる+複数導入できるようですが、認識するのは一つだけのようです)
●AIの歩かせ方
キャラコンを有したモデルに独自のAI…AI Pathコンポーネントをアタッチすると、AstarAIという表記になります。自動でSeekerコンポーネントもアタッチされます。

さらに、キャラクターコントローラとAIDestinationSetterをアタッチ、

シーン上に動かせるオブジェクトをTargetに割り当てて動かすと、AI Pathはキャラクターコントローラを自動で制御して、キャラはターゲットの位置まで動いてくれます。
ただし歩くモーションに関しては、自分でスピードを取得してアニメーションに反映させるスクリプトを作ってアニメーターコントローラーを制御しないといけないですが。
サンプルコードはいっぱいあるので、参考にして頑張って作りましょう。
通常のナビメッシュエージェントと比べて、回転と移動のバランスが自然な挙動になってる感じがしました。
追記:有料版ではリキャストグラフ専用のRich AIコンポーネントがAI Pathの上位互換に使えます。使い方は全く同じで、移動がなめらかになります。
●ナビメッシュのウェイトつけ

ナビメッシュにウェイトを与える方法がデフォのそれと大きく異なり、
GUO…GraphUpdateSceneというコンポーネントに、なんとVector3でワールド座標を配列に直接打ち込んで閉じたエリアを作るという。えっ面倒。微妙に視認性低いし

でもメッシュのプレハブで(車道と歩道とか)切り分けないとウェイトがつけられないよりはまし?
GUOに独自タグをつけ(オブジェクトのそれじゃなくてA*独自の)、

AIのコンポーネントひとつSeekerに、タグとウェイトと通行禁止を設定する項目があるから、そこで設定すると囲った範囲をあるきづらくしたり通行止めとかにできます。またこれも動的に実行中に移動できるようです。
●Navmeshカット

シーン中のオブジェクトにナビメッシュカットNavMeshCutのコンポーネントをつけると、グラフのメッシュをくり抜いて、その範囲を通行不可にします。
グラフの壁認識が甘かったら、これで補えるでしょう。
実行中にこのオブジェクトが移動すると、そのたびナビメッシュをリセットしてくり抜き直して、動的ナビメッシュを実現します。形状は矩形、円、カスタムメッシュが使えます。
似た機能にDynamicGridObstacleがあります。障害物のメッシュにつけるだけでナビメッシュをくり抜きますが、くり抜きはメッシュの形に依存して、挙動が微妙に違います。
リキャストグラフ自体はリアルタイム更新はほぼできないけど、これで部分的に動的ナビが実現できるようです。
●グラフのキャッシュ
グラフは実行時に生成してたら起動が遅くなるので、エディタ上で生成したグラフを事前にファイル保存して、実行時に呼び出して使うことができます。
●使えそうなサンプルシーン
サンプルシーンのExample12_ProceduralとExample13_Movingが興味深いです。
・Example12_Procedural
自動生成するマップの中をキャラの周辺にだけナビ(グリッドグラフ)を作りながら障害物避けるルートで移動

上から見下ろすメインカメラに、グリッドグラフを更新するコンポーネントProceduralGridMoverが取り付けてあり、AIの位置が一定移動量超えるごとにグリッドグラフを更新します。
逆に言えば、グリッドグラフの更新はAIPathのインスペクタ上から直接設定はできません。
(カメラには他にも、ワールド自動生成+削除やカメラスムーズ移動などのコンポーネントもつけてあります)
・Example13_Moving
パスで移動してる船の甲板上で障害物を避けて移動(リキャストグラフ)

ローカル座標にナビメッシュを反映するLocalSpaceGraphと、それに対応するLocalSpaceRichAIが使われています。特に設定項目は見当たりません。船は曲線移動スクリプトが使われて、それで動いています。特にこれといった設定項目はないです。
●渋滞回避機能
キャラの渋滞回避ができる、立体交差対応にできる(有料版のみ)などのスクリプトがあります。
●動かない場合
・ポイントグラフとグリッドグラフはよく引っかかります。
なんか薄い壁(グリッド1マス分とか)は認識できず通り抜けようとはまる時があるような。
またAIエージェントの当たり判定と実際のコライダーは別系統で、コライダーが障害物や段差に引っかかって通れないことがあります。
グリッドグラフの場合はErosion Iterationsを大きく設定すると障害物の周りに通行禁止分を増やして、引っかかりを軽減できるようですが、それでも引っかかる。

リキャストグラフはなかなか引っかかりにくいです。
・ナビメッシュがあってもそれに沿った地形がないと、移動しません
で、冒頭のエリアカリングの話がここで問題になります。
AIを重力無効にしたら動けますが、当然空中を動きます
AIが動けてないという判定があれば…
・ナビメッシュ有効時はナビメッシュの外に移動できません。動かしても瞬時に戻されます
・(追記)変数bool canMoveで、A*を一時停止できます。AIが攻撃で吹っ飛ばされたとか、一瞬だけジャンプとかさせたい場合に使います。ただナビメッシュの外にリングアウトしたらもう動けないけど
(途切れたナビメッシュを飛び移るジャンプスクリプトもあるけど説明割愛)
ただし、この変数はパブリックで直接操作できません。コード書かないと操作できません。
IAstarAI.csがアクセス先です。
インスペクタ上にこの名前は出ないのがハマりポイント…
AIPathのあるオブジェクトにあるコンポーネントのどれかに
Pathfinding.IAstarAI ai;
void OnEnable()
{
ai = GetComponent<Pathfinding.IAstarAI>();
if (ai != null) ai.onSearchPath += Update;
}
public void CanMoveAstar(bool a)
{ ai.canMove = a;}
みたいな感じでコードとメソッドを追加して、やっとcanMoveが操作できます。
・(追記)A*はCharacterColtrollerがなくてもキャラを動かせますが、あっても同様に動作します。
ただし、内部的には操作が変わってます。
Unityでは、CharacterColtrollerがあるときは、CharacterColtrollerを操作しないと移動できないようになります。トランスフォームを変えても動けません。
(気付くのに数日かかった)
なので、A*に移動を完全依存するなら気にしなくてもいいですが、CanMoveでA*を一時停止してキャラを手動で移動させたい場合は、トランスフォームの操作でなくCharacterColtrollerを操作するようにコードを書き換えるか、条件分けが必要になります。
・グラフ生成時にタグやレイヤーで障害物の周りを移動不可に設定するのですが、低解像度に近似してから計算してるせいで想像以上に広く通行止めにされたり、逆に細かい障害物を無視されたりで、入り組んだ場所が問題になります
。教室の机の間とかまず通り抜けられません。(ナビが通れてもキャラコンと障害物のコライダーがぶつかったら進めません)
設定で軽減するのもまた面倒だったりします。
そんなときのために、AIPathにはワープメソッドTeleport (newPosition, clearPath=true)があるので、ターゲットに到達してないのに動いてない時間が数秒続いたら瞬間移動させる、とかやれたら対策できるかも。
・グラフは複数実装できますが、複数のグラフを切り替えたり別のグラフをまたいだ移動はできません。なにか方法がありそうなんだけどわかりません。
Comments