t1k:unity:base:monobehaviour
| Field | Value |
|---|---|
| Module | base |
| Version | 2.2.2 |
| Effort | high |
| Tools | — |
Keywords: component, lifecycle, MonoBehaviour, unity
How to invoke
Section titled “How to invoke”/t1k:unity:base:monobehaviourUnity MonoBehaviour — Lifecycle, Patterns & Best Practices
Section titled “Unity MonoBehaviour — Lifecycle, Patterns & Best Practices”Core reference for MonoBehaviour-based development in Unity 6 (non-DOTS). For ECS, see dots-ecs.
Lifecycle Order (Single Frame Init)
Section titled “Lifecycle Order (Single Frame Init)”Awake() → Called once, even if disabled. Use for self-referencesOnEnable() → Called when enabled. Use for event subscriptionsStart() → Called once, first frame active. Use for cross-referencesLifecycle Order (Per Frame)
Section titled “Lifecycle Order (Per Frame)”FixedUpdate() → Physics tick (default 50Hz). Rigidbody forces hereUpdate() → Once per frame. Input, game logicLateUpdate() → After all Update(). Camera follow, post-processingLifecycle Order (Teardown)
Section titled “Lifecycle Order (Teardown)”OnDisable() → When disabled. Unsubscribe events hereOnDestroy() → When destroyed. Cleanup native resourcesOnApplicationQuit() → App closingCoroutine Patterns
Section titled “Coroutine Patterns”→ See references/coroutine-patterns.md for all yield options, start/stop patterns, and the UniTask gotcha.
ScriptableObject Patterns
Section titled “ScriptableObject Patterns”[CreateAssetMenu(fileName = "NewWeapon", menuName = "Game/Weapon Data")]public class WeaponData : ScriptableObject { public string weaponName; public int damage; public float cooldown; public AnimationClip attackAnim;}
// Runtime event channel (observer pattern):[CreateAssetMenu(menuName = "Events/Void Event")]public class VoidEventChannel : ScriptableObject { public event System.Action OnEventRaised; public void RaiseEvent() => OnEventRaised?.Invoke();}Best practices: Use SO for shared config data, event channels, enum-like sets. Never store runtime state in SO (it persists in Editor between plays).
Singleton Patterns
Section titled “Singleton Patterns”// Lazy persistent singletonpublic class GameManager : MonoBehaviour { public static GameManager Instance { get; private set; }
void Awake() { if (Instance != null) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); }
void OnDestroy() { if (Instance == this) Instance = null; }}Prefer: ScriptableObject event channels or dependency injection over singletons for testability.
Event Patterns
Section titled “Event Patterns”// C# events (preferred for code-to-code):public event System.Action<int> OnHealthChanged;OnHealthChanged?.Invoke(currentHp);
// UnityEvent (preferred for Inspector wiring):[SerializeField] UnityEvent<int> onHealthChanged;onHealthChanged.Invoke(currentHp);
// Subscribe/unsubscribe in OnEnable/OnDisable:void OnEnable() => player.OnHealthChanged += HandleHealthChanged;void OnDisable() => player.OnHealthChanged -= HandleHealthChanged;Common Gotchas
Section titled “Common Gotchas”- Awake vs Start order: Awake order between objects is undefined. Use Awake for self-init, Start for cross-references
- Null after Destroy: Unity overrides
==operator. Useif (obj)notif (obj != null)for destroyed check. NEVER use??or?.with UnityEngine.Object — they bypass Unity’s null override (C# sees destroyed objects as non-null). Use explicit== nullchecks instead:var c = GetComponent<T>(); if (c == null) c = AddComponent<T>(); - Null check performance:
== nulland!objboth cross managed→native boundary (~5x slower than pure C# check). In hot paths (Update, FixedUpdate, per-frame loops) where destroyed-detection is not needed, useReferenceEquals(obj, null)(explicit intent, hard to misuse) orobj is null(shorter but looks deceptively normal in Unity context). Only use== null/!objwhen you genuinely need destroyed-object detection. - Camera.main is slow: Calls
FindGameObjectWithTag("MainCamera")every invocation. Cache inAwake()/Start():private Camera _mainCam; void Awake() => _mainCam = Camera.main; - GetComponent is slow: Cache results in Awake/Start. Never call in Update
- SendMessage is slow: Use events or direct references instead
- DontDestroyOnLoad duplicates: Always check
if (Instance != null)in Awake - Execution order: Set via Script Execution Order settings or
[DefaultExecutionOrder(N)]attribute - Disabled components:
Awake()still runs.Start(),Update(), etc. do NOT run when disabled - Time.deltaTime in FixedUpdate: Use
Time.fixedDeltaTimeor justTime.deltaTime(auto-adjusts in FixedUpdate) - Static events retain destroyed objects: Always unsubscribe instance methods in
OnDestroy()when subscribing to static events. A stale delegate can call into a destroyedMonoBehaviour; guard beforeStartCoroutine()if the event can fire during teardown or across no-domain-reload play sessions. - Per-frame methods can beat lazy init: If
Update()reads fields initialized only by a lazy singleton getter or externalInit(), initialize safe state inline/Awake too. Scene objects can receiveUpdate()before any other script has touchedClass.inst. - Byte-backed enum unboxing — use
Convert.ToInt32, NOT(int): When reading an enum value out ofobject/SerializedProperty.boxedValue/FieldInfo.GetValueand the enum’s underlying type isbyte/sbyte/short/ushort/long/ulong(any non-intintegral backing), the direct cast(int)boxedValuethrowsInvalidCastException: Specified cast is not validbecause C# unbox + numeric cast require the boxed type to match exactly. UseSystem.Convert.ToInt32(boxedValue)instead — handles all integral backings uniformly viaIConvertible. Concrete example: anEquipSlotType : byte(used in DOTS inventory) round-trips fine through reflection write but fails on read with(int)fv. Pattern:case SerializedPropertyType.Enum: prop.enumValueIndex = System.Convert.ToInt32(fv);— never(int)fv. Same rule for any[Serializable]reflective populator (ApplyStructFields,CopySerializedValues, ScriptableObject editor inspectors) that walks enum fields generically.
Related Skills & Agents
Section titled “Related Skills & Agents”dots-ecs— DOTS/ECS alternative (usedots-implementeragent)unity-input-system— Player input handlingunity-scene-management— Scene lifecycleunity-animation— Animator integrationunity-code-conventions— C# naming rules