更新履歴: 即時無効化した場合
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
は実行されない。
Awake
はMonoBehaviour
の有効/無効の影響を受けないので実行される(ただし実行済みの場合は除く)
テスト用コード
ゲームオブジェクトに貼り付けてインスペクターでチェックボックスをクリック!
見る
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
コンストラクターの使用例
--
以上です。お疲れ様でした。