tanaka's Programming Memo

プログラミングについてのメモ。

NavMeshAgentでよい感じにキャラクターを歩かせる

qiita.com

Unityゆるふわサマーアドベントカレンダー 2018の21日目の記事です!

前日は @tsukumaru さんの「Loom Unity SDKのサンプルを動かしてみる」でした。

次は @Nitudon さんの「ShaderGraphを使って頂点シェーダーで遊ぶ」の予定です。


NavMesh(ナビメッシュ)は、障害物を回避して、目的地に到着するルートを探索してくれる素晴らしい機能です。NavMeshAgent(ナビメッシュエージェント)コンポーネントを使えば簡単に作成したルートに沿って移動させるキャラクターが作れます。他のエージェントとの衝突や、動く障害物への対応もできます。しかし、そのまま使うとキャラクターの動き方がイマイチ。ということで、歩き方を変えるスクリプトを公開したのでご紹介します。

  • 2018/8/20 他のエージェントを回避しないことについて追記しました

目次

目的

NavMeshAgentをデフォルトのまま利用した例です。

f:id:am1tanaka:20180807162946g:plain

キャラクターが振り返る時に、ぬる~っと方向が変わって不自然です。また移動が加速方式なので、正反対に目的地を変更すると、ぬるぬるっとスピードが落ちてから移動方向が反転します。歩いている感じがしません。そこで以下のように調整しました。

  • 目指す方向が一定の角度以上の場合は、その場でくるっと方向転換してから歩き始めるようにする
  • 一定の角度以内の場合は、歩きながら一定の角速度以内で旋回
  • キャラクターは向いている方向に定速で歩く

動きの比較

比較のため、改良版とNavMeshAgent版を並べてみました。

改良版 NavMeshAgent版
改良版 NavMeshAgent版

改良版は少し動きが硬いですが、歩いている感じはばっちりだと思います。

こちらにライブデモを用意しました。マウスで指している辺りにユニティちゃんが歩いていきます。

利用方法

こちらに、組み込み用パッケージとサンプルのリポジトリーを公開しました。私が作成したものはMIT License、SDユニティちゃんはユニティちゃんライセンスです。

どのように実装したか

この記事ではどのように実装したかをご紹介します。

方針

移動ルートはNavMeshAgentSetDestinationで目的地を設定すれば自動的に探索してくれます。ルートが完成したら、曲がる座標が配列で得られるので、CharacterControllerを使って自前で動かします。以下のように、NavMeshAgentの移動関連のパラメータはすべて0にして、NavMeshAgentが移動に関与しないようにしました。

f:id:am1tanaka:20180819235830p:plain

ルートの生成

NavMeshが持っているCalculatePathメソッドでもルートを求めることはできるのですが、ブロック関数なので探索中は画面が固まる可能性があることが分かりました(AI.NavMesh.CalculatePath - Unity スクリプトリファレンス)。非同期に経路を探索するにはNavMeshAgentSetDestinationを使えということだったので、NavMeshAgentを利用することにしました。

NavMeshAgentでルートを探索するには、SetDestination()で目的地座標を設定します。

            // agentにNavMeshAgentのインスタンスが設定されていて、
            // posに目的地が入っているとします
            agent.SetDestination(pos);

設定直後はルートは出来ていません。pathPendingプロパティがtrueの間はルート探索中なので、ルートは取得できないので、キャラクターは静止しておきます。

ルートの利用

pathPendingfalseになったらキャラクターを動かします。ルートはVector3型の配列agent.path.corners[]で得ることができます。この配列の0番に次に曲がる座標、1番に次に曲がる座標が入っています。キャラクターを移動させると、通過した座標は自動的に削除されます。

鋭角に曲がる場所では、ほとんど同じ座標にコーナーが続く場合があります。常にagent.path.corners[0]だけを目的地として動かすと、カクカクした動きになったので、1回分の移動距離を越える座標までスキップする処理を加えました。

微調整

agent.path.corners[]で得られる座標をそのまま目指してしまうと、キャラクターがロボみたいなカクカクした動きになってしまいます。通過点を滑らかに繋ぐルートをベジェ曲線などで算出する方法もありますが、今回はシンプルに旋回+前進させることでカクカク軽減を狙いました。

目的地の方向をmokutekiとすると、以下でキャラクターが向いている方向と、目指したい方向の角度が求まります。

float angle = Vector3.SignedAngle(transform.forward, mokuteki, Vector3.up);

この値を元にして、角度がキツければその場を旋回、大丈夫なら規定の角速度でキャラクターを旋回させて前進させました。これにより、目的方向に何フレームかかけて向き直りつつ移動するようになるので滑らかな曲がり方になります。

注意点

以下には非対応です。

他のエージェントの回避(8/20追記)

本来、NavMeshAgentは他のエージェントを避ける動きをしてくれますが、今回の改良版はパスを辿るだけなので他エージェントを避けてくれません。回避させたい場合は、nextPositionの方向も移動に加味する必要があります。この辺りはいずれ対応させたいとは思っています(やるとは言っていない)。

ジャンプや空中

NavMeshAgentは宙に浮くことが考慮されておらず、常に強制着地します。改良版もその点は考慮していないのでジャンプや落下はできません。それらが必要な場合は、ご存知テラシュールブログさんのこちらの記事が参考になると思います。

tsubakit1.hateblo.jp

状況に応じて、NavMeshAgentRigidbodyを切り替える感じです。

立体交差

改良版の到着判定が高さの違いを無視するため、立体交差があると高さが異なる場所でも目的地に到達したと判定する可能性があります。そのようなマップの場合は必要に応じて改造してください。

NavMeshAgentを動かさないために試したこと

参考のためのメモです。

NavMeshAgentを動かさないために、他のブログではStopメソッドを使う例が紹介されていました。が、現在はStopメソッドは廃止されているようだったので使いませんでした。

また、updatePositionupdateRotationというプロパティがあります。これらをfalseにするとTransformは動かなくなります。が、NavMeshAgentは移動してしまいます。Unityの公式マニュアルなどでは、このフラグを利用して、キャラクターの動きやアニメをNavMeshAgentの座標に合わせてカスタマイズできる方法が紹介されています。

docs.unity3d.com

これだとNavMeshAgentの独特の動き方が補正できないとか、ルートデータの管理がどうなるか不明だったとかあったので、問題をシンプルにするためにStealingパラメーターを全て0にしてNavMeshAgent自体を自動では動かないようにしました。

まとめ

NavMeshAgentはとても便利なのですが、動き方がイメージ通りではなかったので改良してみました。NavMeshAgent自身の動きを停止させる機能か、NavMeshのルート算出が非同期で求められるともっと手軽なのですが、やりたいことはできました。

これを応用すると、移動先を指定するプレイヤーや、プレイヤーを追いつつ多彩な動きをする敵の動きを作ることができるのではないかと思います。ご参考になれば幸いです。

ブログ用アセット

デモ動画のプレイヤーと地面はSURIYUNさんのFarmer Girl SD。動物たちは同じくSURIYUNさんのCute Petを利用しました。

障害物はProBuilderで作成しました。

キャラクターのモーションはSDユニティちゃんのものを利用しています。

ユニティちゃんライセンス

この動画のモーションはユニティちゃんライセンス条項の元に提供されています

参考・関連URL