UnityのObjectPool (1 / 2) -オブジェクトプールとは-

Unity解説

Unityでゲームを作っていると1度は目にするObjectPool 、なんだか難しそうですよね。
この記事ではObjectPool を使ってみたいけど二の足を踏んでいる方に向けて、
ObjectPool の考え方とUnity標準ObjectPool APIを使ったシューティングの弾の実例を紹介してObjectPool が使えるように解説します!

使い方実例はコチラ

ObjectPoolとは?

ObjectPoolは高負荷なオブジェクトの生成・消滅の数を減らすための仕組みです。
特にシューティングゲームでよく使います。

ここでは、そもそもなぜObjectPool が必要か、ObjectPool の考え方ををざっくり説明します。

なぜObjectPool が必要なのか

UnityのマニュアルによるとObjectPool の利点はこう記載されています。

Object Pooling is a way to optimize your projects and lower the burden that is placed on the CPU when having to rapidly create and destroy new objects.

https://docs.unity3d.com/2022.1/Documentation/ScriptReference/Pool.ObjectPool_1.html

オブジェクトを作成・破棄するときにかかるCPU使用を軽減するんですね。
これが敵キャラとか木とかなら数も少ないので問題になりませんが、たとえば弾幕シューティングの弾は大量に発生して消えていくのでそのままでは処理が重くなってしまうかもしれません。

普通にInstanceを生成してDestroyをすると、そのたびに重い処理が発生する

ObjectPoolを理解する

前述のとおり、InstantiateとDestroyがたくさんあると処理が重く動作が遅くなってしまいます。
でもいっぱい弾は出したい! では、どうすればよいのでしょうか?
続いてObjectPool を理解するために概念を少しずつ解説します。

InstantiateしたものをDestroyしなければよい

InstantiateとDestroyを減らすにはどうするかというと、インスタンスは一度生成したら消さずに非表示にしておき、必要なときに再表示するようにします。

あらかじめインスタンスを生成しておいて再利用すれば比較的軽い処理で済む

擬似コードでかくとこんなかんじです。

// 弾を発射
if (スペースボタン) {
    bullet.SetActive(true);
    bullet.SetPos(自機.transform.position);
    bullet.SetSpeed(方向、スピード);
}

// 弾が画面端に届いたら非アクティブにする
if (画面外) bullet.SetActive(false);

ただこれだと1発ずつしか撃てないので、複数弾を使うためにbulletを配列で管理する必要があります。

複数の弾を再利用

では、複数の弾を撃つために配列で弾を管理してみます。

GameObject[] bullets = new GameObjects[12];

Start {
    bulletsに12個 に弾をInstantiateしておく
}

Update {
    // リストのすべての弾を調べて画面外のものは非アクティブにする
    foreach (bullet in bullets) {
        if (画面外) bullet.SetActive(false);
    }
    
    // スペースが押されたら、リストの中で非アクティブのオブジェクトを探して使う
    if (スペースボタン) {
        foreach (bullet in bullets) {
            if (!bullet.active) {
                bullet.SetActive(true);
                bullet.SetPos(自機.transform.position);
                bullet.SetSpeed(方向、スピード);
                break;
            }
        }
    }
}

少しややこしくなってきましたが、やっていることは簡単です。
1発のときと発射と消滅の処理は同じで、弾を配列で12個持てるようにしています。
でもまだこれだと問題があります。今回は12個固定で配列を宣言しましたが、

  • 配列が固定なので最大12発しか撃てない
  • 1発しか撃っていないときでも12個分のオブジェクトが確保される
    (たとえば最大10000発撃てるとした場合に10000個のオブジェクトが無駄に確保される)

動的リストにしてみる

ここまでくると結構ややこしいのですがあと一歩です!

  • 何個でも撃てるようにする
  • 使うときにだけオブジェクトを確保する

このために、動的リストを導入します。早速、疑似コードを見てみましょう。

List<GameObject> bullets = new List<GameObjects>();

Update {
    // リストのすべての弾を調べて画面外のものは非アクティブにする
    foreach (bullet in bullets) {
        if (画面外) bullet.SetActive(false);
    }
    
    // スペースが押されたら、リストの中で非アクティブのオブジェクトを探して使う
    if (スペースボタン) {
        GameObject o;
        foreach (bullet in bullets) {
            if (!bullet.active) {
                o = bullet;
                break;
            }
        }
        
        // 空きがなければ新しく作る
        if (o == null) {
            o = Instantiate(prefab);
            bullets.Add(o);
        }
        
        o.SetActive(true);
        o.SetPos(自機.transform.position);
        o.SetSpeed(方向、スピード);
    }
}

動的リスト版は、配列版とコードの違いはあまりありませんが、下記2点が違います。

  • Startで弾オブジェクトを確保しない
  • 代わりに、リストに非アクティブの空きがなければ都度新しいオブジェクトを作る

これにより、配列版で問題になっている大量オブジェクトの確保と最大数の制限が改善されました。

まとめ

お疲れ様です、これでObjectPoolの基本的な部分が終わりました。

まとめると、ObjectPool は、

  • 大量オブジェクトの生成・消滅にかかるCPUを減らす仕組み
  • UnityではInstantiate/Destroyの代わりにSetActiveで制御する

ということでした。

次回はシューティングゲームの弾を発射する例で標準ライブラリのObjectPool の使い方を解説します。

かわいい我が子にオリジナルアプリを!

コメント

  1. […] ObjectPool の簡単な解説はコチラ […]

  2. […] #1 オブジェクトプールとは | coa の開発ブログ […]

タイトルとURLをコピーしました