i-school - 【3D】NavMeshAgent によるベーシックな追跡機能の実装例
 3D(FPS) を想定した、NavMeshAgent によるベーシックな追跡機能の実装例です。

 NavMeshAgentコンポーネントを使用することで、NavMesh上での効率的な移動が実現され、リアルタイムでの対象の追跡が可能になります。



3D用 サンプルコード


 NavMeshAgent コンポーネントがアタッチされているゲームオブジェクトに、このスクリプトをアタッチして利用します。
今回は採用していませんが、[RequireComponent(typeof(T))] 属性を付与することで安全に利用できます。


Chaser.cs

 <= クリックすると開きます



解説


 public Transform target で指定されたプレイヤーオブジェクト(または他の対象)を追跡するためのTransformを保持する変数を宣言します。

 private NavMeshAgent agent でNavMeshAgentコンポーネントを保持する変数を宣言します。NavMeshAgentは、NavMesh上での移動を管理するコンポーネントです。

 Start メソッド内で、NavMeshAgentコンポーネントを取得し、移動速度を設定します。
もしNavMeshAgentコンポーネントが取得できなかった場合、エラーメッセージを表示します。

 Update メソッド内で、以下の処理を行います。

   a. NavMeshAgentが存在しないか、対象(プレイヤーオブジェクト)が設定されていない場合、処理を中断します。これはエラーを防ぐためのチェックです。

   b. オブジェクトと対象までの距離を計算し、デバッグログで表示します。この距離をもとに、対象が視界に入ったかどうかを判定します。

   c. sightRange 変数は視界を表現します。
    この内に対象がいる場合(距離が sightRange 以下の場合)、NavMeshAgentの SetDestination メソッドを使用して対象の位置を移動目標地点に設定します。
    これにより、オブジェクトは対象を追跡します。

   d. sightRange よりも対象が遠くにいる場合、NavMeshAgentの ResetPath メソッドを使用して移動を停止します。つまり、対象が視界外に出たと判断して、追跡を停止します。

   e. 最後に、オブジェクトの回転を制御し、不要な回転を停止します。これにより、オブジェクトが不自然に回転しないようになります。


修正案 ー目標を失った場合に、初期地点まで戻っていく機能の追加ー


 ゲーム実行内で生成されて、経路を見つける場合、および、目標を失った場合に、初期地点まで戻っていく機能を追加してみましょう。

 ゲームオブジェクトをインスタンスして NavMeshAgent による移動を行いたい場合、
事前に NavMeshAgent コンポーネントをオフにしておくか、情報を取得してから経路を設定するようにします。

 インスタンスされた直後は Bake されている経路情報を知らないため、経路検索に失敗してエラーになるためです。


Chaser.cs

 <= クリックすると開きます



<ポイント>


 Update メソッド内の処理が、適切な位置で return され、不要な処理が動かないように制御されています。
このような形で return を上手く活用すると、if 文によるネストを浅く出来ます。

 仮に、もしも追跡中だったら、という if 文で処理を書く形を見てみましょう。


     private void Update()
    {
        // NavMeshAgent が取得できない、あるいは移動先の対象が設定されていない場合
        if (!agent || !target)
        {
            // 処理を行わない(エラーが出てしまうため)
            return;
        }

    // このオブジェクトと移動先の対象までの距離を計算(どちらの計算方法でもよいが、sqrMagnitude の方が処理が早い)
        //float distanceToPlayer = Vector3.Distance(transform.position, target.position);
        float distanceToPlayer = (transform.position - target.position).sqrMagnitude;

        // どの位離れているかを表示
        Debug.Log("対象までの距離 : " + distanceToPlayer);

        if (isChasing)
        {
            // 対象が視界に入った場合(距離が近い場合)
            if (distanceToPlayer <= sightRange)
            {
                // 対象の位置を移動目標地点に設定
                // Update メソッド内で実行しているので、対象が移動すると、目標地点も更新されるので追跡する
                agent.SetDestination(target.position);
            }
            else
            {
                // 対象が視界外の場合(距離が遠い場合)、スタート地点に戻る
                agent.SetDestination(originalDestination);
            }
        }
        else
        {
            // 追跡中ではない場合、移動を停止する
            agent.ResetPath();
        }
    }

 追跡するか、否かを判断する前に計算処理をしていたり、if 文内にさらに if 文を作って追跡時の処理を書いているので、
if 文のネストが深く、また範囲が広く読みにくくなっています。

 分岐処理を作成する場合には、true であったときに処理を動かした方がよいのか、false として処理を止めるようにした方がよいのか
両方の視点を持って作っていくように心がけましょう。
 
 コーディングスキルを上達させるコツの1つです。
このような視点を手に入れられないと、いつまでも同じ形式の分岐処理しか作れないため、スキルアップに繋がりません。