Unityに関連する記事です

 サイドビュー型の2D ゲーム(スーパーマリオブラザーズのようなゲーム画面)における、NavMeshAgent によるベーシックな追跡機能の実装例です。
タイルマップ上でも機能します。








<実装動画>
動画ファイルへのリンク



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

 トップダウン型については、こちらの記事を参考にしてください。

  => 【2D】NavMeshAgent によるベーシックな追跡機能の実装例



サンプルコード


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

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

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

 また NavMeshAgent は3Dをベースに機能し、対象の方向に対しての回転も自動的に行います。
その機能を2Dで使用すると不具合が生じるため、それらの機能を動かないように制御しています。

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 メソッドを使用して移動を停止します。つまり、対象が視界外に出たと判断して、追跡を停止します。



 3Dの場合と大きく異なるのは、agent.updateRotation と agent.updateUpAxis をオフに設定し、3D用の回転設定を無効にしている部分です。
また高さの情報も不要であるため、Y 軸は常にプレイヤーの情報を利用し、上下左右方向の対象までの距離を計算しています。
(分かりにくいですが、NavMeshAgent は3D空間を認識しているため、上下左右の情報は X 軸と Z 軸になります。)

 これにより、2D空間でNavMeshを正しく使用できます。


<チェックポイント  Z 軸の値による不具合ー>


 なお、対象を追跡する際に Position の Z 軸がマイナス方向になって画面から消えてしまう場合には、
スクリプトを利用して強制的に Z 軸を 0 に制御する方法があります。


        // 対象が視界に入った場合(距離が近い場合)
        if (distanceToPlayer <= sightRange)
        {
            // 対象の位置を移動目標地点に設定
            // Update メソッド内で実行しているので、対象が移動すると、目標地点も更新されるので追跡する
            agent.SetDestination(targetPosition);

            // Position Z が 0 以外になって画面から見えなくなる場合には制御する
            Vector3 nextPos = new(transform.position.x, transform.position.y, 0);
            transform.position = nextPos;
        }


<チェックポイント◆ 璽ぅ鵐好織鵐垢靴疹豺腓良垓餽隋次


 NavMeshAgent コンポーネントを持つゲームオブジェクトを動的に生成した場合、
Bake 情報をしらない状態でスタートするため、エラーがでるケースがあります。

 これについては、プレハブ時には NavMeshAgent コンポーネントをオフにしておいて
TryGetComponent 成功後にオンにすることで対応出来ます。
この機能はすでに実装済です。


 
 他に、生成した位置について問題が生じることがあります。
プレハブを生成した位置が Bake されていれば問題ありませんが、Bake 外にて生成してしまうとエラーが発生します。

 対応策としては2つあります。

 1つは、NavMesh には NavMeshPathStatus という enum があり、こちらを利用して、Bake させれている場所かどうかを確認する方法があります。

if(agent.pathStatus != NavMeshPathStatus.PathInvalid) {

    // この中で SetDestination する

}


参考サイト
Unity 公式スクリプト・リファレンス
NavMeshPathStatus



 ただしこの方法の場合、この条件を満たさないと動かない状態になります。

 もう1つの方法としては、NavMesh.SamplePosition メソッドを利用する方法です。
こちらは引数で指定した位置が Bake されていない場合、最も近い Bake 地点を探してくれるメソッドです。
この処理を組み合わせることで安全な生成が行えます。


if (NavMesh.SamplePosition(transform.position, out NavMeshHit hit, 1.0f,NavMesh.AllAreas))
{
   // 位置を Bake エリア内に補正する
   transform.position = hit.position;
}

参考サイト
Unity 公式スクリプト・リファレンス
NavMesh.SamplePosition
ソフトライム 様
敵キャラの出現を自動化する(NavMeshパターン)
Zenn k1t(外神) 様
Unity C# Script かしこくエモい動きの敵キャラはNavMeshを使いこなせ。


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


 目標を失った場合に、初期地点まで戻っていく機能を追加してみましょう。


Chaser.cs

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



<チェックポイント ーif 文の書式ー>


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

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


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

        // このオブジェクトと移動先の対象までの距離を計算
        Vector3 targetPosition = new Vector3(target.position.x, transform.position.y, target.position.z);
        float distanceToPlayer = Vector3.Distance(transform.position, targetPosition);

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

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

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

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

 スキルアップに繋がるような学習を心掛けましょう。


Bake の調整


 Radius などを調整することで、地面ちょうどの位置まで Bake することが出来ます。
そうすることで、地面の上を移動しているように見せることが可能になります。


<調整前>



<調整後>



空中移動の制御


 NavMeshAgent による経路移動は、Bake している範囲内であれば経路として利用されます。
そのため、サイドビューのゲームであると、敵が空中移動してしまうケースが発生します。

 制御方法としては下方向への重力がありますが、残念ながら NavMeshAgent コンポーネントの機能を利用している場合、
Rigidbody2D コンポーネントを一緒に機能させると競合して正常な動作が行えません。

 そのため、疑似的な重力値を設定することでサイドビューに合わせた調整が可能になりますが、それは実装がとても難しいです。



 1つの方法として、新しいタイルマップ用のゲームオブジェクトを用意し、敵の移動を阻害するためのタイルを敷き詰めて対応する方法があります。


<タイルマップを空中に配置する>



<Bake すると空中が移動できない範囲になる>



 この例であれば、空中移動させたくない空中部分に地面のタイルマップを配置します。
このタイル部分は Bake 対象とならないようにしておくことで、空中部分を移動できない範囲として認識させることが出来ます。

 その後、TilemapRenderer をオフにするか、OrderinLayer を下げて画面にタイルが映らない状態にします。

 また Bake に必要な TilmemapCollider2D コンポーネント があるとプレイヤーも頭をぶつけてジャンプできません。

 そのため、TilemapCollider2D コンポーネントについては、Bake 後にオフにするのが簡単な方法なのですが
残念ながらゲーム実行時に再度 Bake が走ってしまう仕様上、Bake 後に TilemapCollider をオフにしてしまうと、上手く Bake 出来ていた部分が消えてなくなってしまいます

 ここは安易にオフには出来ないため、この Layer を Bake にして、プレイヤーのコライダーと接触しないようにして
移動とジャンプを阻害しないようにしています。


<実装動画>
動画ファイルへのリンク



 もう1つのアプローチとしては、上記の応用で、敵の移動範囲自体をもっと大胆に制限する方法です。
下記の画像ように、移動範囲を細かく制限します。




 こちらの手法であれば、特定の範囲内のみで追跡移動する敵なども作成できます。



 以上になります。

 色々な実装方法がありますので、是非チャレンジして、よりよい方法を発見してみてください。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3Dレールガンシューティング(応用編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます