Summary
Every Unity script that inherits from MonoBehaviour has access to a set of lifecycle methods that the engine calls automatically at specific moments. The two most important for beginners are Start, which runs once just before the script’s first Update (for objects already in the scene, that is when Play mode begins), and Update, which runs every frame while the script is enabled. Understanding when each method runs — and which type of logic belongs in each — is the foundation of all Unity scripting.
Key ideas
Start() — runs exactly once, on the frame the script is first enabled, just before its first Update call. For objects placed in the scene this happens as Play mode begins; for objects spawned at runtime with Instantiate, Awake and OnEnable run during the Instantiate call itself, and Start runs later, before that object’s first Update (Unity Scripting Reference, MonoBehaviour.Start and MonoBehaviour.Awake). Use it for:
- Logging initial state to verify the script is attached and configured correctly
- Caching component references (e.g. storing a
Rigidbody2Din a field soUpdatecan use it without callingGetComponentevery frame);Awakeis an equally good, and often better, place for this, because the components already exist and other scripts’Startmay then use your cached references (see “Awake order between scripts” below) - Any one-time initialisation
Update() — runs every frame, as long as the scene is playing and the GameObject is active. Use it for:
- Reading player input
- Moving objects
- Ticking timers
- Anything that must respond to the passage of time
Time.deltaTime — the number of seconds that elapsed since the last frame. Multiplying any per-frame value by Time.deltaTime converts it from “per frame” to “per second”, making it frame-rate independent. A machine running at 60fps calls Update 60 times per second with a small deltaTime; a machine at 30fps calls it 30 times with a larger deltaTime. The product is the same either way.
gameObject.name — the name of the GameObject this script is attached to. Including it in Debug.Log messages is useful when multiple objects share the same script.
In practice
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField] private float moveSpeed = 5f;
private float logTimer = 0f;
// Runs once, just before this script's first Update
void Start()
{
Debug.Log("[" + gameObject.name + "] PlayerController ready. Speed: " + moveSpeed);
}
// Runs every frame
void Update()
{
HandleMovement();
LogPositionPeriodically();
}
private void HandleMovement()
{
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
// Time.deltaTime makes movement speed-per-second, not speed-per-frame
transform.Translate(h * moveSpeed * Time.deltaTime, v * moveSpeed * Time.deltaTime, 0f);
}
private void LogPositionPeriodically()
{
logTimer += Time.deltaTime;
if (logTimer >= 2f)
{
Debug.Log("[" + gameObject.name + "] Position: " + transform.position);
logTimer = 0f;
}
}
}Keep Update() short: delegate logic to named private methods rather than writing everything inline. A long Update body is hard to read and debug. The HandleMovement / LogPositionPeriodically pattern above is the recommended style.
How to read this script:
- Read the fields first.
moveSpeedis a tunable Inspector value;logTimeris remembered between frames. - Read
Startnext. It confirms the script is attached and reports the starting configuration. - Read
Updateas a repeated loop. Every frame it callsHandleMovement, thenLogPositionPeriodically. - Follow each helper method.
HandleMovementchanges position;LogPositionPeriodicallychanges the timer and occasionally logs. - Check which values persist.
logTimermust be a field because it accumulates over many frames;handvcan be local because they are recalculated each frame.
This mirrors the course teaching rhythm: fields store game state, Start verifies setup, and Update performs a small set of readable per-frame actions (course lab series, see source-csharp-unity-labs).
Other lifecycle methods
| Method | When it runs |
|---|---|
Awake() | Before Start, even if the script is disabled. Use for self-initialisation that other scripts’ Start may depend on. The order between different scripts’ Awake is not defined (see below). |
OnEnable() / OnDisable() | When the GameObject is enabled or disabled. Useful for resetting state and for subscribing to / unsubscribing from events. |
FixedUpdate() | At a fixed time step (default 50 times/second). Use for physics calculations instead of Update. |
LateUpdate() | After all Update calls this frame. Use for camera follow logic. |
OnDestroy() | Just before the GameObject is destroyed. Use for cleanup. |
Spawn timing: what runs during Instantiate
Objects created at runtime follow the same methods, but not all at once. When a spawner calls Instantiate on an active prefab:
- Inside the
Instantiatecall: Unity creates the clone and callsAwakeand thenOnEnableon its scripts (Unity Technologies, Scripting API: Object.Instantiate). Instantiatereturns: the spawner’s next line runs. The clone’s components, including itsRigidbodyorRigidbody2D, already exist, soGetComponent, setting fields and calling methods on the clone all work immediately.- Later, before the end of the current frame:
Startruns, before the clone’s firstUpdate. The Scripting Reference states that “Awake and Start are called on the instantiated objects before the end of the current frame” (Unity Technologies, Scripting API: MonoBehaviour.Start). - Next physics step: any force added so far is applied.
AddForceonly accumulates the force at the time of the call; “the physics system applies the effects during the next simulation run” (Unity Technologies, Scripting API: Rigidbody.AddForce).
The practical consequence: values the spawner sets after Instantiate are not visible in the clone’s Awake or OnEnable, but they are visible in its Start.
Worked example: a bullet whose speed is set by the gun
// Gun.cs
[SerializeField] private Bullet bulletPrefab;
[SerializeField] private Transform muzzle;
private void Fire()
{
Bullet b = Instantiate(bulletPrefab, muzzle.position, muzzle.rotation);
// Bullet.Awake and Bullet.OnEnable have ALREADY run here; Bullet.Start has not.
b.Init(20f);
}// Bullet.cs (prefab has a Rigidbody)
public class Bullet : MonoBehaviour
{
private Rigidbody rb;
private float speed = 5f; // default, replaced by Init
private void Awake() => rb = GetComponent<Rigidbody>(); // self-setup: safe here
public void Init(float newSpeed) => speed = newSpeed;
private void Start()
{
// Runs after Init, so speed is 20. Reading speed in Awake would give 5.
rb.AddForce(transform.forward * speed, ForceMode.Impulse);
}
}“Do I have to wait for Update before applying forces?” No. If Awake added or cached the Rigidbody and its Collider, a one-off force (an impulse, a launch) can be applied in Start straight away; Update gains you nothing, because forces are only applied when the physics step runs anyway. Three timing facts are worth knowing:
- Contacts come later. Collision and trigger callbacks (
OnCollisionEnter,OnTriggerEnterand their 2D versions) and contact queries only have results after the first physics step has simulated the new body, so do not expect a collision inStartto be reported yet. A body that spawns overlapping something reports the contact on the first step, not at creation. - Continuous forces belong in
FixedUpdate. A one-off impulse can go anywhere; a force you apply every tick (thrust, wind, a steering force) goes inFixedUpdate, which the Manual describes as the place for “applying a force to a Rigidbody” because physics runs on the fixed timestep (Unity Technologies, Unity Manual: Fixed updates). See unity-rigidbody2d for reading input inUpdateand applying it inFixedUpdate. - Components added in code are usable at once. A
Rigidbodyadded withAddComponent<Rigidbody>()inAwakeexists as soon as that call returns, soStart, or the next line, can use it.
“Should I use Awake or Start so the Rigidbody is ready for forces?” Either works for the physics itself: the Rigidbody exists as soon as the clone does, so cache it in Awake, and a force added in Awake, in Start or by the spawner straight after Instantiate is applied on the next physics step. Choose by data, not physics: if the force depends on something the spawner sets after Instantiate (speed, direction, owner), apply it in Start or inside the Init method itself. A common alternative is to apply the launch force inside Init, which makes the order explicit.
Caveats:
- If the prefab (or the clone’s parent) is inactive,
AwakeandOnEnabledo not run duringInstantiate; they run when the object is first activated (Unity Technologies, Scripting API: Object.Instantiate and MonoBehaviour.Awake). AddForcehas no effect if the GameObject is inactive, and it does not work on a kinematic body (Unity Technologies, Scripting API: Rigidbody.AddForce).- In 2D, the same timing applies: use
Rigidbody2D,ForceMode2D.Impulseand usuallytransform.rightortransform.up(see unity-rigidbody2d). Spawning many bullets is a good case for pooling, whereOnEnablereplacesStartas the “I have just been fired” hook becauseStartruns only once per object.
Awake order between scripts
Unity guarantees that every Awake runs before any Start, but it does not guarantee the order in which different scripts’ Awake methods run, whether the scripts are on different GameObjects or on the same one. The Scripting Reference says “The order in which Unity calls each GameObject’s Awake is not deterministic”, so you should not “assume that a reference set up by one GameObject’s Awake will be usable in another GameObject’s Awake”; instead, “use Awake to set up references between scripts, and use Start, which is called after all Awake calls are finished, to pass any information back and forth” (Unity Technologies, Scripting API: MonoBehaviour.Awake). The Manual adds that scripts sharing the same execution order run in an order that “is not deterministic”, which can differ between builds, machines and Unity versions (Unity Technologies, Unity Manual: Script execution order).
What this means for two scripts on one prefab, say Health and HealthBar:
In HealthBar.Awake | Safe? | Why |
|---|---|---|
health = GetComponent<Health>(); | Yes | The components exist before any Awake runs; you are only getting a reference. |
bar.max = health.MaxHealth; where Health sets MaxHealth in its own Awake | No | Health.Awake may not have run yet; it can work in one build and fail in another. |
The same line in HealthBar.Start | Yes | Every Awake has finished before any Start. |
Does Instantiate change this? Not the rule itself: inside Instantiate, the clone’s scripts all receive Awake (and OnEnable) before the call returns, still in an undefined order between scripts, and Start follows later in the frame. The extra trap is the one in “Spawn timing” above: values the spawner sets after Instantiate are not visible in any Awake. If the prefab or its parent is inactive, none of these Awake calls run until the object is activated.
The working rule: Awake caches your own references and initialises your own state; Start reads other scripts’ data. Only when that is not enough (a manager that must initialise before everything else), force an order:
[DefaultExecutionOrder(-100)] // lower numbers run earlier than the default (0)
public class GameManager : MonoBehaviour { /* ... */ }or set it in Edit > Project Settings > Script Execution Order. The Manual’s own example is configuring the order “to ensure that Awake for one script is always called before Awake for another”; if a script has both an attribute and a Project Settings value, the Project Settings value wins, and orders set with the attribute do not appear in that window (Unity Technologies, Unity Manual: Script Execution Order project settings reference; Unity Manual: Script execution order). Use it sparingly: a long list of forced orders hides dependencies that would be clearer as an explicit Init call.
Gotchas
- Never call
StartorUpdateyourself. Unity calls them. If you want to run initialisation code manually, extract it into a separate method and call that. - Variables declared inside
StartorUpdateare local. They exist only for that call. Fields (declared at class level) persist between frames. Updatedoes not run if the GameObject is inactive or the script component is disabled.- Physics-based movement belongs in
FixedUpdate, notUpdate, to avoid erratic behaviour when frame rate fluctuates. - Reading another script’s data in your
Awakeworks by luck, not by design: the order between scripts’Awakeis undefined. Read it inStart.
Practice
Create a GameObject named LifecycleProbe. Add a script with:
- one
Debug.LoginStart - one timer in
Update - one private method called from
Update - one field that stores elapsed time
Success test:
Startlogs once when Play mode begins- the timer logs every two seconds
- the log includes
gameObject.name - no local variable is expected to remember a value between frames
Extension:
- disable the script component during Play mode and observe that
Updatestops running
Self-test
- Which method runs once:
StartorUpdate? - Why should movement usually multiply by
Time.deltaTime? - A variable declared inside
Updateresets every frame. Where should it go if it needs to remember a value? - Which lifecycle method is normally better for physics movement?
- Why is a short
Updatemethod easier to debug?
Answers
Startruns once when the script first becomes active.Time.deltaTimeturns movement into units per second, so speed stays consistent across different frame rates.- It should be a field, declared at class level.
FixedUpdate, because Unity runs physics on a fixed timestep.- A short
Updatepoints you to named helper methods, so the likely fault is easier to find.
Worked example
- TopDownPlayerMovement.cs and PlayerHealth.cs —
Awakecaching,Startinitialisation, input inUpdateand physics inFixedUpdate(beginner 2D route; see the folder’s README)
Related
- csharp-methods - the method syntax used in lifecycle methods
- unity-input - reading player input in
Update - unity-transform - moving objects in
Update - unity-getcomponent - caching component references in
AwakeorStart - unity-inspector-references - assigning references before
Startruns - unity-prefabs-scripting - spawning prefabs with
Instantiate - unity-rigidbody2d - physics in
FixedUpdate, interpolation and one-shot input - object-pool-pattern - reusing spawned objects, where
OnEnableacts as the re-spawn hook - scriptableobject-event-channels - subscribing to events in
OnEnable/OnDisable - unity-scene-management — Reloading a scene re-runs Awake/OnEnable/Start; sceneLoaded runs after OnEnable, before Start
- unity-debugging — Debugging in Unity: Console, breakpoints and gizmos
- unity-character-controller — One Move call per frame in Update with CharacterController