📝 Lesson 1.2: The Event Function Lifecycle
In Lesson 1.1 you learned that Unity calls into your MonoBehaviour rather than you calling it. This lesson is the map of when: the event functions — Awake, OnEnable, Start, Update, FixedUpdate, LateUpdate, OnDisable, OnDestroy — and the precise order Unity fires them.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Name the core event functions and the order Unity invokes them in
- Choose correctly between
AwakeandStartfor initialization - Explain why physics goes in
FixedUpdateand per-frame logic inUpdate - Use
LateUpdatefor camera follow and other "after everyone moved" work - Understand
OnEnable/OnDisable/OnDestroyfor setup and cleanup - Recognize that these functions are called by signature, not by an interface or
override
Estimated Time: 60 minutes
Project: Instrument a script that logs every lifecycle event, then reason about the exact console order it produces.
In This Lesson
Event Functions, Not Main
A console program has one timeline you control. A Unity game has a player loop that the engine runs many times per second, and at well-defined moments it calls the methods you've defined on your components. Those methods are the event functions (Unity also calls them "messages").
📖 Definition
An event function is a specially-named method (like Update or Start) that Unity calls automatically at a specific point in a component's life. You don't call these yourself and you don't register them — you just define a method with the right name and signature, and the engine finds it.
You already met two of them in Lesson 1.1: Start (for setup) and Update (for per-frame logic). There are more, and knowing which runs when is the difference between code that works and code that mysteriously reads a value before it was set.
💡 The one-line summary: Awake/OnEnable/Start run once at birth, Update/FixedUpdate/LateUpdate run every frame or physics step, and OnDisable/OnDestroy run at death. Get those three phases straight and the rest is detail.
The Lifecycle Order
Here is the sequence for a single component, from the moment it's created to the moment it's destroyed. Unity guarantees this ordering within one component:
once — object created"] --> B["OnEnable()
each time it becomes enabled"] B --> C["Start()
once — before first Update"] C --> D{"Per-frame loop
(repeats every frame)"} D --> E["FixedUpdate()
0, 1, or many times
(physics step)"] E --> F["Update()
once per frame"] F --> G["LateUpdate()
once per frame, after all Updates"] G --> D D -->|"disabled or destroyed"| H["OnDisable()"] H --> I["OnDestroy()
once — object destroyed"] style A fill:#eff6ff,stroke:#3b82f6,stroke-width:2px style C fill:#eff6ff,stroke:#3b82f6,stroke-width:2px style F fill:#e8f5e9,stroke:#4CAF50,stroke-width:2px style I fill:#fdecea,stroke:#c0392b,stroke-width:2px
Two subtleties this diagram captures that trip people up:
FixedUpdatecan run zero, one, or several times per rendered frame — it's tied to the physics clock, not the frame rate.LateUpdatealways runs after every component'sUpdatethat frame — that's what makes it perfect for "react to where things ended up."
⚠️ Watch Out: order across objects isn't guaranteed
Unity guarantees the order of phases within a component, but not the order in which the same function runs across different GameObjects. Object A's Update may run before or after Object B's Update. Never write code that depends on "my Update runs before that other object's Update" — use the initialization split (next section) or explicit references instead. (You can force it via Project Settings → Script Execution Order, but treat that as a last resort.)
Awake vs OnEnable vs Start
All three run near the beginning, but at different moments and for different jobs. Using the wrong one causes the classic "null on the first frame" bug.
| Function | When | Use it for |
|---|---|---|
Awake() | Once, when the object is created — before any Start, even if the component is disabled. | Setting up this object: caching your own components, initializing internal state. No dependence on other objects being ready. |
OnEnable() | Every time the component becomes enabled/active (so it can run many times over a life). | Subscribing to events, resetting on re-activation (pooled objects). Pairs with OnDisable. |
Start() | Once, just before the first Update — after every object's Awake has run. | Logic that depends on other objects already being initialized. |
The key insight is the two-phase startup: Unity runs all Awake calls first, then all Start calls. So anything you set up in Awake is safe to read from any Start. That's how you dodge the cross-object ordering problem.
using UnityEngine;
public class Player : MonoBehaviour
{
private Rigidbody body; // a reference to our own physics component
void Awake()
{
// Set up MYSELF here. This is safe — it doesn't need other objects.
body = GetComponent<Rigidbody>();
}
void Start()
{
// By now every other object's Awake has run, so cross-object lookups are safe.
GameManager.Instance.RegisterPlayer(this);
}
}
✅ Simple rule
Set up yourself in Awake. Talk to others in Start. Subscribe/unsubscribe to events in OnEnable/OnDisable.
⚠️ Important: Remember from Lesson 1.1 — put initialization here, not in a constructor. Unity constructs your component itself, before the GameObject is wired up, so a constructor runs too early and on the wrong thread for most Unity APIs.
Update, FixedUpdate & LateUpdate
These three are the beating heart of gameplay — they run over and over. Choosing the right one is mostly about the clock each one follows.
Update() — once per rendered frame
Runs once every frame, so its rate rises and falls with performance (60 fps → 60 calls/sec; 30 fps → 30). Use it for input polling, timers, non-physics movement, and general game logic. Because frame time varies, multiply movement by Time.deltaTime to stay frame-rate independent:
void Update()
{
// Move 5 units per SECOND regardless of frame rate.
transform.Translate(0f, 0f, 5f * Time.deltaTime);
}
FixedUpdate() — on the physics clock
Runs on a fixed timestep (0.02s / 50 times per second by default), decoupled from the frame rate. This is where all physics code belongs — anything touching a Rigidbody (forces, velocity). Because its interval is constant, use Time.fixedDeltaTime here (or just rely on the physics engine's own integration):
private Rigidbody body;
void Awake() => body = GetComponent<Rigidbody>();
void FixedUpdate()
{
// Apply physics forces here, NOT in Update.
body.AddForce(Vector3.forward * 10f);
}
⚠️ Watch Out: read input in Update, apply it in FixedUpdate
Input events (like a key-down) can be missed if you poll them in FixedUpdate, because it doesn't run every frame. Read input in Update, store it in a field, and use that stored value inside FixedUpdate to drive physics.
LateUpdate() — after all Updates
Runs once per frame too, but guaranteed after every Update has finished. The textbook use is a camera that follows the player: you want the camera to reposition only after the player has already moved this frame, otherwise it lags or jitters.
public Transform target; // the player, assigned in the Inspector
void LateUpdate()
{
// The player already moved in its Update; now we follow.
transform.position = target.position + new Vector3(0f, 5f, -10f);
}
➜ Update()"] --> P["Physics & forces
➜ FixedUpdate()"] P --> L["Follow / cleanup after movement
➜ LateUpdate()"] style I fill:#e8f5e9,stroke:#4CAF50,stroke-width:2px style P fill:#eff6ff,stroke:#3b82f6,stroke-width:2px style L fill:#f3e8ff,stroke:#8b5cf6,stroke-width:2px
💡 deltaTime vs fixedDeltaTime
Time.deltaTime = seconds since the last frame (varies) — use in Update/LateUpdate. Time.fixedDeltaTime = the fixed physics step (constant) — use in FixedUpdate. Handy fact: Time.deltaTime automatically returns fixedDeltaTime when read inside FixedUpdate, so * Time.deltaTime is safe in both.
OnDisable & OnDestroy
Every setup has a matching teardown. Skipping cleanup is a top source of Unity bugs — dangling event subscriptions that fire on dead objects, or "leaked" listeners.
| Function | When | Use it for |
|---|---|---|
OnDisable() | Whenever the component is disabled or its GameObject deactivated (can happen many times). | Undo what OnEnable did — unsubscribe from events, stop timers. |
OnDestroy() | Once, when the component/GameObject is destroyed (or the scene unloads). | Final cleanup — release resources, save state, unregister from managers. |
The most important pattern in the whole lesson: subscribe in OnEnable, unsubscribe in OnDisable. They're mirror images, so subscriptions never leak:
public class ScoreListener : MonoBehaviour
{
void OnEnable()
{
GameEvents.ScoreChanged += HandleScoreChanged; // subscribe
}
void OnDisable()
{
GameEvents.ScoreChanged -= HandleScoreChanged; // ALWAYS undo it
}
void HandleScoreChanged(int newScore) => Debug.Log($"Score: {newScore}");
}
💡 Note on order: when an object is destroyed, Unity callsOnDisablefirst, thenOnDestroy. So the unsubscribe inOnDisablealso covers the destroy case — you rarely need to repeat it inOnDestroy. (We go deep on C# events and this pattern in Lesson 3.2.)
Why No override?
A sharp C# programmer notices something odd: you write void Update() with no override, no interface, and often private. How does Unity call a private method you never registered?
Unity finds these methods by name and signature when your script first runs, using a fast internal lookup (not ordinary reflection every frame). If a method with the right name exists, Unity wires it into the loop; if not, it simply isn't called. This is why:
- An empty
Update()still has a small cost — Unity is invoking it every frame. Delete event functions you don't use. - A typo silently does nothing:
void Updaet()orvoid update()compiles fine and is simply never called. Case and spelling must be exact. - Signatures matter:
Update()takes no parameters;OnCollisionEnter(Collision c)takes exactly one of the right type.
⚠️ The #1 beginner bug in this lesson
Your movement code "does nothing" and there's no error. Nine times out of ten it's a misspelled or mis-cased event function (Start vs start, Update vs Updte). The compiler can't help — the name just doesn't match, so Unity never calls it.
✅ Pro Tip
Let the editor generate these for you. In Visual Studio / Rider, typing the method name offers an autocomplete stub with the correct signature — the safest way to avoid typos. And keep Update lean: heavy work every frame is the most common Unity performance mistake (Lesson 5.1).
Exercise & Quiz
🏋️ Exercise: The Lifecycle Logger
Objective: See the lifecycle with your own eyes by logging every event, then predict the console output before you run it.
Instructions:
- Create a script
LifecycleLogger.csand attach it to any GameObject (e.g. an empty one namedProbe). - Implement
Awake,OnEnable,Start,Update,OnDisable, andOnDestroy, each logging its own name withDebug.Log. - Guard
Updateso it only logs the first few frames (otherwise it floods the console). - Press Play, watch the Console, then stop Play mode. Predict the order first, then check.
- Bonus: toggle the component's checkbox off and on in the Inspector during Play — which functions fire?
Starter Code:
using UnityEngine;
public class LifecycleLogger : MonoBehaviour
{
void Awake() { Debug.Log("1. Awake"); }
// TODO: add OnEnable, Start, Update (first 3 frames only), OnDisable, OnDestroy
}
💡 Hint
Keep a frame counter field and only log inside Update while it's below 3, so you can read the sequence. On stopping Play mode, Unity disables then destroys everything — so you'll see OnDisable before OnDestroy at the end.
✅ Solution
using UnityEngine;
public class LifecycleLogger : MonoBehaviour
{
private int frame;
void Awake() { Debug.Log("1. Awake"); }
void OnEnable() { Debug.Log("2. OnEnable"); }
void Start() { Debug.Log("3. Start"); }
void Update()
{
if (frame < 3)
{
Debug.Log($"4. Update (frame {frame})");
frame++;
}
}
void OnDisable() { Debug.Log("5. OnDisable"); }
void OnDestroy() { Debug.Log("6. OnDestroy"); }
}
Expected order on Play then Stop: Awake → OnEnable → Start → Update(0) → Update(1) → Update(2) → … → (on stop) OnDisable → OnDestroy. Toggling the checkbox off fires OnDisable; toggling it back on fires OnEnable again — but not Awake or Start, which only ever run once.
🎯 Quick Quiz
Question 1: You need to look up another GameObject that must already be initialized. Where should that lookup go?
Question 2: Where should you apply a force to a Rigidbody?
Question 3: Your Update method never runs and there's no compile error. What's the most likely cause?
Summary
🎉 Key Takeaways
- Unity calls event functions at defined moments:
Awake→OnEnable→Start(birth),FixedUpdate/Update/LateUpdate(loop),OnDisable→OnDestroy(death). - Two-phase startup: all
Awakes run before anyStart. Set up yourself inAwake, reference others inStart. Update= per frame (useTime.deltaTime);FixedUpdate= physics on a fixed step (Rigidbody forces);LateUpdate= after all Updates (camera follow).- Subscribe in
OnEnable, unsubscribe inOnDisable— mirror images so nothing leaks. - Event functions are matched by name and signature, not
override/interfaces — so a typo silently does nothing, and unused event functions still cost a little. - Order is guaranteed within a component, not across different GameObjects.
📚 Additional Resources
- Unity Manual — Event function execution order
- Scripting Reference — MonoBehaviour messages (Awake, Start, Update…)
- Scripting Reference — Time.deltaTime
🚀 What's Next?
You can now write components that come alive at the right moments. But real behavior needs objects to find and talk to each other. In Lesson 1.3 we cover accessing GameObjects and components: GetComponent, references, and the [SerializeField] attribute for wiring things up in the Inspector.
🎉 You've mastered the game loop!
Knowing exactly when your code runs is what separates "it works by luck" from "it works by design." Great progress.