How Unity Thinks¶
What You Will Learn
By the end of this guide you will be able to:
- Describe any Unity scene as GameObjects built out of components
- Predict which fields of a script show up in the Inspector, and why
- Explain where Inspector values actually live (and why Play Mode edits vanish)
- Predict the order Unity calls
Awake,OnEnable,Start, andUpdate - Decide what should be a prefab, a prefab variant, or just an override
Difficulty: Beginner ยท Time: ~45 minutes
How to Use This Guide¶
You don't need Unity open for any of this. Every section is a short explanation followed by an exercise:
- Predict: read some code or a scenario and write down what you think will happen before you open "Check your prediction". I'd recommend committing to an answer, on paper or in a notes app or mentally. The wrong guesses are where most of the learning happens anyways (I hope).
- Make: a small design exercise. There's no answer key for these on purpose; if you're stuck, open the hints one at a time, and when you're done, check your work against "What a good answer includes". A lot of these can also be made in Unity if you want to get some practice with the engine.
- Try it in Unity: if Unity is ready on your machine, these let you check your predictions for real and figure out what's actually going on. If it isn't, you won't miss anything; this guide also works as a mental learning exercise on how to interpret Unity.
This guide is intended to be for completely fresh Unity users, so if you already know something here, feel free to skip around! I do think there is value from reading this as an experienced person as well, however!
Compare notes
If someone's sitting next to you, talk to them! Programming is actually a super collaborative task, and it helps a lot to talk to others about what you're doing.
Introduction¶
If you've written programs before, you're probably used to a main() function where everything starts, and code that runs top to bottom. Unity doesn't work like that. There's no main() for you to write. Instead, you build a world out of objects, attach small scripts to them, and Unity calls those scripts at specific moments: when an object is created, every frame, when it's destroyed, and so on.
Almost every "why isn't this working" moment new Unity programmers have comes from expecting the first model while Unity runs the second one. So before we write any real code, I want you to have the mental model of what a scene is made of, where data lives, and when your code actually runs.
Scenes Are Made of GameObjects¶
A scene is a level, a menu, or any other self-contained chunk of your game. Open one and you'll see the Hierarchy: a list of GameObjects.
A GameObject, on its own, does nothing. It has a name, and it always has a Transform (its position, rotation, and scale), and that's it, a container.
New to this? A GameObject is a labeled box
Picture an empty cardboard box with a name written on it, sitting at a spot in the world. The box itself can't do anything. Everything interesting comes from what you put inside it, and in Unity, what you put inside are components.
Components Do the Work¶
Everything a GameObject can do comes from its components. A few you'll see constantly:
| Component | What it gives the GameObject |
|---|---|
SpriteRenderer / MeshRenderer |
Something to look at |
Collider (box, sphere, capsule...) |
A shape that things can bump into |
Rigidbody |
Physics: gravity, forces, being pushed around |
AudioSource |
The ability to play sound |
| Your own scripts | Whatever behavior you write |
So a player might be one GameObject with a SpriteRenderer, a Rigidbody2D, a CapsuleCollider2D, a PlayerMovement script, and a Health script. An enemy might have a completely different movement script but the same Health script. That's the core idea of Unity: a thing is what it's made of. Instead of building a big class hierarchy (Player inherits from Character inherits from Entity), you assemble objects out of small pieces that each do one job.
This idea has a name, the Component pattern, and Unity's whole architecture is built around it.
Predict: the crate
A crate in your level has a MeshRenderer and a BoxCollider, but no Rigidbody. The player (who does have physics) walks into it.
- What happens to the player, and what happens to the crate?
- You add a
Rigidbodyto the crate. What changes? - Instead, you remove the crate's
MeshRendererbut keep the collider. What does the player experience?
Check your prediction
- The player stops, and the crate doesn't move at all. A collider with no
Rigidbodyis treated as a static part of the level, like a wall. - Now the crate falls under gravity and gets shoved when the player pushes it. The
Rigidbodyis the part that says "physics applies to me." - An invisible wall. The collider still blocks the player; there's just nothing to see.
Each component adds exactly one capability. Remove a component and you remove exactly that capability, and nothing else. Keep that in mind, because it's also a great rule for the scripts you write.
Make: take apart a game you know
Pick one moment from a game you've played: a room in a roguelike, a boss arena, a dialogue scene, anything. Find a screenshot of it online (or open it up yourself). Then:
- List every GameObject you think is in that scene, including ones you can't see.
- For each one, list the components it needs.
- Mark any component that shows up on more than one kind of object.
Hint 1
Start with what's visible, then ask: what's making things happen that I can't see? Who spawns the enemies? What's playing the music? What keeps track of the score?
Hint 2
Invisible GameObjects are completely normal. Spawners, trigger zones, managers, and music players are usually GameObjects with no renderer at all. And a component should do one job, the way a Collider only handles shape. Unity's manual page on GameObjects has a good overview.
Hint 3
Go object by object and ask four questions: does it need to be seen, to be touched, to do something, and to remember something? Each "yes" is usually a component. A coin needs to be seen (SpriteRenderer), to be touched (a Collider set as a trigger), and to do something when touched (a Coin script that adds to the score).
What a good answer includes
- At least one GameObject with nothing visible (a spawner, a trigger zone, a manager).
- At least one component reused across different kinds of objects, like
Healthon the player and the enemies. - No component described as doing several unrelated jobs. "PlayerController moves, attacks, plays the music, and saves the game" should really be four components.
- Script names that say what they do (
EnemySpawner), not vague ones (Manager2).
The Transform Hierarchy¶
GameObjects can be parented to other GameObjects. A child moves, rotates, and scales along with its parent, which is how a sword stays in a character's hand or a health bar floats over an enemy's head.
This gives every GameObject two ways to describe where it is:
- World position: where it actually is in the scene (
transform.position). - Local position: where it is relative to its parent (
transform.localPosition).
So game development is actually secretly just math class (mostly linear algebra). Don't you love math? Surprise pop quiz:
Predict: parent and child
An empty parent GameObject sits at world position (10, 0, 0) with a scale of 1. Its child sits at local position (2, 0, 0).
- What's the child's world position?
- The parent's scale changes to
2. What's the child's world position now? What's its local position? What happens to how big it looks?
Check your prediction
(12, 0, 0). The child's local offset of 2 gets added to the parent's position.- The child's world position becomes
(14, 0, 0), because the parent's scale stretches the offset too. Its local position is still(2, 0, 0), since nothing about its relationship to the parent changed, and it looks twice as big because children inherit their parent's scale.
Rotation works the same way: rotate the parent and the child swings around it like it's on an invisible arm. That's why mixing up position and localPosition in code is top 5 bugs ever; they're both correct, just measured from different places.
Scripts Are Components Too¶
When you write a class that inherits from MonoBehaviour, you've made a component. You add it to a GameObject exactly like a Collider or an AudioSource, and Unity creates the instance for you.
Two things to keep in mind:
- The file name has to match the class name.
Turret.csmust containclass Turret, or Unity won't let you add it to anything. (The C# Style Guide has more.) - You never write
new Turret(). Unity logs a warning if you try, and you end up with a broken object that isn't attached to anything. Components come from adding them in the editor, fromAddComponent, or from spawning a prefab.
The other big thing a MonoBehaviour gets you is the Inspector, where some of its fields appear, and where you (and designers) can change them without touching code. But not every field shows up.
Predict: what shows up in the Inspector?
You add this script to a GameObject. Which of these eight members show up in the Inspector?
using UnityEngine;
public class Turret : MonoBehaviour
{
public float range = 10f;
[SerializeField] private float fireRate = 2f;
private int shotsFired;
public static int TurretCount;
public const float MaxRange = 50f;
public readonly float Damage = 5f;
public float Cooldown { get; set; }
[field: SerializeField] public int Ammo { get; private set; } = 30;
}
(Yes I know that public float range breaks two rules in the style guide I meticulously wrote. It's here because you'll see it in basically every Unity tutorial ever.)
Check your prediction
Three of them: range, fireRate, and Ammo.
Unity serializes (saves, and shows in the Inspector) a field when it's public or marked [SerializeField], and it isn't static, const, or readonly. So:
rangeis public. It shows up.fireRateis private, but[SerializeField]opts it in. It shows up. This is the version you should actually write.shotsFiredis private with no attribute. Hidden.TurretCountis static, so it belongs to the class rather than any one turret. Hidden.MaxRangeis a constant and can never change, so there's nothing to save. Hidden.Damageis readonly, so Unity can't write a saved value back into it. Hidden.Cooldownis a property, and Unity doesn't save properties. Hidden.Ammois a property too, but[field: SerializeField]tells Unity to save the hidden field behind it, so it shows up. I also recommend using this one.
Try it in Unity
Create a new project from Unity Hub with any 2D/3D template (Universal 2D/3D is a safe choice). Go to Assets → Create → Scripting and create a MonoBehaviour script named Turret, paste the code in, and drag it onto any GameObject in the scene. Check the Inspector against your prediction.
The Inspector Is Data, Not Code¶
Here's a mental shift that saves a lot of confusion: when you change a value in the Inspector, you're not editing your script. You're editing data saved in the scene (or prefab) file. The = 10f in your code is only a default, used when the component is first added.
Predict: the changed default
Your Turret has public float range = 10f;. You add it to three turret GameObjects in a scene, and set the third one's range to 25 in the Inspector. Save the scene.
Later, you change the code to public float range = 15f;.
- What's the range on each of the three turrets now?
- What range does a brand-new fourth turret get?
Check your prediction
10,10, and25. Nothing changed. When the scene was saved, every turret's range was written into the scene file, and that saved value wins over the code's default.15. The default only matters when a component is first created.
If you want to change existing objects, change them in the Inspector (or in the prefab, which you'll see below).
Predict: Play Mode tweaking
You press Play, and the turret fires too slowly. While the game is running, you bump fireRate from 2 to 5 in the Inspector. It feels great. You stop Play Mode.
What's fireRate now?
Check your prediction
It's back to 2. Any change you make to scene objects while in Play Mode is thrown away when you stop. Play Mode is a sandbox: tweak freely to find values you like, but write them down (or copy the component's values) before you stop, then set them again in Edit Mode.
One exception you'll meet soon: changes to ScriptableObject assets do stick around after Play Mode ends. That's handy and occasionally dangerous, but this will show up later.
Try it in Unity
Put the Turret on a cube, press Play, change fireRate in the Inspector, and stop. While you're at it, I'd recommend setting a Play Mode tint (under Preferences → Colors) so the editor turns a different color while the game is running. It makes it much harder to forget you're in Play Mode.
The Lifecycle¶
Since there's no main(), Unity calls specific methods on your scripts at specific moments. You don't subscribe to anything; if your MonoBehaviour has a method with the right name, Unity calls it. These are the ones you'll use most:
| Method | When Unity calls it |
|---|---|
Awake |
Once, when the object is loaded or created, even if this script component is disabled (as long as the GameObject is active) |
OnEnable |
Every time the component becomes enabled and active |
Start |
Once, just before the component's first Update, but only once the component is enabled |
FixedUpdate |
Every physics step, which might be zero, one, or several times per frame |
Update |
Every frame |
LateUpdate |
Every frame, after every Update has run |
OnDisable |
When the component is disabled or its GameObject is deactivated |
OnDestroy |
When the object is destroyed |
flowchart LR
A["Awake"] --> B["OnEnable"]
B --> C["Start"]
C --> D["FixedUpdate"]
D --> E["Update"]
E --> F["LateUpdate"]
F -->|"next frame"| D
F --> G["OnDisable"]
G --> H["OnDestroy"]
New to this? What's a frame?
A game redraws the screen many times per second, and each redraw is a frame. At 60 frames per second, Update runs about 60 times a second. Physics runs on its own fixed clock (FixedUpdate), which is why the two can run a different number of times.
Predict: two loggers
This script logs a message from each method:
using UnityEngine;
public class LifecycleLogger : MonoBehaviour
{
private bool hasLoggedUpdate;
private void Awake() => Debug.Log($"{name}: Awake");
private void OnEnable() => Debug.Log($"{name}: OnEnable");
private void Start() => Debug.Log($"{name}: Start");
private void Update()
{
if (!hasLoggedUpdate)
{
Debug.Log($"{name}: first Update");
hasLoggedUpdate = true;
}
}
}
The scene has two GameObjects, A and B, each with a LifecycleLogger. On B, the LifecycleLogger component is disabled (its checkbox in the Inspector is unticked). Both GameObjects are active.
Write down every line you expect in the Console after pressing Play, in order.
Check your prediction
You'll see something like:
Three things are worth noticing:
- B's
Awakeruns even though its component is disabled.Awakebelongs to the object being loaded, not to the component being enabled. B never getsOnEnable,Start, orUpdate, until something enables it. - A and B might print in the other order. Unity explicitly doesn't guarantee which object's
Awakeruns first, so your code can't rely on it. - Every
Awakehappens before anyStart. Unity doesn't callStarton anything untilAwakehas run on every object in the scene.
Points 2 and 3 together give you the easiest solution to all sorts of race condition problems: set yourself up in Awake, and talk to other objects in Start. In Awake, the object you want to talk to might not exist yet. By Start, everybody's Awake is guaranteed to be done.
Try it in Unity
Make two cubes named A and B, add LifecycleLogger to both, untick it on B, and press Play. Then, while the game is running, tick B's checkbox and watch its OnEnable and Start show up.
Make: the enemy registry
A GameManager needs to know about every enemy in the level, so it keeps a list. Each Enemy adds itself to that list when it appears, and removes itself when it's gone.
Decide which lifecycle method each of these belongs in, and make sure you know why:
- The
GameManagercreating its empty list. - An
Enemyadding itself to the list. - An
Enemyremoving itself from the list.
Hint 1
For each step, ask: what has to already exist for this to work? Then find the earliest method where that's guaranteed.
Hint 2
Remember the two guarantees from the prediction above: the order of different objects' Awake calls is random, but every Awake finishes before any Start. Also think about enemies that get disabled and re-enabled (a pooled enemy, say), not just ones that are created and destroyed. The execution order page has the full explanation if you're curious.
Hint 3
The list is the manager's own setup, so it doesn't depend on anyone else. An enemy registering depends on the manager's list existing, which is only guaranteed after every Awake. For removal, think about which method is the natural "undo" of the one you picked for adding, so that disabling and re-enabling an enemy doesn't register it twice.
What a good answer includes
- The manager's list is created in the manager's own setup (its
Awake, or even right where the field is declared). - No enemy touches the manager during its own
Awake, because the manager might not be set up yet. - If you used
OnEnableanywhere, you've accounted for the fact that at scene start it runs right after that same object'sAwake. - Adding and removing mirror each other, and you can say exactly what happens under your choice to an enemy that's disabled and re-enabled (like a pooled one).
- Your justifications talk about what's guaranteed, not what happened to work when you tried it once.
Prefabs¶
A prefab is a GameObject (with all its components and children) saved as an asset, so you can stamp out copies of it. Each copy in a scene is a prefab instance, and instances stay linked to the prefab: change the prefab and every instance updates.
Instances can still override individual values. Overridden values show up in bold in the Inspector, with a blue line next to them. And when you want a reusable "same thing, but different" version, like a Spear Goblin based on a Goblin, you make a prefab variant: a prefab built on top of another prefab.
Predict: the mini-boss
Your Goblin prefab has a Health component with a max health of 30. You place 20 goblins in a level. On one of them, the mini-boss, you change max health to 100 in the Inspector.
Later, you open the Goblin prefab and change its max health to 40. What's the max health of each of the 20 goblins?
Check your prediction
Nineteen goblins now have 40, and the mini-boss still has 100.
An instance only stores what's different about it (its overrides). Everything else comes from the prefab, so the 19 untouched goblins pick up the change immediately. The mini-boss's max health was overridden, and Unity's rule is that an override always wins over the prefab's value.
That's great when it's what you want, and a super annoying bug when it isn't: if you accidentally modify a value on some instance, prefab changes will ignore it.
Make: prefabs, variants, or overrides?
Go back to the scene you took apart earlier. For each object, decide:
- Should it be a prefab?
- Would any prefabs make good variants of each other?
- Which differences are just per-placement tweaks (overrides) rather than a new kind of thing (a variant)?
Hint 1
Ask: if I placed this object twice, would I want a fix to one of them to fix both?
Hint 2
Variants are for kinds of things you'll reuse ("Spear Goblin" everywhere), and overrides are for one specific placement ("this particular goblin guards the door, so it's tougher"). The Prefabs manual page covers both.
Hint 3
If you'd find yourself making the same five overrides on every instance of something, that's a variant asking to exist. If a change only makes sense for one spot in one level, it's an override.
What a good answer includes
- Anything that appears more than once is a prefab.
- Variants for reusable kinds that share most of their structure, not for one-off tweaks.
- Overrides for placement-specific changes, and a reason why each one isn't a variant.
- A few things that aren't prefabs at all, because they only ever exist once and never get spawned (though even those are often prefabs so they're easy to reuse across scenes, which is a fine answer too).
Two Things You'll Meet Next¶
ScriptableObjects. Not everything lives in a scene. A ScriptableObject is an asset, like a texture or a sound, that holds data instead of pixels. A spell's damage and cooldown, an enemy type's stats, a level's settings: all good ScriptableObject material, shared by everything that references them. Data-Driven Design explains this in much more detail.
How objects find each other. A turret needs to know where the player is, and a health bar needs to know when health changes. The three most common ways are a serialized reference (drag the object into a field in the Inspector), GetComponent (grab another component on the same GameObject), and events (announce that something happened and let whoever cares listen). Choosing between them is a big part of The OOP Toolkit.
Going Further¶
If you finished early, or this was all familiar:
- C# for Unity: the language-level stuff (properties, events, structs, and Unity's strange version of
null), especially if you're coming from Java, Python, or C++. - Thinking Like an Engineer: why "a thing is what it's made of" is a good idea far beyond Unity.
- The execution order page has the complete lifecycle diagram, including the physics and rendering steps this guide skipped.
Next Steps¶
- Designing a System: next meeting, we use this mental model to design a system for this week's game idea.
- C# Style Guide: how we write the scripts you've been reading.
Further Reading¶
- Unity Manual: GameObjects: the official explanation of GameObjects as containers for components.
- Unity Manual: Prefabs: instances, overrides, nesting, and variants in depth.
- Unity Manual: Script serialization rules: exactly what Unity saves and shows in the Inspector.
- Unity Manual: Event function execution order: the full lifecycle.
- Game Programming Patterns: Component: Robert Nystrom's chapter on the pattern Unity is built around, and a great read in general.