初心者がUnityでなんかしちゃうぞBlog

Unity初心者は、ゲーム開発の夢を見るか?

Instantiateするならば、一度は夢見るキャッシングという言葉

時代はエコ。それはUnity業界にも

今回作るのはこんな感じのものです。 クリックするとInstantiateされるのですが、ヒエラルキーを見るとひたすら新規にクローンを作るわけでなく非アクティブのものがあれば再利用していることがわかります。

f:id:yuu9048:20190414195538g:plain

Instantiate連発は避けたい事情

Instantiate を使ってゲームオブジェクトのクローンを作成するのはゲームを作っているとよくありますね。 たとえば、シューティングゲームで自機から発射する弾などは入力があったらInstantiateを行って弾を生成して飛ばす、なんていう処理をしますよね

もちろんひたすらクローンを作る処理でも動くは動きます。しかし無秩序にInstantiateを連発するのはあまりよろしくありません。 入力があるたびにクローンを作っていては、それこそドラえもんバイバインのように無限に増え続けていく恐ろしい事態になってしまいます。いずれは深刻な宇宙の栗まんじゅう汚染が起こってしまうでしょう。

f:id:yuu9048:20190414192251j:plain

オブジェクト数の問題だけならひたすらDestroyすれば良いという話になるのですが、Instantiate自体が重い処理なので再利用できるならそっちを使おうっていう感じの考え方ですね!

解決方法はいろいろある

生成数を最初からキメておく

それを解決するためのアプローチとしては、「作成したオブジェクトを可能な限り再利用する」ということが考えられます。 まず、先程のシューティングゲームであれば「プレイヤーが発射できる弾は10発まで」と明示的に決めてしまう方法が考えられます。 この場合であればまず最初に10回Instantiateで生成して確保しておけばあとはInstantiateをしないでひたすら使い回せばOKです。

動的に生成量を管理する

それ以外に、「入力量に応じて動的に確保数を決定する」という考え方も可能です。 これは、たとえばプレイヤーが20回発射したとします。まず初回はオブジェクトが無いのでInstantiateで1個作ります。このときこのオブジェクトをListなどに入れて管理します。 2発目を発射する際には、Listに入れて管理しているオブジェクトをチェックして「再利用可能」かどうかをチェックします。再利用可能であれば最初に作ったものを再利用し、再利用可能でなければ新たにInstantiateして作成します。

再利用可能かどうかというのは、フラグをもたせて管理するのが良いでしょう。たとえばシューティングゲームだったら「敵にあたった弾」や「画面外に出た弾」などはもう使用済みですよね。非表示にして再利用待機状態にまわしておき、再び発射するときにこの弾を再利用します。

とりあえずサンプルを書いてみよう

今回は、後者のListを使った動的なキャッシングに挑戦します。といってもサンプルプログラムはCubeをただ落とすだけのものです。

1. PrefabのCubeの準備

まずは、Unityプロジェクトを作成し「Cube」を作ります。 作成した「Cube」に「RigidBody」をアタッチしておきましょう。 また、下記「CubeController.cs」をC#Scriptで作成しCubeにアタッチしておきます。

CubeController.cs

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

public class CubeController : MonoBehaviour
{
    void Update()
    {
        if(gameObject.transform.position.y <= -10) {
            GetComponent<Rigidbody>().isKinematic = true;
            gameObject.SetActive(false);
        }
        
    }
}

y座標が-10以下になったら、Kinematicかつ、自身を非表示にするだけのシンプルな動きです。

あとは、このCubeをPrefab化しておきます。

2. Cubeを作成するスクリプトの作成

下記コードでスクリプトを作り、適当な空のゲームオブジェクトにでもアタッチしておきます。

Test.cs

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

public class Test : MonoBehaviour
{
    [SerializeField] GameObject prefab;
    List<GameObject> cubeMaster = new List<GameObject>();

    // Update is called once per frame
    void Update()
    {

        if (Input.GetMouseButtonDown(0)) {

            bool spawnFlag = true;

            // リストに登録されているならチェック
            if(cubeMaster.Count != 0) {

                // リストの中身をすべてチェック
                for(int i=0; i<cubeMaster.Count; i++) {

                    // 非アクティブなら再利用
                    if (!cubeMaster[i].activeInHierarchy) {
                        cubeMaster[i].transform.position = Vector3.zero;
                        cubeMaster[i].GetComponent<Rigidbody>().isKinematic = false;
                        cubeMaster[i].SetActive(true);
                        spawnFlag = false;
                        break;
                    }
                }
            }

            if (spawnFlag) {
                var obj = Instantiate(prefab) as GameObject;
                cubeMaster.Add(obj);
            }
        }

    }
}

画面をクリックするとPrefabを作るというだけのシンプルなものです。 ヒエラルキーからPrefabにさきほどのCubeのPrefabをアタッチしておきましょう。

3. 実行してみよう

f:id:yuu9048:20190414195538g:plain

ヒエラルキーに注目してもらうと、クリックしたときに非アクティブのCubeがあれば再利用していることがわかります。動画の後半ではクリック数を増やしていますが、再利用可能なタイミングであればクローンを作らずに再利用してInstantiateを避けるようになっています。

Listに再利用可能なものがない場合は新しく作成していますが、それも含めてListに追加するので一時的に連打した場合でもその後は再利用可能なオブジェクトのキャパが増えるのでよっぽど連打しない限りはInstantiateを避けて再利用するようになります。

このようにユーザー側の入力にあわせてキャッシュするオブジェクトの数が変わるのが、動的なキャッシングです。

4. 結論

このようにキャッシュして再利用することを意識することで、無駄な処理を防ぐことができます。今回は非アクティブなら再利用というやり方になっていますが、内部にbool型で再利用可能かどうかのフラグをもたせてそれで管理するのでも良いでしょう。 Instantiateを乱発すると処理も重くなってしまいますので、再利用できそうな処理であればキャッシュして使っていくやり方をぜひ試してみましょう!