前回はObjectPool の簡単な解説をしました。
この記事ではUnity標準ライブラリのObjectPool を実際に使ってみましょう。
Unity標準ライブラリのObjectPool
Unityには2021バージョンから標準ライブラリにObjectPool が搭載されています。
しかしながら公式サンプルが少しとっつきづらく、私は3回挫折しました。
公式サンプルはコチラ
https://docs.unity3d.com/2022.1/Documentation/ScriptReference/Pool.ObjectPool_1.html
ここにサンプルのコードが載ってるんですけど、これがまあ初心者にはわかりづらい。
Unityで初めてオブジェクトプールを使いたくなる場面って、大量の敵とか大量の弾を出したいときかと思うのですが、ここのサンプルはパーティクルを出しています。
でもわたし初心者なのでそもそもパーティクルがわからないんです!
しかもスタックとリストとでなんか処理を分けてるみたいです。これもややこしい。
(恥ずかしながらいまだに意味は分かってません)
単純化! 弾のサンプル作りました
さて、いろいろなサイトをめぐってようやく使い方が分かったので、
いちばん利用頻度が高そうなシューティングの弾を例にObjectPool を使ってみましたので紹介します。
今回はスペースを押したら自機から弾が出るというものを作りました。
ショットは自機から直接ではなくショットを管理するクラスから間接的に発生させていて、そこでオブジェクトプールを使いました。
ObjectPoolに渡すメソッドが4つ
実装に先立って、ObjectPoolを使うのに必要な4つのメソッドを説明します。
ObjectPool はコンストラクタに4つのメソッドを指定します。
※ラムダ式(=>記号なやつ)は使わなくてもダイジョブです
- 弾をInstantiateする
- 弾を表示する(再利用といわれることが多い)
- 弾が画面外にいったときに非表示にする(返却といわれることが多い)
- 一時的にInstantiateしすぎて重くなったときDestroyする
これら4つのメソッドはObjectPool のコンストラクタに指定します。
関数名は何でもいいです。
ObjectPool pool = new ObjectPool<GameObject>(
OnCreatePoolObject, // 1つ目
OnTakeFromPool, // 2つ目
OnReturnedToPool, // 3つ目
OnDestroyPoolObject // 4つ目);
※ObjectPoolは引数いっぱいありますが、第5引数以降は無くても動きます
1つ目のメソッド OnCreatePoolObject
ObjectPoolに渡すメソッドの1つ目は弾をInstantiateする部分です。
一番最初に弾を撃つときなどは弾が1つも生成されていないのでInstantiateします。
あるいは、弾をたくさん撃って非アクティブの空きがないときもInstantiateします。
ObjectPoolはオブジェクトを再利用する仕組みではありますが、
リサイクルする品がそもそもないとどうにもならないのです。
// ObjectPool コンストラクタ 1つ目の引数の関数
// プールに空きがないときに新たに生成する処理
// objectPool.Get()のときに呼ばれる
GameObject OnCreatePoolObject()
{
GameObject o = Instantiate(bullet_prefab);
return o;
}
※例のようにGameObjectを戻り値にするメソッドにしてください
2つ目, 3つ目のメソッド OnTakeFromPoolとOnReturnedToPool
2つ目と3つ目はプールに空きがあったと弾を表示する部分と、
画面外に弾が移動して弾をプールに返却するとき弾を非表示にする部分です。
単純にSetActiveを切り替えればいいです。
// ObjectPool コンストラクタ 2つ目の引数の関数
// プールに空きがあったときの処理
// objectPool.Get()のときに呼ばれる
void OnTakeFromPool(GameObject target)
{
target.SetActive(true);
}
// ObjectPool コンストラクタ 3つ目の引数の関数
// プールに返却するときの処理
void OnReturnedToPool(GameObject target)
{
target.SetActive(false);
}
※この2つのメソッドは例のように、戻り値ナシ引数にGameObjectにしてください
4つ目のメソッド OnDestroyPoolObject
4つ目は空きが多すぎたときに消す処理です。
とりあえず何もしなくて大丈夫ですが、何か入れるならDestroyを書いておけばよいです。
// ObjectPool コンストラクタ 4つ目の引数の関数
// MAXサイズより多くなったときに自動で破棄する
void OnDestroyPoolObject(GameObject target)
{
Destroy(target);
}
※瞬間的に大量の弾を出すけど普段は少ないような場合に使います
処理の流れ
ではサンプルで使い方の実例を説明します。
今回は自機から直接ではなく弾管理クラスから間接的に弾をもらう形をとっています。
登場人物は、3つです。
Player:自機、スペースを押すと弾を要求する
ShotManager:要求を受け取って弾を渡す、弾が消えた連絡があったときに非表示にする
Shot:弾、直進して画面外にいったら消えたいと言う
コード例
ではコードを見ていきましょう。
ShotManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
// 自機の弾を管理するクラス
public class ShotManager : MonoBehaviour
{
// 弾のプレハブ
[SerializeField] private GameObject bullet_prefab;
// オブジェクトプール本体
ObjectPool<GameObject> pool;
private void Start()
{
pool = new ObjectPool<GameObject>(
OnCreatePoolObject,
OnTakeFromPool,
OnReturnedToPool,
OnDestroyPoolObject,
false,
2,
5);
}
// ObjectPool コンストラクタ 1つ目の引数の関数
// プールに空きがないときに新たに生成する処理
// objectPool.Get()のときに呼ばれる
GameObject OnCreatePoolObject()
{
GameObject o = Instantiate(bullet_prefab);
return o;
}
// ObjectPool コンストラクタ 2つ目の引数の関数
// プールに空きがあったときの処理
// objectPool.Get()のときに呼ばれる
void OnTakeFromPool(GameObject target)
{
target.SetActive(true);
}
// ObjectPool コンストラクタ 3つ目の引数の関数
// プールに返却するときの処理
void OnReturnedToPool(GameObject target)
{
target.SetActive(false);
}
// ObjectPool コンストラクタ 4つ目の引数の関数
// MAXサイズより多くなったときに自動で破棄する
void OnDestroyPoolObject(GameObject target)
{
Destroy(target);
}
// プレイヤーから呼び出して画面に弾を発生させる
public GameObject GetShot()
{
GameObject o = pool.Get();
return o;
}
// 弾から呼び出して画面から弾を消滅させる
public void DelShot(GameObject o)
{
pool.Release(o);
}
}
オブジェクトプール関連のメソッドはすでに紹介した通りです。
コンストラクタの第5引数以降はとりあえず気にしなくていいと思います。
新しく出てきたのが、GetShotとDelShotです。
GetShotはプレイヤーから呼び出され、ObjectPoolから弾を引き出してプレイヤーにわたしてあげるメソッドです。
DelShotは弾から呼び出して非表示にするメソッドです。どちらも、ObjectPoolにアクセスするための仲介役ですね。
ObjectPoolからオブジェクトをもらうにはObjectPoolのGet()を使います。
第二引数に指定したOnTakeFromPool が呼ばれます。
※空きがなかったときは第一引数のOnCreatePoolObject が先に呼ばれます
逆に非表示にするにはRelease()を使います。
第三引数に指定したOnReturnedToPool が呼ばれます。
次は自機です。
Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 自機
public class Player : MonoBehaviour
{
[SerializeField] private Transform shotpoint;
[SerializeField] private ShotManager shotManager;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
GameObject shot = shotManager.GetShot();
shot.transform.position = shotpoint.transform.position;
}
}
}
スペースキーを押したらショット管理クラスに弾を要求しています。
その後、弾の位置を自機の発射ポイントに変更しています。
最後に弾です。
Shot.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shot : MonoBehaviour
{
[SerializeField]private ShotManager shotManager;
[SerializeField] private float speed;
void Update()
{
if (transform.position.y >= 5)
{
shotManager.DelShot(gameObject);
}
}
private void FixedUpdate()
{
transform.Translate(new Vector3(0, speed * Time.deltaTime, 0));
}
}
Updateでは、画面端にきたらショット管理クラスに知らせて非表示にしてもらっています。
FixedUpdateでは、直進しています。
実演
動かすと下記動画のようになります。
インスペクタに弾がどんどん増えていって、画面外に出ると非アクティブになっているのがわかると思います。
そして、非アクティブになっていたものが急にアクティブになったりします。
これが再利用です。
まとめ
おつかれさまでした。これで説明はおわりです。
UnityのObjectPoolにはオブジェクトを操作する4つのメソッドが必要でした。
- 弾をInstantiateする
- 弾を表示する(再利用といわれることが多い)
- 弾が画面外にいったときに非表示にする(返却といわれることが多い)
- 一時的にInstantiateしすぎて重くなったときDestroyする
これらはObjectPoolのコンストラクタに指定しました。
ObjectPoolから弾をもらうのにはGet()を使い、非表示にするのにはRelease()を使いました。
これらは間接的に指定したメソッドを呼びます。
あとはObjectPoolが空きを探したり無かったら作ったりをしてくれました。便利ですね。
以上でObjectPoolの使い方説明は終わりです。
かわいい我が子にオリジナルアプリを!
コメント