1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Unity公式が公開しているプログラミングパターンを読み取ってみた!~その7. Object Pool~

Posted at

はじめに

Unity公式がプログラミングパターンのデモを説明書と共に全12種公開していたので、1つずつ読み取っていきたいと思います!
英語の説明書でしたので間違った解釈をしているかもしれませんが、ご了承下さい!
最初の5種は、SOLID原則に沿ったデモとなっており、コードのみが公開されています。
残り7種は、Scene付きのデモが入っていますので、ZIPファイルをダウンロードして試してみてください。
今回は、7つ目のObject Poolについてです。

環境

OS : Windows10
Unity バージョン : 2021.3.8f1

Object Poolとは

大量のGameObjectを作成、破棄するときに使用する最適化手法です。大量に生成するGameObjectは、ロード画面中などのユーザーが気づかない箇所で生成してしまい、後は必要に応じてGameObjectを表示・非表示させます。Object Poolを使用することで、CPUに負荷が掛かりにくくなります。

デモ

デモは、マウスでフィールド内をクリックすると、大砲の向いている方向に弾丸が発射されるというシンプルなゲームです。
20230218_AdobeExpress.gif

ここで大量に発生するGameObjectは、弾丸になります。
Hierarchy上には空のオブジェクトObjectPoolが作成してあり、弾丸を生成して溜めておくObjectPool.csがアタッチされています。
image.png
ObjectPool.csは、ゲーム起動すると指定数(initPoolSize)弾丸を生成し、stack内にプールしておくようになっています。

public class ObjectPool : MonoBehaviour
{
    [SerializeField] private uint initPoolSize;
    [SerializeField] private PooledObject objectToPool;
    
    private Stack<PooledObject> stack;
    private void Start()
    {
        SetupPool();
    }
    // ゲーム起動と共に、initPoolSize分弾丸が生成され、stack内にプールされる
    private void SetupPool()
    {
        stack = new Stack<PooledObject>();
        PooledObject instance = null;
        for (int i = 0; i < initPoolSize; i++)
        {
            instance = Instantiate(objectToPool);
            instance.Pool = this;
            instance.gameObject.SetActive(false);
            stack.Push(instance);
        }
    }

ゲームを起動すると弾丸が以下のように生成されます。Projectileというのが弾丸です。
image.png

大砲(TurretGunBase)オブジェクトにはExampleGun.csというスクリプトがアタッチされていて、マウスクリックしたときの処理が書かれています。

image.png

ExampleGun.csは、マスクをクリックするとプール内にある弾丸を表示させ、一定時間が経つと非表示になるようになっています。

public class ExampleGun : MonoBehaviour
{
    [Tooltip("Prefab to shoot")]
    [SerializeField] private GameObject projectile;
    [Tooltip("Projectile force")]
    [SerializeField] float muzzleVelocity = 700f;
    [Tooltip("End point of gun where shots appear")]
    [SerializeField] private Transform muzzlePosition;
    [Tooltip("Time between shots / smaller = higher rate of fire")]
    [SerializeField] float cooldownWindow = 0.1f;
    [Tooltip("Reference to Object Pool")]
    [SerializeField] ObjectPool objectPool;

    private float nextTimeToShoot;

    private void FixedUpdate()
    {
        if (Input.GetButton("Fire1") && Time.time > nextTimeToShoot && objectPool != null)
        {
            // プール内にある弾丸を表示させる
            GameObject bulletObject = objectPool.GetPooledObject().gameObject;

            if (bulletObject == null)
                return;

            bulletObject.SetActive(true);

            bulletObject.transform.SetPositionAndRotation(muzzlePosition.position, muzzlePosition.rotation);

            bulletObject.GetComponent<Rigidbody>().AddForce(bulletObject.transform.forward * muzzleVelocity, ForceMode.Acceleration);
            
            // 時間が経つと弾丸が非表示になる処理
            ExampleProjectile projectile = bulletObject.GetComponent<ExampleProjectile>(); 
            projectile?.Deactivate();

            nextTimeToShoot = Time.time + cooldownWindow;

        }
    }
}

2021年以降のUnityバージョンを使用している場合は、独自のPooledObjectまたはObjectPoolクラスを作る必要がないそうです。

UnityのObjectPoolを使用したデモ

ObjectPoolは、よく使用されるためUnity2021バージョン以降は、独自のUnityEngine.Pool.APIをサポートするようになりました。

using UnityEngine.Pool;
public class RevisedGun : MonoBehaviour
{
    private IObjectPool<RevisedProjectile> objectPool;
    [SerializeField] private bool collectionCheck = true;
    [SerializeField] private int defaultCapacity = 20;
    [SerializeField] private int maxSize = 100;
    private void Awake()
    {
        objectPool = new ObjectPool<RevisedProjectile>(CreateProjectile,
        OnGetFromPool, OnReleaseToPool, OnDestroyPooledObject,
        collectionCheck, defaultCapacity, maxSize);
    }
    // ゲーム起動と共に、弾丸をプールしておく
    private RevisedProjectile CreateProjectile()
    {
        RevisedProjectile projectileInstance = Instantiate(projectilePrefab);
        projectileInstance.ObjectPool = objectPool;
        return projectileInstance;
    }
    // 弾丸が非表示
    private void OnReleaseToPool(RevisedProjectile pooledObject)
    {
        pooledObject.gameObject.SetActive(false);
    }
    // プール内から弾丸を表示させる
    private void OnGetFromPool(RevisedProjectile pooledObject)
    {
        pooledObject.gameObject.SetActive(true);
    }
    // 上限を超えると弾丸を破棄
    private void OnDestroyPooledObject(RevisedProjectile pooledObject)
    {
        Destroy(pooledObject.gameObject);
    }

ほとんど、前の例と同じですが、以下機能がデフォルトで入っています。

  • 最初に弾丸をプールする
  • 弾丸をプールに戻す
  • 上限を設定でき、超えると弾丸を破棄する

個人的には、こちらの方がシンプルに見えて好きかなと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?