Unityで敵をプレイヤーの周りにランダム出現させる|NavMesh対応のSpawnerをコルーチンで作ろう

広告

ゲームでは、敵が少しずつ現れてプレイヤーに迫ってくると、一気に緊張感が出ます。この記事では、その「敵を出すしくみ」を作るスクリプト Spawner を、5つのポイントに分けて読み解いていきます。

この記事で作るもの

次の3つを実現する Spawner スクリプトです。

  • プレイヤーの周りのランダムな場所に敵を出す
  • 壁の中など、おかしな場所には出さない
  • プレイヤーが倒れたら、敵を出すのをやめる

全体のコード

まずは全体を見てみましょう。

using UnityEngine;
using System.Collections;
using UnityEngine.AI;

public class Spawner : MonoBehaviour
{
    [SerializeField] private PlayerStatus playerStatus;
    [SerializeField] private GameObject enemyPrefab;

    private void Start()
    {
        StartCoroutine(SpawnLoop());
    }

    private IEnumerator SpawnLoop()
    {
        while (true)
        {
            var distanceVector = new Vector3(10, 0);
            var spawnPositionFromPlayer = Quaternion.Euler(0, Random.Range(0, 360f), 0) * distanceVector;
            var spawnPosition = playerStatus.transform.position + spawnPositionFromPlayer;

            NavMeshHit navMeshHit;
            if (NavMesh.SamplePosition(spawnPosition, out navMeshHit, 10, NavMesh.AllAreas))
            {
                Instantiate(enemyPrefab, navMeshHit.position, Quaternion.identity);
            }

            yield return new WaitForSeconds(10);

            if (playerStatus.Life <= 0)
            {
                break;
            }
        }
    }
}

少し長く見えるかもしれませんが、5つのポイントに分けて読むと、むずかしくありません。

補足:PlayerStatus は、プレイヤーの体力 Life などを持つスクリプトです。ご自身のプロジェクトのプレイヤー用スクリプトに置き換えて試してみてください。

ポイント1:2つの変数

[SerializeField] private PlayerStatus playerStatus;
[SerializeField] private GameObject enemyPrefab;
  • playerStatus は、プレイヤーの情報です。どこにいるかと体力が残っているかを調べるために使います。
  • enemyPrefab は、出現させる敵の設計図(プレハブ)です。

[SerializeField] がついているので、Unity のインスペクターから、ドラッグ&ドロップで設定できます。

ポイント2:コルーチンで10秒ごとに繰り返す

private void Start()
{
    StartCoroutine(SpawnLoop());
}

ゲームが始まると、SpawnLoop() というコルーチンが動き出します。

コルーチンは「途中で待つことができる関数」です。yield return new WaitForSeconds(10); と書くと、そこで10秒間待ってから続きを実行します。この待ち時間のおかげで、約10秒ごとに敵が1体出るようになります。

while (true) は「ずっと繰り返す」という意味ですが、あとで説明する break でやめることができます。

ポイント3:プレイヤーの周りのランダムな場所を決める

ここが、いちばん面白いところです。

var distanceVector = new Vector3(10, 0);
var spawnPositionFromPlayer = Quaternion.Euler(0, Random.Range(0, 360f), 0) * distanceVector;
var spawnPosition = playerStatus.transform.position + spawnPositionFromPlayer;

次の3ステップで考えてみましょう。

  1. 10m先を指すベクトルを作る。 new Vector3(10, 0) は「横に10進んだ位置」を表します。
  2. ランダムな角度で回す。 Quaternion.Euler(0, 角度, 0) は、上から見て回す回転です。角度は Random.Range(0, 360f) で、0〜360度のどれかになります。この回転をベクトルに掛けると、向きだけがランダムに変わります。
  3. プレイヤーの位置に足す。 プレイヤーの位置に足せば、プレイヤーからちょうど10m離れた円の上の、ランダムな地点になります。

プレイヤーを中心にして、まわりの円周のどこかにサイコロで点を打つようなイメージです。

ポイント4:NavMesh で「出てよい場所」に直す

ただし、ランダムに決めた場所が、壁の中や床のない場所になることもあります。そこで、次のコードを使います。

NavMeshHit navMeshHit;
if (NavMesh.SamplePosition(spawnPosition, out navMeshHit, 10, NavMesh.AllAreas))
{
    Instantiate(enemyPrefab, navMeshHit.position, Quaternion.identity);
}

NavMesh.SamplePosition は、「この地点の近くで、歩ける場所(NavMesh の上)はどこ?」と探してくれる関数です。

  • 見つかったら true が返り、その場所が navMeshHit.position に入ります。
  • 見つからなかったら false なので、この回は敵を出しません。

Instantiate は、プレハブから敵を1体つくる命令です。見つかった場所に出すので、敵はかならず歩ける場所に出現します。

補足:敵が NavMeshAgent で動くときは、NavMesh の外に出すと動けません。だから、出す前に場所を直しておくことが大切です。

ポイント5:プレイヤーが倒れたら止める

if (playerStatus.Life <= 0)
{
    break;
}

10秒待ったあと、プレイヤーの体力(Life)が0以下かどうかを調べます。0以下なら break で while をぬけて、コルーチンが終わります。これでゲームオーバーのあとは、敵が出てこなくなります。

使い方

  1. シーンに空の GameObject を作り、Spawner をアタッチします。
  2. インスペクターで、Player Status にプレイヤーを、Enemy Prefab に敵のプレハブを設定します。
  3. シーンの床に NavMesh をベイク(作成)しておきます。
  4. 再生すると、10秒ごとにプレイヤーの近くに敵が出ます。

図解でおさらい

5つのポイントを、図でもう一度たどってみましょう。

まとめ

やりたいこと使うもの
一定時間ごとに繰り返すコルーチン + WaitForSeconds
まわりのランダムな方向を決めるQuaternion.Euler × Vector3
歩ける場所に直すNavMesh.SamplePosition
プレイヤーが倒れたら止めるLife の確認 + break

挑戦してみよう

数字を変えるだけで、ゲームの手ざわりはがらりと変わります。ぜひ試してみてください。

  • 出現の間隔(10秒)を、5秒や3秒に変えると、ゲームの難しさはどう変わるでしょうか。
  • 出現する距離(10m)を変えると、どんな感じになるでしょうか。
  • 敵を1回に2体ずつ出すには、どこを変えればよいでしょうか。

自分で数字を変えて、動きを確かめてみましょう。

訪問数 1 回, 今日の訪問数 1回

広告