8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity 6 対応】MonoBehaviour ライフサイクルの実行条件

Last updated at Posted at 2025-01-28

更新履歴: 即時無効化した場合

destroyCancellationToken がキャンセルされない、Awake が実行されないケースが存在する。

ライフサイクルの実行条件

GameObject が一度もアクティブにならなかった場合

MonoBehaviour のホスト GameObject が一度もアクティブにならなかった場合。

  • Awake: 実行されない
  • destroyCancellationToken: キャンセルされない
    • ※ トークン取得時にエラーは出ない。
    • 永遠にキャンセルされることのない CancellationToken をエラーなしで取得出来てしまう点に注意
  • OnDestroy: 実行されない
void CreateObject()
{
    var go = new GameObject();

    // AddComponent より前に SetActive(false) する
    go.SetActive(false);
    go.AddComponent<X>();
}

MonoBehaviour が無効化されている場合

MonoBehaviour が無効(.enabled = false)な状態で GameObject をアクティブにした場合の実行順。

  • Awake
  • --- オブジェクト削除 or シーンの破壊 ---
  • destroyCancellationToken: GameObject, MonoBehaviour の状態に関わらずキャンセルされる
  • OnDestroy: GameObject, MonoBehaviour の状態に関わらず呼ばれる

以下は実行されない

  • OnEnable
  • OnDisable

MonoBehaviour が有効な状態で GameObject を非アクティブにした場合

以下が実行される。

  • OnDisable

フレームを跨がない場合

void CreateObject()
{
    var go = new GameObject();

    go.AddComponent<X>();
    go.SetActive(false);   // AddComponent より後
}

以下の順序で実行される。

  • Awake
  • OnEnable
  • Start: 実行されない
  • OnDisable: 同一フレーム内で OnEnable の後に呼ばれる
  • --- オブジェクト削除 or シーンの破壊 ---
  • destroyCancellationToken: GameObject が非アクティブでもキャンセルされる
  • OnDestroy: GameObject が非アクティブでも呼ばれる

フレームを跨ぐ場合

正確には Update FixedUpdate LateUpdate いずれかが実行される場合。

void CreateObject()
{
    var go = new GameObject();

    go.AddComponent<X>();
    StartCoroutine(DeactivateOnEndOfFrame(go));
}

IEnumerator DeactivateOnEndOfFrame(GameObject go)
{
    yield return new WaitForEndOfFrame();
    go.SetActive(false);
}
  • Awake
  • OnEnable
  • Start: Update シリーズの直前に一度のみ
  • Update
  • LateUpdate
  • (FixedUpdate)
  • --- オブジェクト削除 or シーンの破壊 ---
  • OnDisable
  • destroyCancellationToken
  • OnDestroy

MonoBehaviour が削除された場合

MonoBehaviour そのもの、またはホスト GameObject やシーンに巻き込まれて削除された場合の実行順。

  • OnDisable削除前に MonoBehaviour が有効だった場合は呼ばれる
    • 👉 言い換えると OnEnable が呼ばれていて OnDisable がまだ呼ばれていない場合
  • destroyCancellationToken
  • OnDestroy

追記:即時無効化した場合

同一メソッド内でオンオフ操作を行った場合。

// 1)非アクティブ状態から GameObject をオンオフする
go.SetActive(true);
go.SetActive(false);

// 2)無効化状態から MonoBehaviour をオンオフ
behaviour.enabled = true;
behaviour.enabled = false;

// テスト用の実装
void OnDisable() => Debug.Log("無効化");
void OnEnable()
{
    Debug.Log("有効化");
    StartCoroutine(LogOnEndOfFrame());  // 無効化のタイミングを確認する為
}

static IEnumerator LogOnEndOfFrame()
{
    yield return new WaitForEndOfFrame();
    Debug.Log("フレーム終了");
}
  • OnEnable
  • OnDisable: 同一フレーム内で即時実行され、WaitForEndOfFrame を待たない
  • Update LateUpdate FixedUpdate: 実行されない
  • WaitForEndOfFrame
    • 「1」の場合は GameObject が無効化されているのでコルーチンは発火しない。後で有効化したタイミングで遅れて発火することもない。

前述の「MonoBehaviour が無効化されている場合」に記載の通り、GameObject をアクティブにしても MonoBehaviour が無効化されている場合は OnEnable OnDisable は実行されない

AwakeMonoBehaviour の有効/無効の影響を受けないので実行される(ただし実行済みの場合は除く)

テスト用コード

ゲームオブジェクトに貼り付けてインスペクターでチェックボックスをクリック!

見る
class TestRunner : MonoBehaviour
{
    [SerializeField] bool CreateGO_Deactivate_AddComponent = false;
    [SerializeField] bool CreateGO_AddComponent_DeactivateImmediately = false;
    [SerializeField] bool CreateGO_AddComponent_DeactivateOnEndOfFrame = false;
    [SerializeField] bool CreateGO_in_Update = false;

    int counter = 0;
    public void OnValidate()
    {
        if (CreateGO_Deactivate_AddComponent)
        {
            CreateGO_Deactivate_AddComponent = false;
            var go = new GameObject(nameof(CreateGO_Deactivate_AddComponent) + "\t\t" + counter++);

            go.SetActive(false);
            go.AddComponent<MyTestBehaviour>();
        }

        if (CreateGO_AddComponent_DeactivateImmediately)
        {
            CreateGO_AddComponent_DeactivateImmediately = false;
            var go = new GameObject(nameof(CreateGO_AddComponent_DeactivateImmediately) + "\t\t" + counter++);

            go.AddComponent<MyTestBehaviour>();
            go.SetActive(false);
        }

        if (CreateGO_AddComponent_DeactivateOnEndOfFrame)
        {
            CreateGO_AddComponent_DeactivateOnEndOfFrame = false;
            var go = new GameObject(nameof(CreateGO_AddComponent_DeactivateOnEndOfFrame) + "\t\t" + counter++);

            go.AddComponent<MyTestBehaviour>();
            StartCoroutine(DeactivateOnEndOfFrame(go));
        }
    }

    IEnumerator DeactivateOnEndOfFrame(GameObject go)
    {
        yield return new WaitForEndOfFrame();
        go.SetActive(false);
    }

    void Update()
    {
        if (CreateGO_in_Update)
        {
            CreateGO_in_Update = false;
            var go = new GameObject(nameof(CreateGO_in_Update) + "\t\t" + counter++);

            go.AddComponent<MyTestBehaviour>();
            go.SetActive(false);
        }
    }
}

class MyTestBehaviour : MonoBehaviour
{
    void Awake()
    {
        Debug.Log(nameof(Awake) + "\t\t" + this.name);

        this.destroyCancellationToken.Register(() =>
        {
            Debug.Log(nameof(this.destroyCancellationToken) + "\t\t" + this.name);
        });
    }

    public void OnDestroy() => Debug.Log(nameof(OnDestroy) + "\t\t" + this.name);

    public void OnEnable() => Debug.Log(nameof(OnEnable) + "\t\t" + this.name);
    public void OnDisable() => Debug.Log(nameof(OnDisable) + "\t\t" + this.name);

    public void Start() => Debug.Log(nameof(Start) + "\t\t" + this.name);
    public void Update() => Debug.Log(nameof(Update) + "\t\t" + this.name);
    public void LateUpdate() => Debug.Log(nameof(LateUpdate) + "\t\t" + this.name);
    public void FixedUpdate() => Debug.Log(nameof(FixedUpdate) + "\t\t" + this.name);
}

まとめとオマケ

  • Awake は呼ばれない可能性がある
    • 無駄な処理を省くために GameObject を無効化するなら AddComponent の後または Awake の最後に
  • Awake が呼ばれたら OnDestroy destroyCancellationToken が保証される
  • OnDisable はオブジェクト削除時にも .enabled が真の場合は呼ばれる
    • つまり OnEnable が呼ばれたら OnDisable が保証される

例: 脱 Unity イマジナリーコード

class DatsuUnity : MonoBehaviour
{
    // Unity エディター経由でリストに設定できるように公式がんばって欲しい
    [SerializeReference] List<IAwakable> _onAwakeList;
    [SerializeReference] List<IUpdatable> _onUpdateList;
    [SerializeReference] List<ILateUpdatable> _onLateUpdateList;

    void Awake()
    {
        for (int i = 0, count = _onAwakeList.Count; i < count; i++)
            _onAwakeList[i].OnAwake(this.destroyCancellationToken);  // 非アクティブでもキャンセルされる

        // 無効化は最後に
        this.SetActive(false);  // アップデートマネージャーも兼ねてる場合はやっちゃダメ
    }

    void OnDestroy()  // 非アクティブでも呼ばれる
    {
        for (int i = 0, count = _onAwakeList.Count; i < count; i++)
            if (_onAwakeList[i] is IDisposable d)
                d.Dispose();
    }

    void Update()
    {
        for (int i = 0, count = _onUpdateList.Count; i < count; i++)
            _onUpdateList[i].OnUpdate(this);

        // インターフェイスで実行タイミングを制御せず、どのマネージャーに追加するかで実行タイミングを制御する方式
        for (int i = 0, count = _onFrameList.Count; i < count; i++)
            _onFrameList[i].OnFrame(this);
    }

    void LateUpdate()
    {
        for (int i = 0, count = _onLateUpdateList.Count; i < count; i++)
            _onLateUpdateList[i].OnLateUpdate(this);

        // MonoBehaviour 側が OnFrame をどのサイクルで実行するかを制御する方式の場合、
        // コンポーネント側は追加するマネージャーを切り替えるだけで LateUpdate 呼び出しに変更できる
        for (int i = 0, count = _onFrameList.Count; i < count; i++)
            _onFrameList[i].OnFrame(this);
    }
}

Awake の実行漏れを検出する方法

👇 は一応合法。アクティブだとオッケーで非アクティブの GameObject に追加したときだけエラーが出る。

class AwakeDetectorBehaviour : MonoBehaviour
{
    static readonly List<object> _failsafe = new();

    AwakeDetectorBehaviour()  // 👈 条件が厳しいだけでコンストラクターは普通に使える
    {
        _failsafe.Add(this);
        Debug.Log(this.GetHashCode());  // Debug は使える。.ToString (.name) はアクセス不能
    }

    void Awake()
    {
        _failsafe.Remove(this);
    }

    // デストラクタは思ってるのと違うタイミングで呼ばれるので能動的にリストを見に行ってレポートする
    ~AwakeDetectorBehaviour()
    {
    }

    void OnValidate()
    {
        if (_failsafe.Count != 0)
        {
            var msg = "Something went wrong!! " + string.Join(", ", _failsafe.Select(x => x.GetHashCode()));
            Debug.LogError(msg);
        }
    }
}

Unity 公式の UnityEngine.Object コンストラクターの使用例

--

以上です。お疲れ様でした。

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?