Prefabs & Instantiation
So far every object in your collect-the-pickups game has been a thing you placed by hand in the editor — the player, the ground, a pickup or two you dropped into the scene. That works right up until you want twenty pickups, or pickups that keep appearing while the game runs. You don't want to copy-paste a GameObject twenty times and then, when you decide the pickup should be gold instead of blue, edit all twenty by hand. That way lies madness.
The fix is the single most important asset type in Unity: the prefab.
The mental model: a prefab is a template
Here's the whole idea in one breath: a prefab is a reusable GameObject saved as an asset — a template.
Instantiate stamps copies of it at runtime; Destroy removes them. Edit the prefab, and every copy
changes.
📝 If you've used C# classes, the analogy is close: a prefab is like a class, and each thing you spawn from it is an instance. One definition, many live copies. (It's not a perfect analogy — a prefab is data, not code — but "template you stamp out" is exactly the right instinct.)
Think of it like a rubber stamp. You carve the stamp once. Then you press it onto the page as many times as you like, and every imprint looks the same. Re-carve the stamp and every future imprint changes — and with Unity prefabs, even the imprints already on the page update too, because each one stays linked to the stamp. That linkage is what makes prefabs the DRY (don't-repeat-yourself) tool for game objects.
Creating a prefab
You already have the raw material in your scene. To turn a GameObject into a prefab, you drag it from the
Hierarchy window down into the Project window. That's it — Unity creates a .prefab asset, and the
GameObject in your scene becomes a linked instance of it (its name turns blue in the Hierarchy to show the
connection).
Walk through it for the pickup:
- Set up one pickup the way you want it — a small sphere, a Collider marked Is Trigger, a script that bumps the score, maybe a material so it glows.
- Drag that GameObject from the Hierarchy into the Project window (a folder like
Assets/Prefabsis a good home). - You now have a
Pickupprefab asset. Delete the one in the scene if you want — the template lives on in the Project window, ready to be stamped out.
To change the prefab later, double-click the asset to open it in Prefab Mode (an isolated editing view), make your edits, and save. Every instance in every scene — and every copy you spawn at runtime — picks up the change. That's the edit-once-update-all payoff.
💡 Prefab Variants are prefabs that inherit from a base prefab, the way a subclass inherits from a parent
class. You make a GoldPickup variant of Pickup, override just its color and point value, and it still
tracks every other change you make to the base Pickup. Handy when you have a family of similar objects;
reach for it when you notice yourself making near-identical prefabs.
Spawning copies at runtime: Instantiate
A prefab sitting in the Project window does nothing on its own — it's just a template. To put copies into the
running game you call Instantiate from a script:
Instantiate(pickupPrefab, position, rotation);
Instantiate creates a fresh copy of the prefab in the scene and returns a reference to that new instance,
so you can capture it and tweak it:
GameObject pickup = Instantiate(pickupPrefab, spawnPos, Quaternion.identity);
pickup.name = "Pickup (spawned)";
What just happened: we stamped out a new copy of pickupPrefab at spawnPos with no rotation, and the
return value pickup is that specific copy — not the template. Renaming pickup touches only this one
instance; the prefab asset and every other copy are untouched. Capturing the return value is how you spawn
something and then immediately give it speed, a target, a color, whatever this particular copy needs.
The mirror image is Destroy, which removes an object from the scene:
Destroy(gameObject); // remove this object now (end of frame)
Destroy(gameObject, 3f); // remove it after 3 seconds
What just happened: the first line schedules this GameObject for removal — Unity actually deletes it at the
end of the current frame, not the instant you call it, so any code running right after still sees a valid
object. The second line delays removal by three seconds, which is perfect for "spawn a particle burst, then
clean it up" or "this pickup expires if nobody grabs it." Spawn with Instantiate, remove with Destroy —
that's the full lifecycle of a runtime object.
The PickupSpawner
Let's make pickups actually appear in your game. Create an empty GameObject called Spawner, attach this
script, and your scene starts producing pickups on a timer:
using UnityEngine;
public class PickupSpawner : MonoBehaviour
{
[SerializeField] private GameObject pickupPrefab;
[SerializeField] private float interval = 2f;
void Start() => InvokeRepeating(nameof(Spawn), 1f, interval);
void Spawn()
{
var pos = new Vector3(Random.Range(-5f, 5f), 0.5f, Random.Range(-5f, 5f));
Instantiate(pickupPrefab, pos, Quaternion.identity);
}
}
What just happened: [SerializeField] private GameObject pickupPrefab creates a slot in the Inspector where
you drag your Pickup prefab — that's how the script knows what to spawn. In Start, InvokeRepeating
tells Unity: call Spawn after a 1-second delay, then again every interval seconds, forever. Each Spawn
picks a random x/z somewhere on the ground (y is fixed at 0.5 so the pickup sits on the surface) and
calls Instantiate to stamp a new pickup there. Quaternion.identity means "no rotation" — Unity stores
rotations as quaternions, and identity is the do-nothing rotation, the rotational equivalent of zero. The
result: a pickup pops into existence every couple of seconds at a random spot, and your collect-the-pickups
game finally has things to collect.
The trap: an unassigned prefab
⚠️ Here is the mistake nearly everyone hits at least once. You write PickupSpawner, hit Play, and the
console explodes with UnassignedReferenceException: The variable pickupPrefab of PickupSpawner has not been assigned.
The cause: [SerializeField] private GameObject pickupPrefab declares the slot, but a slot is empty until
you fill it. You have to select the Spawner in the Hierarchy and drag your Pickup prefab into that field
in the Inspector. The script can't guess which prefab you mean — that wiring happens in the editor, not in
code. A null pickupPrefab throws the instant Spawn runs.
So the rule: any [SerializeField] reference you see in a script is a promise that you'll assign it in the
Inspector. If something "isn't working" and the console mentions Unassigned, an empty Inspector slot is
almost always the culprit — go look.
When spawning gets heavy: object pooling
💡 Instantiate and Destroy are fine for a pickup every two seconds. But the moment you spawn fast — a
machine gun firing bullets, an explosion throwing particles, a wave of enemies — they become a performance
trap. Every Instantiate allocates memory, and every Destroy leaves garbage behind. Do that hundreds of
times a second and the garbage collector eventually has to sweep up, which causes a visible stutter — a
"GC hitch" — right when the action is most intense.
The fix is object pooling: instead of creating and destroying objects, you create a fixed pool of them up
front, then reuse them. When you need a bullet, you grab an inactive one from the pool and switch it on
(SetActive(true)); when it's done, you switch it off (SetActive(false)) and return it to the pool instead
of destroying it. No allocation, no garbage, no hitch — you're recycling the same handful of objects forever.
You don't need to build this by hand. Unity ships a built-in ObjectPool<T> in UnityEngine.Pool that
manages the get/release cycle for you. For your collect-the-pickups game, a pickup every couple of seconds is
nowhere near the threshold where pooling matters — plain Instantiate/Destroy is the right, simple choice
here. But file pooling away as the next step the day you build something spawn-heavy. Premature pooling is
wasted effort; pooling when you're spawning hundreds of objects a second is the difference between smooth and
stuttering.
Recap
- A prefab is a reusable GameObject saved as an asset — a template you create by dragging a GameObject from the Hierarchy into the Project window. Edit the prefab and every linked instance updates.
Instantiate(prefab, position, rotation)stamps a copy into the running scene and returns a reference to that new instance, so you can capture and customize it.Quaternion.identitymeans no rotation.Destroy(gameObject)removes an object (at end of frame);Destroy(obj, seconds)delays removal.- A spawner references its prefab through a
[SerializeField]field and spawns on a timer (e.g.InvokeRepeating). - ⚠️ You must assign the prefab in the Inspector — an empty slot throws
UnassignedReferenceExceptionthe moment you spawn. - 💡 For spawn-heavy games, swap
Instantiate/Destroyfor object pooling (ObjectPool<T>) to avoid garbage-collection hitches — but only when you actually spawn fast.
Quick check
[
{
"q": "What does Instantiate(pickupPrefab, pos, Quaternion.identity) return?",
"choices": ["Nothing (void)", "The prefab asset itself", "A reference to the newly spawned copy", "A boolean for success"],
"answer": 2,
"explain": "Instantiate returns a reference to the new instance in the scene, so you can capture it and modify that specific copy without touching the prefab asset."
},
{
"q": "You hit Play and get 'UnassignedReferenceException: the variable pickupPrefab has not been assigned.' What's the fix?",
"choices": ["Mark the field public instead of private", "Drag the Pickup prefab into the script's slot in the Inspector", "Call Instantiate twice", "Add a try/catch around Spawn"],
"answer": 1,
"explain": "A [SerializeField] field creates an Inspector slot that starts empty. You have to drag the prefab into that slot in the Inspector; the script can't guess which prefab you mean."
},
{
"q": "Why prefer object pooling over Instantiate/Destroy when spawning many objects fast?",
"choices": ["Pooling makes objects move faster", "It avoids the memory allocation and garbage-collection hitches that constant create/destroy causes", "Instantiate doesn't work for more than 10 objects", "Pooling is required for all prefabs"],
"answer": 1,
"explain": "Constant Instantiate/Destroy allocates memory and creates garbage, triggering GC stutters under heavy spawning. Pooling reuses a fixed set of objects (SetActive on/off), eliminating that churn."
}
]
← Phase 6: Physics & Collisions · Guide overview · Phase 8: UI, Audio & Building →
Check your understanding
1. What does Instantiate(pickupPrefab, pos, Quaternion.identity) return?
2. You hit Play and get 'UnassignedReferenceException: the variable pickupPrefab has not been assigned.' What's the fix?
3. Why prefer object pooling over Instantiate/Destroy when spawning many objects fast?