2018年6月26日火曜日

[Unity C#] オブジェクトのプールの実装方法(サンプルあり)

Unityで開発する際、よくオブジェクトをプールする必要があるのですが、毎回調べなおしたり作り直したりするのが億劫になったので、使い回す用のUtil作成してみました。

とりあえず、ソースはこちらにUPしてあります。

[Pool.cs]
https://github.com/table-cloth/tc-common/blob/develop/Assets/Scripts/Pool.cs
[PoolManager.cs]
https://github.com/table-cloth/tc-common/blob/develop/Assets/Scripts/PoolableObject.cs

オブジェクトのプールって何?

そもそもプールって何よ?という方向けの説明です。
知っている方は読み飛ばしてください。

プールとは何か。

プールとはオブジェクトのストックを保持する場所のことで、オブジェクトの生成、取得処理を効率化されるためによく使われている手法です。
プールを使用する場合、基本的にプールからオブジェクトを取得します。
そしてプールの中に使用可能なオブジェクトが存在する場合はそれを渡し、存在しない場合のみオブジェクトを新規生成しそれを渡します。
また、使用終了したオブジェクトをプールに返すことによって、可能な限りオブジェクトのインスタンス化する処理を省くというものです。

例えば、マシンガンで1万発弾を打つ場合、本来なら1万個のオブジェクト生成が必要ですが、0.01秒に弾を一発発射し、0.1秒で着弾(使用終了)する場合、オブジェクトの生成回数は10回のみで済みます。
そうすることで処理は高速化されますし、シーン上に無駄にオブジェクトを増やさなくて済むことになります。

オブジェクトをプールする方法について。

今回実装した内容は、以下のような内容になっております。

  1. PoolableObjectを設定したPrefabを作成する。
  2. Poolインスタンスを保持したクラスを用意する。
    • 今回はManagerクラスという名前ということにしておく。
  3. ManagerクラスとPoolableObjectを設定したPrefabを紐付ける。
    • SerializeFieldPrefabを設定するか、Resources.LoadPrefabを取得する想定です。
  4. Pool生成時にPoolableObjectを設定。
  5. Pool.GetOrCreate()という関数を呼び出せば自動的に設定したPoolableObjectが取得、または生成される。

ちょっと3番のPoolPrefabの紐づけが手動で煩わしいけれど、プールしたい物体なら毎回Prefabを設定するだろうということで今回の実装となっています。

サンプルコード

0.1秒ごとにオブジェクトをプールから取得し、1.0秒毎にオブジェクトをプールに返すサンプル。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
/// <summary>
/// Sample pool manager.
/// </summary>
public class SamplePoolManager : MonoBehaviour
{
    private const float SpawnDelay = 0.1f;
    private const float DestroyDelay = 1.0f;
 
    [SerializeField]
    private PoolableObject poolableObject;
    private Pool pool;
 
    public void Start()
    {
        pool = new Pool(poolableObject);
        StartCoroutine(RepeatSpawnPoolableObject(SpawnDelay));
    }
 
    /// <summary>
    /// Repeats the spawn poolable object.
    /// </summary>
    /// <returns>The spawn poolable object.</returns>
    /// <param name="_delay">Delay.</param>
    private IEnumerator RepeatSpawnPoolableObject(float _delay)
    {
        yield return new WaitForSeconds(_delay);
 
        PoolableObject obj = pool.GetOrCreate(this.transform);
 
        float randX = Random.Range(0.0f, 1.0f);
        float randY = Random.Range(0.0f, 1.0f);
        float randZ = Random.Range(0.0f, 1.0f);
        obj.GetComponent<Rigidbody>().velocity = new Vector3(randX, randY, randZ);
        obj.transform.localPosition = Vector3.zero;
 
        obj.Return2Pool(DestroyDelay);
 
        StartCoroutine(RepeatSpawnPoolableObject(_delay));
    }
}

実行サンプル


実際のコード

プールクラス。

オブジェクトのプールを行う場合、こちらのインスタンスが1つ必要。
インスタンスが複数存在してしまうと、プールの中身が分散されてしまうので要注意。

基本的にGetOrCreateReturn2Poolだけで使えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
/// <summary>
/// Class for basic pooling.
/// Will need to be used with PoolableObject.
/// </summary>
public class Pool
{
 
    private const int PoolSizeNoLimit = -1;
 
    // Object to be pooled.
    private readonly PoolableObject poolableObject;
    private readonly Queue<PoolableObject> pool;
 
    /// <summary>
    /// Initializes a new instance of the <see cref="Pool"/> class.
    /// </summary>
    /// <param name="_poolableObject">Poolable object.</param>
    /// <param name="_maxPoolSize">Max pool size.</param>
    public Pool(PoolableObject _poolableObject, int _maxPoolSize = PoolSizeNoLimit)
    {
        poolableObject = _poolableObject;
        pool = _maxPoolSize == PoolSizeNoLimit
            ? new Queue<PoolableObject>()
            : new Queue<PoolableObject>(_maxPoolSize);
    }
 
    /// <summary>
    /// Gets the pool instance from queue or creates new pool instance.
    /// </summary>
    /// <returns>The or create.</returns>
    /// <param name="_parent">Parent.</param>
    /// <param name="_localPos">Local position.</param>
    public PoolableObject GetOrCreate(Transform _parent, Vector3 _localPos = default(Vector3))
    {
        PoolableObject poolObj = GetPoolableObject(_parent);
        if (poolObj == null) poolObj = CreatePoolableObject(_parent);
 
        // Initialize poolable object, no matter get or create.
        poolObj.gameObject.SetActive(true);
        poolObj.transform.localPosition = _localPos;
 
        return poolObj;
    }
 
    public List<PoolableObject> GetAllObjectsInPool()
    {
        return new List<PoolableObject>(pool);
    }
 
    /// <summary>
    /// Returns pool instance to the pool.
    /// </summary>
    /// <param name="_poolableObject">Poolable object.</param>
    public void Return2Pool(PoolableObject _poolableObject)
    {
        pool.Enqueue(_poolableObject);
        _poolableObject.gameObject.SetActive(false);
    }
 
    /// <summary>
    /// Gets the poolable object from queue.
    /// </summary>
    /// <returns>The poolable object.</returns>
    /// <param name="_parent">Parent.</param>
    private PoolableObject GetPoolableObject(Transform _parent)
    {
        if (!IsPoolObjectAvailable()) return null;
 
        PoolableObject poolObj = pool.Dequeue();
        poolObj.transform.SetParent(_parent);
        return poolObj;
    }
 
    /// <summary>
    /// Creates new poolable object.
    /// </summary>
    /// <returns>The poolable object.</returns>
    /// <param name="_parent">Parent.</param>
    private PoolableObject CreatePoolableObject(Transform _parent)
    {
        PoolableObject poolObj = PoolableObject.Instantiate(this, poolableObject);
        poolObj.transform.SetParent(_parent);
        return poolObj;
    }
 
    /// <summary>
    /// Determines whether reusable pool object is available in pool.
    /// </summary>
    /// <returns><c>true</c> if this instance is pool object available; otherwise, <c>false</c>.</returns>
    private bool IsPoolObjectAvailable()
    {
        return pool.Count > 0;
    }
}

プールしたいオブジェクト用クラス。

基本的にこちらの処理を呼び出す必要はなし。
必要に応じてReturn2Poolを呼び出してあげればOK。
Return2PoolPool、または PoolableObjectのどちらかで1回呼ぶだけでOK。)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
/// <summary>
/// Class for pooling objects.
/// </summary>
public class PoolableObject : MonoBehaviour
{
 
    // Pool where this object is spawned from.
    private Pool pool;
 
    /// <summary>
    /// Instantiate the specified _poolableObject in _pool.
    /// </summary>
    /// <param name="_pool">Pool.</param>
    /// <param name="_poolableObject">Poolable object.</param>
    public static PoolableObject Instantiate(Pool _pool, PoolableObject _poolableObject)
    {
        PoolableObject poolableObject = Instantiate(_poolableObject);
        poolableObject.pool = _pool;
        return poolableObject;
    }
 
    /// <summary>
    /// Returns this poolable object to pool.
    /// </summary>
    public void Return2Pool() {
        pool.Return2Pool(this);
    }
 
    /// <summary>
    /// Returns this poolable object to pool after delay sec.
    /// </summary>
    public void Return2Pool(float _delay)
    {
        StartCoroutine(CoReturn2Pool(_delay));
    }
 
    /// <summary>
    /// Returns this poolable object to pool after delay sec.
    /// </summary>
    private IEnumerator CoReturn2Pool(float _delay)
    {
        yield return new WaitForSeconds(_delay);
        Return2Pool();
    }
 
}
sa

0 件のコメント:

コメントを投稿

技術ブログを始める際に困ったこと。コードの見やすさについて。

以前の記事 で Atom使ってマークダウンで記事書けばすっごい楽! ということを書きましたが、色々試しているうちにどうやらマークダウンだけでは少々問題がありそうだったので、主な問題とその解決方法について書いていきます。 まぁタイ...