はとの豆知識

Unityを中心に手っ取り早い機能の実装を紹介します。

【Unity】シューティングゲームの弾の生成などで使える標準搭載のオブジェクトプールを手っ取り早く実装してみる。

シューティングゲームなどで多くのオブジェクト生成を繰り返してしまうと、
CPUの処理能力に負荷をかけてしまします。
そこで今回は最近Unityに標準搭載されたオブジェクトプールを使用してシューティングゲームの一部を作成し
処理負荷を軽減してみたいと思います。


過去にブログで紹介した

スムーズに移動させる方法
【Unity】プレイヤー、オブジェクトをスムーズに移動させる方法 「transform.Translate」の紹介 - はとの豆知識

移動範囲を制限する方法
【Unity】プレイヤー、オブジェクトの移動範囲を制限させる方法「Mathf.Clamp」の紹介 - はとの豆知識

画面外に弾が出たら非表示
【Unity】画面外に出た、カメラから見えなくなったオブジェクトを検知する方法。 - はとの豆知識


これらを組み合わせて
作成します。

手っ取り早い実装方法

f:id:HatoHatter:20210617002744g:plain


少し見えづらいですが、右上の弾のオブジェクトが表示になったり非表示になったりして
オブジェクトを再利用できていますね。

下のコードをプレイヤーに張り付けることで
このような挙動ができます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool; /*これが必要*/

public class Player : MonoBehaviour
{
    [SerializeField, Header("弾オブジェクト")] GameObject bullet;

    public IObjectPool<GameObject> bulletPool;

    //移動制限処理用変数
    private Vector2 playerPos;
    private readonly float playerPosXClamp = 5.0f;
    private readonly float playerPosYClamp = 5.0f;

    //移動速度
    private readonly float moveSpeed = 5f;

    private void Update()
    {
        //移動系
        if (Input.GetKey(KeyCode.UpArrow))
        {
            this.Moving(Vector2.up);
        }

        if (Input.GetKey(KeyCode.DownArrow))
        {
            this.Moving(Vector2.down);
        }

        if (Input.GetKey(KeyCode.LeftArrow))
        {
            this.Moving(Vector2.left);
        }

        if (Input.GetKey(KeyCode.RightArrow))
        {
            this.Moving(Vector2.right);
        }


        //弾を発射
        if (Input.GetKeyDown(KeyCode.Space))
        {
            this.Shooting();
        }
    }

    /*プレイヤーを動かす*/
    private void Moving(Vector2 vector)
    {
        //引数で受け取った方向に移動する
        transform.Translate(vector * this.moveSpeed * Time.deltaTime);


        /*ここから下は移動制限の処理です*/

        //変数に自分の今の位置を入れる
        this.playerPos = transform.position;

        //playerPos変数のxとyに制限した値を入れる
        //playerPos.xという値を-playerPosXClamp~playerPosXClampの間に収める
        this.playerPos.x = Mathf.Clamp(this.playerPos.x, -this.playerPosXClamp, this.playerPosXClamp);
        this.playerPos.y = Mathf.Clamp(this.playerPos.y, -this.playerPosYClamp, this.playerPosYClamp);

        transform.position = new Vector2(this.playerPos.x, this.playerPos.y);
    }

    /*弾を発射*/
    private void Shooting()
    {
        this.Pool.Get();//オブジェクトプールの処理を呼び出す
    }

    /*オブジェクトプール*/
    public IObjectPool<GameObject> Pool
    {
        get
        {
            if (this.bulletPool == null)
            {
                bool collectionChecks = true;
                int maxSize = 10;
                /*
                CreatePooledItem   :弾を生成するとき
                OnTakeFromPool     :生成せずプールしたものを表示するとき
                OnReturnedToPool   :弾がプールに返却されるとき
                OnDestroyPoolObject:プールできる最大値に達したとき
                collectionChecks   :Releaseの際すでにpoolにあるかチェック
                maxSize            :プールできる最大数
                 */
                this.bulletPool = new ObjectPool<GameObject>(this.CreatePooledItem, this.OnTakeFromPool, this.OnReturnedToPool, this.OnDestroyPoolObject, collectionChecks, maxSize);
            }

            return this.bulletPool;
        }
    }

    /*プールしたいオブジェクトを生成する処理*/
    private GameObject CreatePooledItem()
    {
        //弾のオブジェクトを,プレイヤーと同じ位置,プレイヤーと同じ回転 で生成する。
        GameObject bullet = Instantiate(this.bullet, transform.position, transform.rotation);

        //生成した弾オブジェクトに弾用のコンポーネントを付ける
        var returnToPool = bullet.AddComponent<BulletComponent>();
        returnToPool.pool = this.Pool;

        return bullet;
    }

    /*弾がプールに返却されるときに呼び出される*/
    //NOTE:BulletComponentのOnBecameInvisible()から呼び出しています
    void OnReturnedToPool(GameObject bullet)
    {
        bullet.SetActive(false);
    }

    /*弾がプールから取得されるとき呼び出される*/
    void OnTakeFromPool(GameObject bullet)
    {
        //非表示になった位置で止まっているのでプレイヤーの位置に戻す
        bullet.transform.position = transform.position;

        bullet.SetActive(true);
    }

    /*プールできる最大値に達したときに呼び出される*/
    void OnDestroyPoolObject(GameObject bullet)
    {
        Destroy(bullet);
    }
}

/*これを発射する弾オブジェクトに付けます
   CreatePooledItem()のタイミングで付けてます。*/
[RequireComponent(typeof(GameObject))]
public class BulletComponent : MonoBehaviour
{
    private GameObject bullet;
    public IObjectPool<GameObject> pool;

    //移動速度
    private readonly float moveSpeed = 10f;

    private void Start()
    {
        //自分をセット
        this.bullet = this.gameObject;
    }

    private void Update()
    {
        //自分が表示されているなら
        if (gameObject.activeSelf)
        {
            //上に移動する
            transform.Translate(Vector3.up * this.moveSpeed * Time.deltaTime);
        }
    }

    //画面外に出たら呼ばれます
    private void OnBecameInvisible()
    {
        //プールに返却する
        this.pool.Release(this.bullet);
    }
}

今回使用したオブジェクトプールについて

docs.unity3d.com