【Unity】Unity標準のオブジェクトプールを使用する
Unityには標準でオブジェクトプールが使えるようにUnity2021.1からなっています。
これを使って動作を軽く出来たら良いなと思って使い方を色々調べていたのですが公式のリファレンスがパーティクルを使った解説なのもあってとても解りにくかったんですよね。色々と解説して下さっているサイトさんを見て何とか形にはなったので忘備録として書いておきます。
このnoteを書くにあたって下記サイト様の記事を参考にさせて頂きました。この場を借りてお礼申し上げます。とても解りやすく書かれています。
シューティングゲームで使いたいので、自機の弾をオブジェクトプールで管理出来るようにサンプルを作ってみました。
まずオブジェクトプールを使うにあたって必要なゲームオブジェクトは
①オブジェクトプールを管理する本体
②自機
③自機弾(Prefab)
の3つのゲームオブジェクトが必要です。
①オブジェクトプールを管理する本体
Unityには標準でオブジェクトプールを使う仕組みが実装されているのでそれを使います。
スクリプトはこんな感じ
using UnityEngine;
using UnityEngine.Pool;
public class Pool_Manager : MonoBehaviour
{
public ObjectPool<GameObject> pool;//オブジェクトプール本体
[SerializeField] private GameObject player_shot_prefab;//ショットのprefab
public int shot_max;//ショット最大数(CountActiveで制限しないと最大数を超えてInstantiateする)
private void Start()//オブジェクトプール初期化
{
shot_max = 20;
pool = new ObjectPool<GameObject>(
createFunc: OnCreateObject,//第1関数:プールにオブジェクトがない場合オブジェクト生成(Instantiate)する
actionOnGet: OnGetObject,//第2関数:プールに使用していないオブジェクトがある場合はプールから出す。SetActive(true)する
actionOnRelease: OnReturnedObject,//第3関数:プールに返却する
actionOnDestroy: OnDestroyObject,//第4関数:プールの許容量を超えた時にオブジェクトを削除する
collectionCheck: false,//既にプールにあるオブジェトを追加した場合に例外とするか。エディタでのみ実行される
defaultCapacity: shot_max,//初期のプールサイズ
maxSize: shot_max//最大プールサイズ
);
}
void Update()
{
Application.targetFrameRate = 60;//FPS60を維持
shot_max = pool.CountActive;//プールにある使用しているオブジェクトの最大数
}
//第1関数
//プールにオブジェクトがない場合オブジェクト生成(Instantiate)する
//Get()の時に呼ばれる
GameObject OnCreateObject()
{
GameObject obj = Instantiate(player_shot_prefab);//ショットを生成
return obj;
}
//第2関数
//プールに使用していないオブジェクトがある場合はプールから出す。SetActive(true)する
// Get()の時に呼ばれる
void OnGetObject(GameObject obj)
{
obj.SetActive(true);//ショットを再利用
}
//第3関数
//プールに返却する
void OnReturnedObject(GameObject obj)
{
obj.SetActive(false);
}
//第4関数
//プールの最大許容量を超えた時にオブジェクトを自動で削除する
void OnDestroyObject(GameObject obj)
{
Destroy(obj);
}
}
とりあえず必要最低限のものだけ書いてみました。
まずは先頭にusing UnityEngine.Pool;の記述が必要です。
オブジェクトプールを使うにあたってObjectPoolに4つの関数を自分で作る必要があります。
第1が生成
第2が再利用
第3が返却
第4が削除
となってます。第4以降もありますが最初は気にしなくて良いと思います。
private void Start()//オブジェクトプール初期化
{
shot_max = 20;
pool = new ObjectPool<GameObject>(
createFunc: OnCreateObject,//第1関数:プールにオブジェクトがない場合オブジェクト生成(Instantiate)する
actionOnGet: OnGetObject,//第2関数:プールに使用していないオブジェクトがある場合はプールから出す。SetActive(true)する
actionOnRelease: OnReturnedObject,//第3関数:プールに返却する
actionOnDestroy: OnDestroyObject,//第4関数:プールの許容量を超えた時にオブジェクトを削除する
collectionCheck: false,//既にプールにあるオブジェトを追加した場合に例外とするか。エディタでのみ実行される
defaultCapacity: shot_max,//初期のプールサイズ
maxSize: shot_max//最大プールサイズ
);
}
こんな感じで関数を作ります。関数名は何でも良いです。
//第1関数
//プールにオブジェクトがない場合オブジェクト生成(Instantiate)する
//Get()の時に呼ばれる
GameObject OnCreateObject()
{
GameObject obj = Instantiate(player_shot_prefab);//ショットを生成
return obj;
}
第1関数はヒエラルキーにオブジェクトが無い場合に生成(Instantiate)します。もしくは設定したオブジェクト最大数を超えていた場合も生成します。
ObjectPool.Get();命令を出した時に「プールにオブジェクトがない場合もしくはオブジェクト最大数を超えていた場合」呼ばれます。
//第2関数
//プールに使用していないオブジェクトがある場合はプールから出す。SetActive(true)する
// Get()の時に呼ばれる
void OnGetObject(GameObject obj)
{
obj.SetActive(true);//ショットを再利用
}
第2関数はヒエラルキーに使用していないオブジェクトある時にプールから取り出します。再利用とも言われるようです。
ObjectPool.Get();命令を出した時に「プールに使用していないオブジェクトがある場合」呼ばれます。
//第3関数
//プールに返却する
void OnReturnedObject(GameObject obj)
{
obj.SetActive(false);
}
第3関数は使用中のオブジェクトが画面外に出た時などにプールに返却します。Destroyの代わりですね。
//第4関数
//プールの最大許容量を超えた時にオブジェクトを自動で削除する
void OnDestroyObject(GameObject obj)
{
Destroy(obj);
}
第4関数はオブジェクト最大数を超えて生成していたオブジェクトを自動で削除します。
と言った感じです。通常オブジェクトプールを実装しようと思ったらリスト作ったりして結構大変なのですがUnity標準だとこの関数を書いてしまうだけで殆ど出来たようなものなので有難いですね。
②自機
自機からはスペースキーを押したら自機弾を発射するようにしてみます。
using UnityEngine;
public class player : MonoBehaviour
{
[SerializeField] private Pool_Manager poolManager;//Pool_Manager参照用(インスペクタで指定する)
private float pxv;//自機横座標
private float rad;//自機移動角度
private float ds;//自機移動速度
private int time;//経過時間
private void Update()
{
ds = 0;//自機移動速度
if (Input.GetKey(KeyCode.RightArrow))//右移動
{
rad = 0f;
ds = 0.3f;
}
if (Input.GetKey(KeyCode.LeftArrow))//左移動
{
rad = 3.14f;
ds = 0.3f;
}
Vector3 pos = transform.position;
pxv = Mathf.Cos(rad) * ds;//横移動
pos.x = pos.x + pxv;//横移動
pos.z = 0f;
transform.position = pos;
if (Input.GetKey(KeyCode.Space) && time % 2 == 0)//自機弾発射処理
{
//if (poolManager.shot_max <= 20)//コレを使うと自機弾最大数を設定できる
{
GameObject obj = poolManager.pool.Get();//Get()でショットをプールから取り出す
obj.transform.position = pos;//ショットの初期位置
player_shot shot = obj.GetComponent<player_shot>();//rad,dsに値を代入する
shot.rad = Random.Range(1.14f, 2f);//ショットの移動方向
shot.ds = 0.5f;////ショットの速度
}
}
time++;
}
オブジェクトプールを管理しているPool_Managerを参照出来るようにインスペクタで指定しておきます。
自機弾発射処理はこんな感じ
if (Input.GetKey(KeyCode.Space) && time % 2 == 0)//自機弾発射処理
{
//if (poolManager.shot_max <= 20)//コレを使うと自機弾最大数を設定できる
{
GameObject obj = poolManager.pool.Get();//Get()でショットをプールから取り出す
obj.transform.position = pos;//ショットの初期位置
player_shot shot = obj.GetComponent<player_shot>();//rad,dsに値を代入する
shot.rad = Random.Range(1.14f, 2f);//ショットの移動方向
shot.ds = 0.5f;////ショットの速度
}
}
スペースキーが押されたらGameObject obj = poolManager.pool.Get();で自機弾を生成もしくは再利用します(~Get();が呼ばれたら生成or再利用)
ここで自機弾の初期位置や発射角度、速度なども設定しています。
③自機弾(Prefab)
pool.Get();が呼ばれたら自機弾が生成か再利用されます。
using UnityEngine;
public class player_shot : MonoBehaviour
{
Pool_Manager Pool_Manager_get;//Pool_Manager参照用(スクリプトで指定する)
GameObject X;
public float rad;//ショットの移動角度
public float ds;//ショットの移動速度
private float txv;//ショットのX移動量
private float tyv;//ショットのY移動量
private int time;//経過時間
// Start is called before the first frame update
void Start()
{
X = GameObject.Find("pool_Manager");//ヒエラルキーのpool_Managerを検索
Pool_Manager_get = X.GetComponent<Pool_Manager>();//スクリプト指定用
time = 0;
}
// Update is called once per frame
void Update()
{
Vector3 pos = transform.position;
txv = Mathf.Cos(rad) * ds;//ショットのX移動量
tyv = Mathf.Sin(rad) * ds;//ショットのY移動量
pos.x = pos.x + txv;//ショットのX座標
pos.y = pos.y + tyv;//ショットのY座標
pos.z = 1f;//ショットのZ座標
transform.position = pos;
transform.rotation = Quaternion.Euler(0, 0, rad * Mathf.Rad2Deg - 90);//ショットの回転表示
if (time >= 50)//一定時間が過ぎたらショット削除
{
time = 0;//ショット時間初期化
Pool_Manager_get.pool.Release(gameObject);//ショットをプールに戻す
}
time++;
}
}
このスクリプトでは50フレーム後に
Pool_Manager_get.pool.Release(gameObject);
で自機弾をプールに戻しています。
プールに戻したオブジェクトは初期化されるわけではない(再利用時はvoid Start()は呼ばれない)ので戻す時に変数なども自分で初期化しておくのが良いと思います。
Unity標準のオブジェクトプールは設定したオブジェクト最大数を超えると自動的に生成してくれるのですが、最大数を制限したい時もあると思います。
最大数を制限したい場合はObjectPool.CountActiveで使用中のオブジェクトを数える事で制限出来ますね。
このオブジェクトプールの方法だと最初にオブジェクトを纏めて確保するのではなく徐々にInstantiateしていくので、Instantiateするのが嫌な場合は最初に最大数分InstantiateしてSetActive(false);しておくと良さそうです。
上記を踏まえて自機と敵が出てきて弾を撃つプログラムを作ってみました。
https://drive.google.com/file/d/1D6u7H_iSK1J8kbdJu7OvRPi8hwluXk4E/view?usp=sharing
使用しているUnityのバージョンは2021.3.15f1です。
左右キーで自機移動、スペースで自機弾発射、敵と自機弾、敵弾と自機で当たり判定を行っています。
自機弾と敵弾を別々のオブジェクトプールで管理してる感じです。
パッケージ化してますのでインポートして使用して下さい。