【Unity】NavMeshを使って敵キャラを障害物のないところに出現(Instantiate)させる

2023年10月10日

ドラクエのようなRPGで敵オブジェクトを自動的に出現させるための方法について考えましょう
作成にあたっては、次のような条件が必要になると思われます

  1. 障害物がある場所には出現させない(障害物に埋もれるから)
  2. プレイヤーから距離を空ける(すぐやられるので)
  3. 出現場所はランダム
  4. 出現時間は一定時間ごと

仕組み

ベクトルとベクトルの回転、NavMeshを使った判断を組み合わせて実現します

チュートリアルで確認

チュートリアルを通して、検証してみましょう

シーンの作成、オブジェクトの配置

必要に応じてEnemyAppearanceプロジェクトを作成します。

3Dでプロジェクトを作成します。

床を作成します。

3DオブジェクトでPlaneを選択します。
サイズを大きくするために Scale(10, 1, 10)にしておきます。

プレイヤーを作成します。

3DオブジェクトでCupsuleを選択します。
Position(0, 1, 0)に変更して、床の上に移動しておきます

障害物を作成します。

3DオブジェクトでCubeを選択します。
Position(10, 0.5, 0)に変更して10m離れた場所に設置します。
Scale(3, 1, 3)に変更して、サイズを大きくしておきます。

NavMeshの作成

PackageManagerからAIのインストール

必要に応じてWindowメニューのPackageMangerからAIをインストールします

NavMesh Surfaceの作成

Hierarchyビューを右クリック(または+をクリック)して、NavMesh Surfaceゲームオブジェクトを作成します

NavMeshをベイクします

NavMesh Surfaceゲームオブジェクトを選択し、NavMeshSurfaceコンポーネントのBakeをクリックします

ベイクの結果ですが、なんと、障害物の上を歩けちゃいますね。
これでは、困りますので、設定を変更します。

歩いてはいけないエリアの指定

障害物の上は歩けないように設定します。

障害物をクリックして選択します。

NavMesh Modifierコンポーネントをアタッチ

歩けないエリア(Not Walkable)に設定

再度、ベイクします。

敵の作成

敵のプレファブを作成する

球(Sphere)を敵に見立てて、ゲームオブジェクトを作成
Project WindowsにD&Dしてプレファブを作成します。
元の球は消しておきます。

スクリプトの作成

テスト用スクリプトを作成

プレイヤーを中心に半径10mのところに敵を作成するテストをします。
作る場所は、20°ずつ回転された場所になります。

using UnityEngine;
using UnityEngine.AI;

public class Spawner : MonoBehaviour
{
    // プレイヤーの位置
    [SerializeField]
    Transform playerTransform;

    // エネミープレファブ
    [SerializeField]
    GameObject enemyPrefab;

    // 回転させる角度(初期は0。テストでは、コードの中で20°ずつ増やしていく)
    int yAngle = 0;

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            SpawnEnemy();
        }
    }

    private void SpawnEnemy()
    {
        // 長さ10mのベクトルをプレイヤーと敵との間の距離とし、変数に代入
        var basePosition = new Vector3(10, 0);

        // 上記のポジションを、(0, 0, 0)を中心(原点)として、y軸を中心に20°(yAngle分)回転させた位置を取得
        var spawnPositioAfterRotate = Quaternion.Euler(0, yAngle, 0) * basePosition;

        // プレイヤーの位置を足すことで、プレイヤー位置から一定距離(10m)の円を描いた位置を取得
        var enemySpawnPositon = playerTransform.position + spawnPositioAfterRotate;

        // もし、どのようにしても置けない時だけ、結果がfalseになる
        if (NavMesh.SamplePosition(enemySpawnPositon, out NavMeshHit navMeshHit, 10, NavMesh.AllAreas))
        {
            // navMeshHit変数は、
            //   NavMeshベイクエリアに置ける場合は、spawnPositionの情報が代入される
            //   NavMeshベイクエリアじゃない場合、一番近いNavMeshベイクエリアの情報が代入される
            // Instantiate()メソッドは次のような引数も取れます
            //  Instantiate(作成するオブジェクト, 作成する位置, 作成する角度)
            Instantiate(enemyPrefab, navMeshHit.position, Quaternion.identity);
            Debug.Log("敵を置ける");
        }
        else
        {
            Debug.Log("敵は置けない");
        }

        // 角度を20°増やして繰り返す
        yAngle += 20;
    }
}

空のゲームオブっジェクトを作成し、Spawnerスクリプトをアタッチします。
playerTransformには、Capsuleを
enemyPrefabには、作成したプレファブをアウトレット接続します。

テスト結果

実行してみましょう。
最初の敵は、出現場所が障害物の位置になるので、一番近いところに配置されているのがわかります。
2番目以降は、20°ずつ、プレイヤーから10m離れて出現しているのがわかると思います。

参考

Instantiateの解説は、ジェネリック版を参照するのが今では一般的です。

Unity,小技

Posted by hidepon