[Unity] ブロック崩しをあのアプリ風にする #15 パフォーマンス改善

前回の出来をスマホで確認したら時が止まりました。



原因


作りながら薄々は気づいていたんですが、物理処理のしすぎでガベージコレクションが発動しました。ガベージコレクションとはメモリ領域が足りなくなった時に自動で不要になった部分を解放してくれる機能です。でもこれが結構重い処理らしく一瞬固まります。

それプラスCanvasの作りすぎですね。パフォーマンス度外視で見つけた手法を手当たり次第実行してきたツケが回ってきました。

Game画面のStatsを押すと色々な状態がみれます。その中にBatches(ドローコール)という項目があります。Update毎に何回描画処理が呼ばれたかです。Canvasは1つごとに1回ドローコルがされます。目安としてモバイルなら100、高くても200を超えてきたら見直しが必要だそうです。


動いていない状態で186です。動き出して、さらに物理衝突と破片をInstantiateした時の絵図はお察しです。左上にチラリと見えるfpsも低すぎます。


ブロック作り直し!



ということで現在のCanvasを内包している作りではどうやっても解決しません。
前回アイテムを作った時はCanvasを内包するのではなくTextMeshを使いました。これと全く同じ方法で作り直します。


ブロックの子要素のCanvasを削除しCreateEmptyから名前をTextへ。そこにTextMesh
をアタッチして設定を上のようにしました。正直Canvas使うよりはるかにラクです。
ただせっかく作ったWaveの中身を入れ替える作業だけは時間がかかりました。計画って大事ですね。
ブロックの構造を変えたのでスクリプトも編集します。ハイライトの部分を変えました。

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

public class Block_life : MonoBehaviour {

 public int life = 2;
 TextMesh numText;
    private Block_Color blockColor;

 void Start(){
  GameObject child = gameObject.transform.Find ("Text").gameObject;
        numText = child.GetComponent<TextMesh>();
  numText.text = life.ToString();
        blockColor = this.GetComponent<Block_Color>();
        blockColor.ChangeColor(life);
 }

 void OnCollisionEnter(Collision col){
        if(col.gameObject.CompareTag("Ball")||col.gameObject.CompareTag("Split")){
   life -= 1;
            blockColor.ChangeColor(life);

  }
  if (life <= 0) {
            Break br = this.GetComponent<Break>(); 
            br.Division();
//三角の時は階層構造が違うので親から削除
            if (transform.parent.gameObject.CompareTag ("Block")) {
                Destroy (transform.parent.gameObject);
            }
        Destroy (gameObject);
        }
  numText.text = life.ToString();
 }

    void OnTriggerEnter(Collider col){
        if (col.gameObject.tag =="Laser") {
            life -= 1;
            blockColor.ChangeColor (life);
        }
        if (life <= 0) {
            Break br = this.GetComponent<Break>();
            br.Division();
            Destroy (gameObject);
        }
        numText.text = life.ToString();
    }
}



Batchesが119まで下がりました。60以上下がってますが破片を生成したりする事を考えるとまだまだ高いです。
というか、Canvas分の描画が消えただけでブロックのバッチングが効いてません。

Frame Debuggerを使う

なぜバッチングがされてないかはFrame Debuggerで確認できるようなのでみてみます。
Window > Frame Debugger でウィンドウが開きます。プレイした状態でEnableを押すと一時停止状態になり、描画処理の流れを見ることができます。上から下に向かって更新されています。


通りがかりに発見。ボールもバッチングされず、一つ一つ描画してます。原因は...


"マテリアルはGPU instancingを有効にしていません" だそうです。マテリアルのインスペクタを探したらその項目があったのでチェックしてみました。



ようやく100を切りました。同じようにブロックにも適用すればもっと減るはず。


変わりませんでした。

どうやら動的に色を変えていることがよくないようです。

色は変える Batchesは増やさない

両方』やらなくっちゃあならないってのが『モバイルアプリ』のつらいところだな。

使用しているマテリアルは全て一緒ですが、ブロックの色はlife値を元に色を書き換えています。GetComponent<Renderer>().color = カラーのような変更の仕方だと元のシェーダーを複製してそれを書き換えるようです。マテリアルが違うとバッチングが効かないのでブロックの数だけBatchがかさみます。

その解決策としてMaterialPropertyBlockというものがあるようです。
とりあえず、Block_Colorスクリプトをこのように書き換えました。


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

public class Block_Color : MonoBehaviour {

    MaterialPropertyBlock mpb;
    void Start(){
        mpb = new MaterialPropertyBlock ();
    }

//Block_lifeが呼ぶ
    public void ChangeColor(int life){
        StartCoroutine("Flush",life);
    }
    IEnumerator Flush(int life){
        bool fla = true;
        while (fla) {
            mpb.SetColor("_Color",new Color(1,1,1));
            GetComponent<Renderer>().SetPropertyBlock(mpb);
            fla = false;
            yield return null;
        }
//R基準の補正 100以上は同じ色 上限を160,下限を60とする
        int r = life > 100 ? 160 : life+60;
        r = 230 - r;
        mpb.SetColor("_Color",new Color(r/255f,(r+30)/255f,(r+30)/255f));
        GetComponent<Renderer>().SetPropertyBlock(mpb);
    }
}

やはりこの辺りを参考にしてみました。
MaterialPropertyBlockを作って情報を登録してRendererにセット。色だけでなくテクスチャなどの情報も行けるようです。便利というか、必須の機能でしょうか。

Bachersはここまで落ちました。


十分落ちたとは思うんですがもう少し改良の余地はあると思います。正直一桁まで落ちて欲しいところですが、とりあえず及第点ではあるのでその辺りは追々やるとして、スクリプトの見直しも行いました。

スクリプト改善

かなり細々とした修正になるので詳細は省きますが、主にこんなところを変えました。
  • FadeOutの半透明時間を短縮
  • ループ内のGetComponent<>をキャッシュ化
  • ループ内のnewを無くす
  • new Color()を使い回す
  • foreachをforに変える
  • シャドウをきる
他にもライトマップを焼くなどありましたが、やってみたところビルドが激重&fpsが一桁のコンボを食らったのでもう少し調べてからにしたいと思います。

結果ここまでサクサクになりました。


Batchesは破片が飛び散った瞬間を撮ったのでこんなもんでしょう。(元は40です。なぜか上がっちゃいました)
効果が大きかったのはCPU使用率です。動いている時ですら改善前より低い数値が出ました。

スマホでも確認しましたがスムーズに動いてくれました。
最適化はまたやるとは思いますが、これで一旦よしとします。


コメント