| # Unity Fundamentals |
|
|
| ## MonoBehaviour Lifecycle |
|
|
| Every gameplay script in Unity derives from `MonoBehaviour`. The engine calls |
| a fixed sequence of messages; understanding the order is the difference |
| between code that runs smoothly and code that stutters or races. |
|
|
| ``` |
| Awake() -> OnEnable() -> Start() |
| | |
| v |
| FixedUpdate() (physics, fixed timestep) --+ |
| | |
| Update() (input, gameplay) --+ |
| | |
| LateUpdate() (camera follow, post-sim) --+ |
| | |
| OnDisable() -> OnDestroy() |
| ``` |
|
|
| | Message | When it runs | Typical use | |
| |----------------|-----------------------------------------------|-----------------------------------------------| |
| | `Awake` | Once, when the script instance is loaded | Cache components, set up singletons | |
| | `OnEnable` | Every time the component is enabled | Subscribe to events | |
| | `Start` | Once, before the first Update | Cross-object references (other objects' Awake have run) | |
| | `FixedUpdate` | Fixed timestep (default 0.02 s = 50 Hz) | Rigidbody physics forces | |
| | `Update` | Every rendered frame | Input polling, gameplay logic | |
| | `LateUpdate` | After all Update calls have finished | Camera follow, post-processing | |
| | `OnDisable` | When the component is disabled | Unsubscribe from events | |
| | `OnDestroy` | When the GameObject is destroyed | Cleanup native resources | |
|
|
| > **Gotcha:** `Camera.main` searches the scene by tag every call in 2022.3. |
| > Cache it in `Awake` if you call it more than once per frame. |
|
|
| ## GameObjects and Components |
|
|
| A `GameObject` is an empty container. Behaviour is added through |
| **components**: |
|
|
| ```csharp |
| var go = new GameObject("Enemy"); |
| go.transform.position = Vector3.zero; |
| go.AddComponent<Rigidbody>(); |
| go.AddComponent<CapsuleCollider>(); |
| var ai = go.AddComponent<NPCController>(); |
| ai.player = GameObject.FindGameObjectWithTag("Player").transform; |
| ``` |
|
|
| * Every GameObject always has a `Transform` (or `RectTransform` under a |
| Canvas). You cannot remove it. |
| * Use `GetComponent<T>()` / `GetComponentInChildren<T>()` / |
| `GetComponentInParent<T>()` to reach related components. Cache the result |
| in `Awake` instead of calling it every frame. |
| * `RequireComponent(typeof(T))` makes Unity auto-add the required component |
| when the script is added in the inspector. |
|
|
| ## The Component pattern in practice |
|
|
| ```csharp |
| [RequireComponent(typeof(Rigidbody))] |
| public class PlayerController : MonoBehaviour |
| { |
| private Rigidbody _rb; |
| |
| private void Awake() |
| { |
| _rb = GetComponent<Rigidbody>(); // safe: RequireComponent guarantees it |
| } |
| } |
| ``` |
|
|
| ## Coroutines |
|
|
| Coroutines let you write asynchronous-looking code that runs across multiple |
| frames: |
|
|
| ```csharp |
| private IEnumerator RespawnAfterDelay(float seconds) |
| { |
| yield return new WaitForSeconds(seconds); |
| transform.position = _spawnPoint; |
| } |
| |
| private void OnDeath() => StartCoroutine(RespawnAfterDelay(2f)); |
| ``` |
|
|
| * `yield return null;` waits one frame. |
| * `yield return new WaitForFixedUpdate();` waits until the next physics step. |
| * `yield return new WaitUntil(() => _isGrounded);` waits for a condition. |
|
|
| ## Inspector best practices |
|
|
| * Use `[Header]`, `[Tooltip]`, `[Range]`, `[SerializeField]` to keep the |
| inspector tidy. |
| * Prefer `private` + `[SerializeField]` over `public` for fields that should |
| not be touched by other scripts but should be editable in the inspector. |
| * Use `[System.Serializable]` on structs/classes to make them expandable in |
| the inspector. |
|
|