LoginSignup
3
5

【Unity】Unity標準のObjectPoolを汎用的に使うクラスの作成

Last updated at Posted at 2024-05-10

はじめに

背景

以前私が執筆した「[Unity] UniRxのObjectPoolを使ってParticleSystemを管理する」では、UniRxを用いてオブジェクトプールのデザインパターンを実装しました。
UniRxの後継ライブラリであるR3では、UniRx.ToolKit.ObjectPoolに相当するものがなく、R3にはオブジェクトプールの機構がありません。また、UniRxは後継のR3が登場したこともあり、現在はアーカイブされ、今後更新されないようです。

そこで今回はUniRx.ToolKit.ObjectPoolの代替としてUnity公式のオブジェクトプールを使用することにしました。

しかし、Unity標準のオブジェクトプールは、各ゲームオブジェクトごとにオブジェクトプールの生成と、プールされるゲームオブジェクトの生成、オブジェクトプールへの返却、オブジェクトプールからの取得、破棄を全て記述する必要がありとても手間がかかります。
そこで共通化できるところは共通化して継承して使ってしまおうと考えて実装したのがこの記事の内容になります。

検証環境

Unity : 2023.2.16f1
MacOS Ventura

オブジェクトプールとは

オブジェクトプールとは、オブジェクトを使いまわすための機構のことです。

Unityではオブジェクトを生成するときにInstantiate、削除するときにDestroyを使いますが、InstantiateとDestroyのどちらもそれなりに重い処理であるため、多用するとゲームの処理が重くなる恐れがあります。
特にゲームのエフェクトは大量に生成されることが多く、毎回Instantiateをしてエフェクトを再生し、再生が終了したらDestroyをしていてはフレームレートを著しく下げてしまいます。

今回はこの問題を解決するためのデザインパターンのオブジェクトを用いて、生成したゲームオブジェクトやエフェクトを削除せずに非表示にして、必要になったら再度表示してエフェクトを再生する仕組みを実例として実装していきます。

実装

プールされるオブジェクトのインターフェースの作成

ここでは、プールされるオブジェクトのインターフェースを作成します。

IPooledObject.cs
using UnityEngine.Pool;

public interface IPooledObject<T> where T : class
{
    public IObjectPool<T> ObjectPool { set; }
    public void Initialize();
    public void Deactivate();
}

インターフェースでは、ObjectPoolと生成されたタイミングで呼ばれるメソッドと破棄されるタイミングで呼ばれるメソッドを用意します。

オブジェクトプールを構築する抽象クラスの作成

ここでは、オブジェクトプールを構築する抽象クラスを作成します。

PoolManager.cs
using UnityEngine;
using UnityEngine.Pool;

public abstract class PoolManager<T> : MonoBehaviour where T : MonoBehaviour, IPooledObject<T>
{
    [SerializeField] private T pooledPrefab;
    protected IObjectPool<T> _objectPool;
    
    [SerializeField] private bool _collectionCheck = true;
    
    [SerializeField] private int _defaultCapacity = 32;
    [SerializeField] private int _maxSize = 100;

    public virtual  void Initialize()
    {
        _objectPool = new ObjectPool<T>(Create, OnGetFromPool, OnReleaseToPool, OnDestroyPooledObject,
            _collectionCheck, _defaultCapacity, _maxSize);
    }
    
    protected virtual T Create()
    {
        T instance = Instantiate(pooledPrefab, transform.position, Quaternion.identity, transform);
        instance.ObjectPool = _objectPool;
        return instance;
    }
    
    protected virtual void OnReleaseToPool(T pooledObject)
    {
        pooledObject.gameObject.SetActive(false);
    }
    
    protected virtual void OnGetFromPool(T pooledObject)
    {
        pooledObject.gameObject.SetActive(true);
    }
    
    protected virtual void OnDestroyPooledObject(T pooledObject)
    {
        pooledObject.Deactivate();
        Destroy(pooledObject.gameObject);
    }
}

PoolManager<T>は、MonoBehaviourを継承する抽象クラスです。ジェネリック型パラメータTには、MonoBehaviourを継承し、かつIPooledObject<T>インターフェイスを実装する型を指定する必要があります。

このIPooledObject<T>インターフェースとPoolManager<T>抽象クラスを用いてオブジェクトプールの機構を作成していきます。

実例

ゲームオブジェクトのオブジェクトプール

まずはプールされるゲームオブジェクトのクラスを作成します。
オブジェクトプールに返却するまでの遅延でコルーチンを使用しています。

BulletObject.cs
using System.Collections;
using UnityEngine;
using UnityEngine.Pool;

public class BulletObject : MonoBehaviour, IPooledObject<BulletObject>
{
    private IObjectPool<BulletObject> _objectPool;
    public IObjectPool<BulletObject> ObjectPool
    {
        set => _objectPool = value;
    }

    public void Initialize()
    {
        StartCoroutine(DelayDeactivation(3f));
    }

    private IEnumerator DelayDeactivation(float seconds)
    {
        yield return new WaitForSeconds(seconds);
        Deactivate();
    }

    public void Deactivate()
    {
        var rigidbody = GetComponent<Rigidbody>();
        rigidbody.velocity = Vector3.zero;
        rigidbody.angularVelocity = Vector3.zero;
        _objectPool.Release(this);
    }
}

IPooledObject<T>のインタフェースを実装し、
BulletObjectを管理するBulletPoolManagerクラスの作成をします

BulletPoolManager.cs
using UnityEngine;

public class BulletPoolManager : PoolManager<BulletObject>
{
    private void Awake()
    {
        Initialize();
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (_objectPool == null)
            {
                return;
            }
            
            var bulletObject = _objectPool.Get();

            if (bulletObject == null)
            {
                return;
            }
            
            bulletObject.transform.position = transform.position;
            bulletObject.Initialize();
        }
    }
}

PoolManager<T>を継承し、ジェネリック型パラメータTには先ほど作成したBulletObjectクラスを指定します。

ParticleSystemのオブジェクトプール

まずはプールされるParticleSystemにアタッチするクラスを作成します。
こちらもオブジェクトプールに返却するまでの遅延でUniTask.Delayを使用しています。

ParticleObject.cs
using System.Collections;
using UnityEngine;
using UnityEngine.Pool;

public class ParticleObject : MonoBehaviour, IPooledObject<ParticleObject>
{
    private IObjectPool<ParticleObject> _objectPool;
    public IObjectPool<ParticleObject> ObjectPool
    {
        set => _objectPool = value;
    }
    public void Initialize()
    {
        var particle = GetComponent<ParticleSystem>();
        particle.Play();
        var lifetime = particle.main.startLifetime.constantMax;
        StartCoroutine(DelayDeactivation(lifetime));
    }
    
    private IEnumerator DelayDeactivation(float seconds)
    {
        yield return new WaitForSeconds(seconds);
        Deactivate();
    }

    public void Deactivate()
    {
        _objectPool.Release(this);
    }
}

IPooledObject<T>のインタフェースを実装し、
ParticleObjectを管理するBulletPoolManagerクラスの作成をします

ParticlePoolManager.cs
using UnityEngine;

public class ParticlePoolManager : PoolManager<ParticleObject>
{
    private void Awake()
    {
        Initialize();
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (_objectPool == null)
            {
                return;
            }
            
            var particle = _objectPool.Get();

            if (particle == null)
            {
                return;
            }
            
            particle.transform.position = transform.position;
            particle.Initialize();
        }
    }
}

PoolManager<T>を継承し、ジェネリック型パラメータTには先ほど作成したParticleObjectクラスを指定します。

動作確認

qiita_objectpool.gif
オブジェクトプールに貯めてある個数以上のオブジェクトが必要になった場合はその都度オブジェクトを生成し、使用可能なオブジェクトがある場合は使いまわしているのが確認できます。
みなさんもぜひ大量生成大量破棄をするものを作る際はオブジェクトプールの機構を作ってみましょう!!

3
5
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
3
5