前回は Unity で 100 枚の「桜の花びら」オブジェクトをそれぞれ異なる横幅で移動させながら舞うアニメーションを作りました。
ここからはゲーム開始時の 100 枚だけではなく、プレイ中、ずっと花びらが舞い落ちるようにしていきます。
※ Unity は 2021.3.14f1、 OS は Windows 10 です。

いろいろな横移動距離でランダムな配置から花びらが舞い落ちてきました。
実装のイメージ
ずっと花びらが舞い落ちるようにするには、常に上画面の方で花びらを作り続ければ良いです。
それだけだと、どんどん花びらのオブジェクトが増え続けてしまうので、画面下に消えたらオブジェクトを破棄します。
花びらを画面上に新しく作るタイミングは、画面下に花びらが消えてオブジェクトを破棄したときにします。
そうすることで、一定数の花びらが常に画面上から下方向に舞い落ちるようになります。

桜の花びらの生成と破棄のサイクルのイメージ
ここで心配なのは、オブジェクトの生成と破棄には多めの処理時間がかかるということです。
少量のオブジェクトならば無視できますが、大量のオブジェクトを使う予定の場合は配慮すべきコストです。
大量のオブジェクトの生成と破棄をする場合 ObjectPool という仕組みを使うことで、その処理時間を軽減します。
ObjectPool とは
ObjectPool は Pool(プール) という言葉のとおり、オブジェクトをためておく仕組みです。
オブジェクトを作って、いらなくなったら消すのではなく、再利用目的でためておくことで、生成と破棄の処理自体を減らします。

今回は Unity の標準機能の ObjectPool を使い「桜の花びら」ゲームオブジェクトを管理します。
Unity ObjectPool の使い方
以前に作った「桜の花びら」たちを管理する Manager クラスのプログラムに ObjectPool をメンバ変数として追加して使います。
Unity の ObjectPool はテンプレート引数を持つテンプレートクラスです。
ObjectPool ではテンプレート引数に、管理するオブジェクトの型を指定します。
今回は「桜の花びら」を管理するので、それに紐づけたクラス SakuraNoHanabira をテンプレート引数に割り当て ObjectPool<SakuraNoHanabira> という型で扱います。
※命名規則としてローマ字を使った名前は勧められていないので、英語の名前を使うようにしましょう(自戒)。
※テンプレートクラスの詳細は「ジェネリック クラス – C# プログラミング ガイド | Microsoft Learn」をご覧ください。
テンプレート引数は決まりました。
次にそのテンプレート引数で指定したクラスがどのようにオブジェクトの生成や破棄を行うか教える必要があります。
例えば、「オブジェクトを生成するときはこの UnityEvent を呼び出す」、「オブジェクトをくださいと言われたときはこの Action を呼び出す」といった風にです。
※ UnityEvent も Action も呼び出してほしい関数をまとめたものです。
ObjectPool を生成するソースコード
「桜の花びら」たちを管理する Manager クラスの Start イベント関数で ObjectPool<SakuraNoHanabira> オブジェクトを生成し、メンバ変数に持つようにします。
コンストラクタの引数には、前述した具体的な生成や破棄の処理を定義した関数を渡します。
※ ObjectPool は UnityEngine.Pool 名前空間に定義されています。以下のプログラムは事前に using UnityEngine.Pool; でその名前空間を省略しています。
void Start()
{
// スタック形式のオブジェクトプールを作成します。各種イベントハンドラを引数で指定しています。
// プール生成とともに defaultCapacity で指定した個数の「桜の花びら」ゲームオブジェクトが作成されます。
pool = new ObjectPool<SakuraNoHanabira>
(
// オブジェクトを生成するイベントです。
createFunc: () =>
{
Debug.Log("createFunc Called.");
// Prefab のインスタンスを作成します。位置は actionOnGet で取得される直前にランダムに設定します。
GameObject instance = Instantiate(Prefab, Vector3.zero, Quaternion.identity);
return instance.GetComponent();
},
// 使うオブジェクトを取得するイベントです。
actionOnGet : (SakuraNoHanabira hanabira) =>
{
Debug.Log("actionOnGet Called." + hanabira.name);
Vector3 pos = new Vector3();
// 出現場所(固定)を設定します。
pos.x = RespawnArea.x + Random.Range(0f, RespawnArea.width);
pos.y = RespawnArea.y + Random.Range(0f, RespawnArea.height);
pos.z = 0f;
hanabira.transform.position = pos;
// 有効にします。
hanabira.gameObject.SetActive(true);
},
// 使い終わったオブジェクトを戻すイベントです。
actionOnRelease : (SakuraNoHanabira hanabira) =>
{
Debug.Log("actionOnRelease Called." + hanabira.name);
hanabira.gameObject.SetActive(false);
},
// オブジェクトを破棄するイベントです。
actionOnDestroy : (SakuraNoHanabira hanabira) =>
{
Debug.Log("actionOnDestroy Called." + hanabira.name);
GameObject.Destroy(hanabira.gameObject);
},
collectionCheck : true,
defaultCapacity : 100,
maxSize : 200
);
}
pool = new ObjectPool<SakuraNoHanabira>(…); は、クラスからオブジェクトを生成するおなじみの部分です。
その引数である…の部分には UnityEvent や Action を引数として渡しています。
「XXXのときはこの関数を呼び出す」 の関数の部分です。
いちいち関数を定義して、それを引数として渡すのも面倒なので、引数の部分でラムダ式(無名関数)を使って定義し引数として渡しています。
ラムダ式の前にある「createFunc : 」は名前付き引数で ObjectPool のコンストラクタで宣言されている createFunc 仮引数に対応する引数を割り当てることを表しています。
ソースコードのコメントにあるように
- createFunc はオブジェクトを生成する処理
- actionOnGet はオブジェクトを要求されてプールから外部に渡すときの処理
- actionOnRelease は使い終わったオブジェクトをプールに戻すときの処理
- actionOnDestroy はオブジェクトを破棄する処理
です。
そのほかの名前付き引数は、
- collectionCheck が true の場合、プールに戻したオブジェクトについて再びプールに戻そうとした時にエラーで知らせます。
- defaultCapacity は初期のオブジェクトの許容数です。
- maxCapacity はオブジェクトの許容数の最大です。
です。
これで、「桜の花びら」をためておく ObjectPool<SakuraNoHanabira> のオブジェクトが作られました。
説明が長くなってしまったので、続きは次回にします。
コメント