MonoBehaviour
MonoBehaviour is the base class for every Unity script — it provides lifecycle hooks, Component access, and the bridge between C# code and the Unity Engine.
Imagine...
MonoBehaviour is like an employment contract with Unity. Once you sign (inherit MonoBehaviour), the employee (your script) is automatically invited to scheduled meetings: kickoff (Awake), first day (Start), daily standup (Update), physics sync (FixedUpdate), and exit interview (OnDestroy). No inheritance = never called, even if you define the methods.
The concept in detail
MonoBehaviour inherits
Behaviour → Component → Object. That inheritance lets Unity invoke
magic methods (Awake, Start, Update…) via
reflection — you do not override or call a base method.
Key lifecycle:
Awake runs as soon as the object is created (even if disabled),
Start runs on the first frame after the object is enabled,
Update every frame, FixedUpdate on the Physics timestep
(default 0.02s).
Performance: an empty Update() still
costs a reflection call. Delete empty Updates. Use enabled = false to pause
the lifecycle instead of destroying the whole object.
Limits: you cannot use a constructor (
new MyScript()) — use AddComponent<T>(). Not thread-safe —
MonoBehaviour APIs run on the main thread only.
Diagram: Inheritance chain & magic methods
Awake()
Internal init · Before Start
Start()
Initial setup · First frame
Update()
Per-frame logic · Input, AI...
FixedUpdate()
Physics · every 0.02s
LateUpdate()
After all Updates · Camera follow
OnEnable()
When the object is activated
OnDisable()
When the object is deactivated
OnDestroy()
When the object is destroyed
Hands-on steps
Create a new C# script
Assets → Create → C# Script. Match the file name to the class name to avoid compile errors.
Use Awake for internal setup
Awake to cache component refs (_rb = GetComponent<Rigidbody>()). Avoid depending on other objects here.
Use Start for cross-object setup
Start when you need other objects (their Awake has already finished).
Split Update vs FixedUpdate
Input and AI → Update. Physics forces (AddForce, velocity) → FixedUpdate to avoid glitches.
Clean up in OnDestroy
Unsubscribe events, stop Coroutines/timers in OnDestroy to prevent leaks.
Interactive simulator
Press Play Scene to watch lifecycle methods fire in order. Try Disable or Destroy for the next steps.
Lifecycle Timeline
Console
Press Play to begin...
Code example
BasicStandard MonoBehaviour layout with lifecycle methods and why each one exists.
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField] private float speed = 5f;
private Rigidbody _rb;
private Vector3 _inputDir;
void Awake()
{
// Cache component — no dependency on other scripts
_rb = GetComponent<Rigidbody>();
}
void Start()
{
// Setup that needs GameManager.Awake to have finished
GameManager.Instance.RegisterPlayer(this);
}
void Update()
{
// Read input every frame
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
_inputDir = new Vector3(h, 0, v).normalized;
}
void FixedUpdate()
{
// Physics move — do not use Time.deltaTime here
_rb.MovePosition(_rb.position + _inputDir * speed * Time.fixedDeltaTime);
}
void OnDestroy()
{
GameManager.Instance.UnregisterPlayer(this);
}
}Code example
AdvancedOptimize: avoid empty Update; prefer OnEnable/OnDisable for event subscriptions.
using UnityEngine;
using System;
// Full lifecycle component — pairs well with an event system
public class HealthComponent : MonoBehaviour
{
[SerializeField] private int maxHp = 100;
private int _currentHp;
public event Action<int,int> OnHealthChanged; // (current, max)
public event Action OnDied;
void Awake() => _currentHp = maxHp;
void OnEnable()
{
// Subscribe when the component becomes active
GameEvents.OnDamageDealt += TakeDamage;
}
void OnDisable()
{
// Unsubscribe immediately — avoid calls while disabled
GameEvents.OnDamageDealt -= TakeDamage;
}
public void TakeDamage(int amount)
{
_currentHp = Mathf.Max(0, _currentHp - amount);
OnHealthChanged?.Invoke(_currentHp, maxHp);
if (_currentHp == 0) OnDied?.Invoke();
}
}📌 Quick recap
- ▸Awake = internal, Start = cross-object, Update = every frame
- ▸Physics → FixedUpdate, not Update
- ▸Empty Update() still costs — delete it if unused
- ▸OnEnable/OnDisable for subscribe/unsubscribe
⚠️ Common mistakes
❌ GetComponent inside Update()
Slow — searches every frame
✅ Cache in Awake: _rb = GetComponent<Rigidbody>()
❌ Using a constructor: new PlayerCtrl()
MonoBehaviour forbids new — Unity warns
✅ go.AddComponent<PlayerCtrl>()