Raspberlyのブログ

Raspberlyのブログ

Unityネタをメインとした技術系ブログです。にゃんこ大戦争や日常なども。そろそろブログタイトル決めたい

【アセット紹介】PoolManagerでオブジェクトプールを実装する【Unity】

今回はアセットの紹介をしていきます。
紹介するのはPoolManager
大量のオブジェクトを効率よく運用するユーティリティアセットです。

f:id:Raspberly:20201027191929p:plain

 

 

お得なバンドル情報!

現在アセットストアでは、10周年を記念したメガバンドルが発売中です! 終了しました
今回紹介するPoolManagerも含め全20アセットが超お得に手に入るチャンス!
下のバナーリンクからどうぞ。

f:id:Raspberly:20201030030512p:plain

 

 

 

どんなアセット?

大量のオブジェクトをインスタンスする際に活躍する、オブジェクトプールを簡単に実装できるアセットです。
Instantiateしたゲームオブジェクトをプール、再利用することでガベージコレクションを回避し、
パフォーマンス向上が期待できます。

PoolManagerはUnity向けのオリジナルでベストなインスタンスプーリングソリューションで、インスタンスをより効率的に管理してパフォーマンスを向上、シーンヒエラルキーを整理、実装はシンプルです。信じられないようでしたら、わたしたちのウェブサイトやこちらに書かれているレビューをご覧ください。

インスタンスが何度も何度も必要になるのになぜ破棄するのでしょうか?再利用しましょう!PoolManagerは、プリローディングインスタンスなど多くの追加機能で、ゲームプレイとShurikenの自動消去中にインスタンスを作成されないよう、開発を支援してゲームの実行スピードアップを実現します。詳細については以下のリンクをご覧ください。

公式ドキュメントも用意されています。

docs.poolmanager.path-o-logical.com

 

レビューの評価が非常に高く、
2011年の初リリースから現在までアップデートされ続けているため、とても信頼性の高いアセットです。

 

 

 

 

オブジェクトプールとは?

オブジェクトをインスタンス(生成)、プール(溜める)し、
再利用をすることでガベージコレクションを防ぐデザインパターンです。

シューティングゲームで例えると

インスタンスと破棄の場合

弾を撃つたびにInstantiateし、撃ち終わった弾をDestroyすると
メモリの確保と開放が頻繁に起こるため負荷が高くなります。

f:id:Raspberly:20201029210542p:plain

 

オブジェクトプールの場合

オブジェクトプールなら最初にまとめてInstantiateした後、
それらのActiveを切り替え再利用することでメモリ負荷を軽減できます。

f:id:Raspberly:20201029211614p:plain

 

 

オブジェクトプールは複雑な作りではないため自作されている方も多いかと思います。

 

 

 

開発環境

Unity 2020.1.8

PoolManager ver7.0.1

 

 

 

実際に試してみる

オブジェクトプールを使わない場合

まずはオブジェクトプールを使わずに、InstantiateとDestroyで銃を撃つ仕組みを作ってみます。

Bullet

弾はこんな感じ、SphereにRigidbodyとBullet.csをアタッチします。これはプレハブ化しておきます。

f:id:Raspberly:20201030005838p:plain

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{

	[SerializeField] Rigidbody Rb;
	[SerializeField] float m_Speed;
	[SerializeField] float m_AutoDisposeTime = 3;

	//初期化処理
	public void Init(Transform _trn, float _speed)
	{
		transform.position = _trn.position;
		transform.rotation = _trn.rotation;
		m_Speed = _speed;

		Rb.AddForce(transform.forward * _speed);
		StartCoroutine("AutoDispose");
	}

	//終了処理
	public void Dispose()
	{
		Destroy(gameObject);
	}

	//何にも当たらない場合時間経過で消す
	IEnumerator AutoDispose()
	{
		yield return new WaitForSeconds(m_AutoDisposeTime);
		Dispose();
	}

	private void OnTriggerEnter(Collider other)
	{
		StopCoroutine("AutoDispose");
		Dispose();
	}


}

 

銃にはGun.csをアタッチします。銃口となるTransformと先ほど作ったBulletプレハブを紐づけておきます。
見た目はBoxを組み合わせてそれっぽくしました。

f:id:Raspberly:20201030010912p:plain

f:id:Raspberly:20201030010955p:plain

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Gun : MonoBehaviour
{
    [SerializeField] Transform GunPoint;
    [SerializeField] GameObject Bullet;
    [SerializeField] float BulletSpeed = 1000;


    //マウスクリックしたら撃つ
    void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            var obj = Instantiate(Bullet);
            obj.GetComponent<Bullet>().Init(GunPoint, BulletSpeed);
	}
    }
}

この状態でプレイしてみるとマウスの左クリックで弾が出ます。

f:id:Raspberly:20201030012630g:plain

Bulletプレハブが生成され、その後破棄されています。

f:id:Raspberly:20201030032246g:plain

 

 

PoolManagerに置き換える

先ほど作った銃と弾をPoolManagerを使ってオブジェクトプールに置き換えてみます。

SpawnPoolの作成

空のゲームオブジェクトを作成して、SpawnPoolコンポーネントをアタッチします。
このオブジェクトの名前をPoolとしておきます。

f:id:Raspberly:20201030014651p:plain

各パラメータは以下の通りです。

Pool Name : プールへのアクセスに使用する名前

Match Pool Scale : 生成されるオブジェクトをPoolオブジェクトに合わせてスケーリングする
生成するオブジェクトがUIの場合にチェックを入れるとよい

Match Pool Layer : 生成させるオブジェクトのレイヤーを、Poolオブジェクトのレイヤーと合わせる

Don't Reparent : 生成したオブジェクトをPoolオブジェクトの子にしない

Don't Destroy On Load : 生成したオブジェクトのDontDestroyOnLoad(シーン切り替えなどでDestroyされない)を有効にする

Log Messages : PoolManagerが何をしているかログを表示させる

Per-Prefab Pool Options : プールしておくオブジェクトの設定

 

SpawnPoolにBulletの登録

SpawnPoolコンポーネントにBulletを登録して生成させます。
Per-Prefab Pool Optionsを開いて、右の+ボタンを押すとオプション画面が表示されます。

f:id:Raspberly:20201030020510p:plain

prefab : プール対象のプレハブです。今回はBulletを登録します。

preload Amout : 事前にいくつ生成しプールしておくか。

preload Time : 何フレームかけて生成するかと、ディレイの設定。
シーン開始時のスパイクを回避するために、少し遅らせて生成することができます。

f:id:Raspberly:20201030020919p:plain

limit Instances : 生成する数に制限をつけるかどうか
プールしているインスタンス数がこれを超えた場合、SpawnされてもNullを返します。
FIFOを有効にしている場合は、一番古いインスタンスを終了させ即座に使いまわします。

f:id:Raspberly:20201030021219p:plain

cull Despawned : 生成したが使われていないインスタンスを削除するかどうか
下の例の場合、使われていないインスタンスが50以上ある場合、60秒ごとに5づつ削除する。
メモリの問題が発生していない場合は基本的に使わない方がいいらしい

f:id:Raspberly:20201030021845p:plain

log Messages : ログメッセージを出すかどうか

 

 

今回はこのような設定にしました。

f:id:Raspberly:20201030023039p:plain

実行するとBulletが15個生成されプールされているのが確認できます。

f:id:Raspberly:20201030023137p:plain

 

 

 

GunからPoolにアクセスし弾を発射する

スクリプトはBullet.csとGun.csのInstantiateとDestroyの部分を差し替えるだけです。

	//終了処理
	public void Dispose()
	{
		PoolManager.Pools["Pool"].Despawn(transform);
	}
    //マウスクリックしたら撃つ
    void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            var obj = PoolManager.Pools["Pool"].Spawn(Bullet);
            obj.transform.GetComponent<Bullet>().Init(GunPoint, BulletSpeed);
        }
    }

SpawnPoolで生成されたBulletが使われているのが確認できました。

f:id:Raspberly:20201030025552g:plain

 

 

 

その他有効な使用パターン

今回は一番簡単な使い方をしましたが、公式で様々なパターンが紹介されているのでこちらもどうぞ

docs.poolmanager.path-o-logical.com


 

 

 

まとめ

・PoolManagerを使うことで、簡単にオブジェクトプールを作成することができる
・オブジェクトプールは、ガベージコレクションを避けメモリ負荷を抑えることができる
スクリプトは、InstantiateとDestroyをSpawnとDespawnに書き換えるだけ

 

 

 

 

参考資料

qiita.com

nobollel-tech.hatenablog.com

docs.unity3d.com

 

 

 

他のアセットの紹介記事はこちら↓

raspberly.hateblo.jp

 

 

 

※本記事にはAssetStoreアフィリエイトリンクが含まれています。
他、間違っている箇所、わかりにくい所がありましたらコメントにお願いします。