スクリプトからプレハブを生成しよう【Unityスタートガイド7-3】

コインを10枚、敵を時間ごとに1体ずつ、弾をボタンを押すたびに1発。ゲームでは、オブジェクトを「あとから作る」場面がたくさんあります。6-9の記事で「Instantiateは第7章で」とお話ししていたのがこれです!今回は、スクリプトからプレハブを生成するInstantiateと、消すDestroyをまとめてご紹介します。

目次

この記事で学べること

  • Instantiateでプレハブを生成する方法
  • 生成する位置と回転を指定する方法
  • たくさん生成する・時間ごとに生成する方法
  • 入力に合わせて弾を撃つ方法
  • Destroyで生成したオブジェクトを消す方法

前提知識

Instantiateでプレハブを生成する

Instantiateの基本

スクリプトからプレハブを生成するには、Instantiateを使います。指定したプレハブのコピー(インスタンス)を、シーンに作ってくれます。

using UnityEngine;

public class PrefabSpawner : MonoBehaviour
{
    [SerializeField] private GameObject prefabToSpawn; // 生成するプレハブをInspectorで設定

    void Start()
    {
        // プレハブを生成
        Instantiate(prefabToSpawn);
    }
}

スクリプトとプレハブを準備する

手順1: スクリプトを作成

  1. Projectウィンドウで右クリック>Create>MonoBehaviour Script
  2. 名前を「PrefabSpawner」にして作成

手順2: スクリプトを編集

上のコードに書き換えて保存します。

手順3: プレハブを設定

  1. Hierarchyで右クリック>Create Emptyで空のオブジェクトを作り、名前をSpawnerにする
  2. PrefabSpawnerをアタッチ
  3. Inspectorの「Prefab To Spawn」に、Projectウィンドウのプレハブ(7-1で作ったCubeや、6-10のコインをプレハブにしたもの)をドラッグ&ドロップ

再生すると、Hierarchyに「Cube(Clone)」のように、名前に(Clone)が付いたオブジェクトが増えます。

Hierarchyのオブジェクトではなく、Projectウィンドウのプレハブを設定するのがポイントです。シーンのオブジェクトを設定すると、そのオブジェクトをコピーすることになり、シーンから消したときに生成出来なくなります。

生成する位置と回転を指定する

位置を指定して生成

Vector3 spawnPosition = new Vector3(0f, 1f, 0f);
Instantiate(prefabToSpawn, spawnPosition, Quaternion.identity);
  • 2つ目:生成する位置(Vector3)
  • 3つ目:回転(Quaternion.identityは「回転なし」)
  • 自分と同じ位置・向きにしたいときはInstantiate(prefabToSpawn, transform.position, transform.rotation);

生成したオブジェクトを変数で受け取る

Instantiateは、生成したオブジェクトを返してくれます(6-6の戻り値です)。

GameObject spawned = Instantiate(prefabToSpawn, spawnPosition, Quaternion.identity);
spawned.name = "Coin_01";   // 名前を変える

生成したあとに名前を変えたり、コンポーネントを取得して設定したりできます。

ランダムな位置に生成

using UnityEngine;

public class RandomSpawner : MonoBehaviour
{
    [SerializeField] private GameObject prefabToSpawn;

    void Start()
    {
        // -5〜5の範囲でランダムな位置に生成
        float randomX = Random.Range(-5f, 5f);
        float randomZ = Random.Range(-5f, 5f);

        Vector3 randomPosition = new Vector3(randomX, 0.5f, randomZ);
        Instantiate(prefabToSpawn, randomPosition, Quaternion.identity);
    }
}

Random.Range(最小, 最大)で、その範囲のランダムな数が作れます。

たくさん生成する

for文で一気に生成:コインをばらまく

6-5のfor文と組み合わせると、一度にたくさん生成できます。

using UnityEngine;

public class CoinScatter : MonoBehaviour
{
    [SerializeField] private GameObject coinPrefab;
    [SerializeField] private int coinCount = 10;     // 生成する数
    [SerializeField] private float range = 8f;       // 生成する範囲

    void Start()
    {
        for (int i = 0; i < coinCount; i++)
        {
            Vector3 position = new Vector3(Random.Range(-range, range), 0.5f, Random.Range(-range, range));
            Instantiate(coinPrefab, position, Quaternion.identity);
        }
    }
}

6-10のコイン(タグがCoinでIs Triggerにチェックしたもの)をプレハブにして設定すると、PlayerMoverとCoinCollectorで集められるコインが、再生するたびにばらまかれます。

時間ごとに生成する

using UnityEngine;

public class TimedSpawner : MonoBehaviour
{
    [SerializeField] private GameObject prefabToSpawn;
    [SerializeField] private float spawnInterval = 2f;   // 生成する間隔(秒)

    private float timer = 0f;

    void Update()
    {
        timer += Time.deltaTime;

        if (timer >= spawnInterval)
        {
            timer = 0f;
            Vector3 position = new Vector3(Random.Range(-5f, 5f), 1f, 10f);
            Instantiate(prefabToSpawn, position, Quaternion.identity);
        }
    }
}

timerで時間を数えて、spawnInterval秒ごとに1つ生成します。Updateで条件なしにInstantiateすると、毎フレーム生成されてあっという間に何千個にもなるので、必ず間隔や回数を決めましょう。

実践:ボタンで弾を撃つ

入力(6-7)と組み合わせて、左クリック(Attack)で弾を撃ってみましょう。

弾のプレハブを作る

  1. Hierarchyで右クリック>3D Object>Sphereを作り、名前をBulletにする
  2. Scaleを(0.3, 0.3, 0.3)にする
  3. Add Component>Physics>Rigidbodyを付けて、Use Gravityのチェックを外す
  4. 下の「AutoDestroy」スクリプトをアタッチする(一定時間で消えるように)
  5. ProjectウィンドウのPrefabsフォルダにドラッグしてプレハブにし、Hierarchyのものは削除
using UnityEngine;

public class AutoDestroy : MonoBehaviour
{
    [SerializeField] private float lifetime = 3f;   // 何秒で消えるか

    void Start()
    {
        // 生成されてからlifetime秒後に消える
        Destroy(gameObject, lifetime);
    }
}

弾を撃つスクリプト

「BulletShooter」を作って、6-8のPlayer(PlayerMoverを付けたCapsule)にアタッチします。

using UnityEngine;

public class BulletShooter : MonoBehaviour
{
    [SerializeField] private Rigidbody bulletPrefab;   // 弾のプレハブ(Rigidbody付き)
    [SerializeField] private float bulletSpeed = 15f;  // 弾の速さ

    private InputSystem_Actions inputActions;

    void Awake()
    {
        inputActions = new InputSystem_Actions();
    }

    void OnEnable()
    {
        inputActions.Enable();
    }

    void OnDisable()
    {
        inputActions.Disable();
    }

    void Update()
    {
        if (inputActions.Player.Attack.WasPressedThisFrame())
        {
            Shoot();
        }
    }

    void Shoot()
    {
        // 自分の少し前に弾を作る
        Vector3 spawnPosition = transform.position + transform.forward * 1f;
        Rigidbody bullet = Instantiate(bulletPrefab, spawnPosition, transform.rotation);

        // 前に向かって飛ばす
        bullet.linearVelocity = transform.forward * bulletSpeed;
    }
}
  • 変数の型をRigidbodyにしている:Rigidbodyの付いたプレハブだけを設定でき、Instantiateの戻り値もそのままRigidbodyとして使えます(GetComponentしなくてよい)
  • bullet.linearVelocity:6-8で紹介した、Rigidbodyの速度。古い記事のvelocityは、Unity6ではlinearVelocityです
  • transform.forward:自分が向いている前の方向。PlayerMoverで進む方向を向いているので、進んでいる方向に弾が飛びます

生成したオブジェクトを消す

Destroyで消す

生成したオブジェクトは、Destroyで消します(6-9で紹介しました)。

Destroy(gameObject);        // 自分を消す
Destroy(gameObject, 3f);    // 3秒後に自分を消す

弾のように「作って、しばらくしたら消す」ものは、上のAutoDestroyのように、生成されたオブジェクト自身が自分を消すようにしておくと管理が楽です。

条件で消す:HPがなくなったら消える

using UnityEngine;

public class DestructibleTarget : MonoBehaviour
{
    [SerializeField] private int hp = 3;

    void OnCollisionEnter(Collision collision)
    {
        // 弾がぶつかったらHPを減らす
        if (collision.gameObject.name.StartsWith("Bullet"))
        {
            hp--;
            Destroy(collision.gameObject);   // 当たった弾を消す

            if (hp <= 0)
            {
                Destroy(gameObject);         // 自分を消す
            }
        }
    }
}

Cubeなどの的に付けて、弾を3回当てると消えます。6-10で紹介したタグを弾に付けて、CompareTag("Bullet")で調べるとより確実です。

たくさん作って消すときは:オブジェクトプール

弾のように、たくさん作ってすぐ消すことを繰り返すと、そのたびにメモリの確保と解放が起きて、ゲームが一瞬カクつくことがあります。

そこで、使い終わったオブジェクトを消さずに非表示にしておき、次に必要になったら使い回すのがオブジェクトプールという方法です。UnityにはUnityEngine.Pool.ObjectPoolという仕組みが用意されています。

最初のうちはInstantiateとDestroyで十分です。弾や敵が多くなってカクつきが気になってきたら、ObjectPoolを使って、メモリスパイクを回避せよ!の記事を参考にしてみてください。

注意点やヒント

スライム

再生したら、Hierarchyが「(Clone)」だらけになってる・・・

フェアリー

生成しっぱなしのサインだね!消すタイミング(Destroy)を決めておくか、Updateで条件なしに生成していないか確認しよう

生成したものの親をまとめる

Instantiate(プレハブ, 位置, 回転, 親のTransform)のように4つ目に親を渡すと、その子として生成されます。空のオブジェクト(例:CoinsRoot)を用意して親にしておくと、Hierarchyが散らからずに済みます。

よくあるエラーと対処法

エラー:UnassignedReferenceException / NullReferenceException

  • 原因:Inspectorでプレハブを設定していない
  • 対処法:Projectウィンドウのプレハブを、Inspectorの欄にドラッグ&ドロップする

エラー:オブジェクトが大量に生成されて重い

  • 原因:Updateの中で、条件なしにInstantiateしている
  • 対処法:タイマーや入力(WasPressedThisFrame)で、生成するタイミングを決める。もし止まらなくなったら、再生を止めれば消えます

エラー:弾が飛ばない・すぐ落ちる

  • 原因:弾のプレハブにRigidbodyが付いていない、またはUse Gravityがオンになっている
  • 対処法:弾のプレハブにRigidbodyを付けて、Use Gravityのチェックを外す

エラー:シーンから元のオブジェクトを消したら生成出来なくなった

  • 原因:Inspectorに、Projectウィンドウのプレハブではなく、Hierarchyのオブジェクトを設定していた
  • 対処法:ProjectウィンドウのプレハブをInspectorに設定し直す

まとめ

  • Instantiate(プレハブ, 位置, 回転)で、スクリプトからプレハブを生成できる
  • Inspectorに設定するのは、Projectウィンドウのプレハブ
  • for文でまとめて、タイマーで時間ごとに生成できる
  • 生成したオブジェクトはDestroyで消す(弾は自分で自分を消すと楽)
  • たくさん作って消す場合は、オブジェクトプールも検討

これで第7章はおしまいです!プレハブとInstantiateを覚えたことで、コインや敵や弾を自由に出せるようになりました。

次のステップ

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次