渋谷ほととぎす通信

完全趣味でやってるUnityメモ。説明できないところを説明できるようにするための個人ブログ。昨日の自分より少しでも大きくなれるように。。。 ※所属団体とは一切関係がありません

Unity SetActiveとenabledどっちを使うべきかの考察


f:id:esakun:20150730215258g:plain:w450

MonoBehaviourを継承したクラスにはAwakeやStartなど、Unityから特定のイベントを受け取れるメソッドを定義することができます。

以下は使用頻度が高い代表的なイベントですが、これらには実行順が決められています。

  1. Awake
  2. OnEnable
  3. Start
  4. Update

これらの関数が実行される条件は以下です。
「GameObject、アタッチされたコンポーネントが共にアクティブ状態の場合」

f:id:esakun:20161027215320p:plain
※アクティブ状態というのはこのようにチェックが入った状態

本記事では前半非アクティブからアクティブに切り替えた場合の実行順をまとめつつ、後半本題の SetActiveとenabledどっちを使うべきかについて言及していきます。

非アクティブの状態からアクティブに切り替えた場合の実行順

前提としてシーンロード時にGameObject、コンポーネント共に非アクティブの状態とします。ちなみにこの状態では、どのイベントも実行されません。

下記パターンごとに実行されるイベントとその順を記載しています。

パターン1. GameObject アクティブ / コンポーネント 非アクティブ
  1. Awake
パターン2. GameObject 非アクティブ / コンポーネント アクティブ

※イベントが呼ばれません

パターン3. GameObject アクティブ / コンポーネント アクティブ
  1. Awake
  2. OnEnable
  3. Start
  4. Update
補足1. パターン1の後、コンポーネント アクティブ
  1. OnEnable
  2. Start
  3. Update

OnEnableから始まりStart, Updateが続きました。

補足2. 補足1.の後、GameObject or コンポーネントを非アクティブにしてアクティブに戻す
  1. OnEnable
  2. Update

※GameObject、コンポーネントともに同じ結果になります。

◆わかったこと

  • Awake、Startイベントはインスタンス生成後1度しか実行されないイベント
  • アクティブの切り替えがトリガーで実行されるイベントはOnEnable
  • GameObject、コンポーネントをどちらか非アクティブにするとUpdateは停止
補足3. パターン3においてStartでコルーチン使った場合

以下のような場合どうなるでしょうか。

ログはこのように出力されます。

Start 1
Update 1
Update 2
Start 2

処理順番としてはStart -> Updateですが、Start関数が遅延実行の場合、Start関数処理実行中にUpdateは処理されることになります。

しかしUpdateが 2回 呼ばれた後、1フレーム待機した Start 2 が実行されることに疑問がわきます。

理由はマニュアルに記載されていました。

yield コルーチンは、次のフレームで Update 関数がすべて呼び出された後に続行します。

正解はこの一文です。

コルーチンは全てのUpdateが終了してから実行されます。
Startメソッド内の コルーチン終了後のStart 2の処理Update処理の後に実行 されているため、2回連続Updateが呼ばれているように見えたというわけですね。

ここまでのまとめ

  • GameObjectが非アクティブの場合はコンポーネントがアクティブになっても何もイベントは走らない
  • Awakeは例外でコンポーネントではなくGameObjectのアクティブがトリガーで1度だけ実行される

少し視点を変えて

非アクティブの状態における関数実行の挙動も確認していきましょう。

1. GameObjectをアクティブ / コンポーネントを非アクティブ

◇関数実行

可能

◇コルーチン実行

可能

2. GameObject 非アクティブ / コンポーネント アクティブ

◇関数実行

可能

◇コルーチン実行

不可(以下のエラーが出力されます)

Coroutine couldn't be started because the the game object '〜〜〜' is inactive!

3. GameObject アクティブ / コンポーネント アクティブ

◇関数実行

可能

◇コルーチン実行

可能

4. GameObject 非アクティブ / コンポーネント 非アクティブ

◇関数実行

可能

◇コルーチン実行

不可(以下のエラーが出力されます)

Coroutine couldn't be started because the the game object '〜〜〜' is inactive!

~~~ にはGameObject名が入ります

まとめ

GameObject、コンポーネントが共に非アクティブ状態でも、関数の実行は可能です。

ただしコルーチンはGameObjectがアクティブではないと実行はされません。

この結果から分かることは、Update等のイベント以外で関数の呼び出しを止めたいからGameObject、コンポーネントを非アクティブにするといったことは全くもって見当違いということです。

もし独自マネージャクラスでUpdateを一括して呼び出しているのであれば、仕様次第ですがマネージャ側が対象インスタンスのアクティブをチェックして実行可否を判断しなければならない場合もあるかもしれません。

public class UpdateManager : MonoBehaviour
{
    private List<IUpdatable> _updateObjList;

    void Update ()
    {
        foreach(var obj in _updateObjList)
        {
            // オブジェクトのアクティブをチェックしてUpdate処理をする必要があるかもしれない
            if (obj.IsActive)
            {
                obj.CustomUpdate();
            }
~~~~~略~~~~~
    }
}

GameObject、コンポーネントのアクティブ切り替えをする主な目的の1つは無駄な描画をさせないためだと思います。

例えばuGUIのImageコンポーネントは、Graphicクラスを継承しており、OnPopulateMesh というイベント処理によって画面にメッシュを描画しています。

このイベントもGameObject、コンポーネントが非アクティブになると実行されないため描画されなくなります。この切替で描画のパフォーマンスチューニングの1つの手段になるかと思います。

結局SetActiveとenabledどっちを使えばよいのか

  • コンポーネント のアクティブ切り替え(enabled)・・・ イベントの実行 / 停止 を基準に判断
  • GameObject のアクティブ切り替え(SetActive)・・・ イベントの実行 / 停止 が基準になるが、コルーチン が絡む場合は使わない。コルーチンを止めたい場合は検討。

という着地です。

とりあえず非アクティブにしたいから何でもかんでもSetActiveを使っていた時代が私にはありましたが、今は上記のことを意識して切り替えています。

環境

  • Unity5.4.2f1

あわせてどうぞ