【Unity】GameObject.SetActive() は重い処理のようだ

Unityで表示・非表示を実装する時にまず思い浮かべるのが「GameObject.SetActive()」です。 UI周りでも比較的頻度が高い処理だと思います。 私も実際のゲームで何の疑問もなく使っていたのですが、これがメモリーを圧迫する(処理落ち)の原因になるという話を最近聞いたので、少し調べてみました。

GameObject.SetActive()

とりあえず、Textを100個用意して「GameObject.SetActive()」で点灯をさせてみます。 [c] private bool stop = true;

private IEnumerator UpdateCoroutine(GameObject text) { var delayTime = new WaitForSeconds(1.0f); while (stop) { text.gameObject.SetActive(false); yield return delayTime; text.gameObject.SetActive(true); yield return delayTime; } }

[/c]

これをUnityの「Profiler」を使って測定します。

なお、私はこれまで「Profiler」を含めてのパフォーマンス・チューニングをしてきていなかったので、いろいろなサイトを見て挑戦をしてみました。 CPUプロファイラでパフォーマンスを改善する - 前編 CPUプロファイラでパフォーマンスを改善する - 後編

CPU Usage

まずは「CPU Usage」です。 特徴として「GameObject.SetActive()」をtureにした時に「CG Alloc」が増大して負荷がましていました。

2019 1 30 2

2019 1 30 1

GC Alloc

ガベージコレクション(GC)を使ってメモリを確保した時に発生します。 具体的にはヒープ領域内にメモリを確保するために、GCが空き領域を検索するコストになります。

ヒープ領域に関してはこちらのサイトが図も含めてとてもわかりやすいです。 メモリ管理 - コンピューターの基礎知識

GC Alloc」が増加しているということから、GameObject.SetActive()を使った時に、再度メモリの割当作業が発生しているということです。 UI の最適化に関するその他のヒントとテクニック

Canvasのリビルドとリパッチ処理

Profilerを見ると、TimeLine上に少し色の違った処理が行われています。 2019 1 30 3

  • UGUI.Rendering.UpdateBatches
  • Canvas.SendWEillRenderCanvased

Canvas上での変更点を修正するために、レイアウトとメッシュの再計算が行われる。これをリビルド処理といいます。 「バッチの処理」とは、CanvasがそのUI要素を表すメッシュを組み合わせ、Unityのグラフィックス・パイプラインに送るための適切なレンダリングコマンドを生成する処理のことです。 上記のスクリプトはこの「リビルド処理」「バッチ処理」になります。 Unity - UIの基礎 設計からパフォーマンスを意識した開発(UI編)

回避方法

通常、実装する場合には「GameObject.SetActive()」を使っても問題ないと思いますが、大量にGameObjectの表示・非表示周りを触る場合には別の方法を模索したほうがよいかもしれません。

  • 移動させる
  • アルファで調整(UGUI)
  • CanvasRenderで調整

移動させる

単純にカメラの外にPositionを移動させて表示させなくする処理です。 見た目を一切触らずに実装ができます。

アルファを調整

UGUIのTextやImageなどColorを調整して表示・非表示を実装します。 [c] var delayTime = new WaitForSeconds(1.0f); var alphaZero = new Color32(0xff, 0xff, 0xff, 0x00); var alphaOne = new Color32(0xff, 0xff, 0xff, 0xff); Text text = gameObject.GetComponent<Text>(); if (text != null) { while (stop) { text.color = alphaZero; yield return delayTime; text.color = alphaOne; yield return delayTime; } } [/c]

こちらの方法だと「GC Alloc」が発生しません。 ただ、「リビルド」「バッチ処理」が発生しています。

CanvasRenderで調整

CanvasRenderを使う場合も「GC Alloc」「リビルド」「バッチ処理」が発生しません。 [c] private IEnumerator UpdateRendererCoroutine(Text gameObject) { var delayTime = new WaitForSeconds(1.0f); var cr = gameObject.GetComponent<CanvasRenderer>(); while (stop) { cr.SetAlpha(0.0f); yield return delayTime; cr.SetAlpha(1.0f); yield return delayTime; } } [/c]

CanvasRenderで調整するのが一番良い方法に思われます。

最後に

タイトルで「GameObject.SetActive()」は重い処理と書きました理由は「GC Alloc」「リビルド」「バッチ処理」が発生するためです。 ただ「重い」と書きましたが、あくまで一度に大量の処理を行った場合になりますので、もし処理落ちなどが起こった場合に別の方法を考えるのがよいと思います。