更新履歴: 即時無効化した場合
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 の状態に関わらず呼ばれる
以下は実行されない。
OnEnableOnDisable
MonoBehaviour が有効な状態で GameObject を非アクティブにした場合
以下が実行される。
OnDisable
フレームを跨がない場合
void CreateObject()
{
var go = new GameObject();
go.AddComponent<X>();
go.SetActive(false); // AddComponent より後
}
以下の順序で実行される。
AwakeOnEnable- ※
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);
}
AwakeOnEnable-
Start: Update シリーズの直前に一度のみ UpdateLateUpdate- (
FixedUpdate) - --- オブジェクト削除 or シーンの破壊 ---
OnDisabledestroyCancellationTokenOnDestroy
MonoBehaviour が削除された場合
MonoBehaviour そのもの、またはホスト GameObject やシーンに巻き込まれて削除された場合の実行順。
-
OnDisable: 削除前に MonoBehaviour が有効だった場合は呼ばれる- 👉 言い換えると
OnEnableが呼ばれていてOnDisableがまだ呼ばれていない場合
- 👉 言い換えると
destroyCancellationTokenOnDestroy
追記:即時無効化した場合
同一メソッド内でオンオフ操作を行った場合。
// 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 を待たない - ※
UpdateLateUpdateFixedUpdate: 実行されない -
WaitForEndOfFrame- 「1」の場合は GameObject が無効化されているのでコルーチンは発火しない。後で有効化したタイミングで遅れて発火することもない。
前述の「MonoBehaviour が無効化されている場合」に記載の通り、
GameObjectをアクティブにしてもMonoBehaviourが無効化されている場合はOnEnableOnDisableは実行されない。
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が呼ばれたらOnDestroydestroyCancellationTokenが保証される -
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 コンストラクターの使用例
--
以上です。お疲れ様でした。