Summary
A prefab is a reusable GameObject asset stored in the project. Any number of identical instances can be created from it at runtime using Instantiate. This is the standard pattern for pickups, enemies, projectiles, and any object that appears more than once in a scene. The spawning script holds a reference to the prefab (assigned in the Inspector), calls Instantiate to create instances, and tracks them in a List<GameObject> so they can be reconfigured or destroyed later.
Key ideas
Prefab: A saved GameObject asset in the Project window. Editing the prefab asset updates all scene instances simultaneously. Instances can be overridden individually without affecting the prefab.
Instantiate(prefab, position, rotation): Creates a new copy of the prefab in the scene at the given position and rotation. Returns a GameObject reference to the new instance. Quaternion.identity means no rotation (the object spawns upright).
List<GameObject>: A resizable list from System.Collections.Generic. Better than an array for spawning because the size is not known in advance. Use list.Add(obj) to append, list.Remove(obj) to remove by reference, list.RemoveAt(i) to remove by index, list.Count for the current size.
transform.SetParent(container): Parents the spawned object under a container transform. Keeps the Hierarchy organised — all spawned coins live under a single ”--- Coins ---” folder rather than cluttering the root.
Iterating backwards to remove: When looping through a list and removing by index (RemoveAt(i)), loop from the last index down to zero. Iterating forwards while removing causes elements to be skipped because the indices shift after each removal.
Remove before Destroy: Remove the item from the tracking list before calling Destroy. This prevents the list from holding a dead reference to a destroyed object.
In practice
Inspector-assigned prefab field:
[Header("Prefab to spawn")]
[SerializeField] private GameObject coinPrefab;
[Header("Spawn settings")]
[SerializeField] private int spawnCount = 5;
[SerializeField] private float spacingX = 1.5f;Spawning a row of prefab instances:
using System.Collections.Generic;
private List<GameObject> spawnedCoins = new List<GameObject>();
private Transform coinContainer;
private void Start()
{
// Create a container object to keep the Hierarchy tidy
coinContainer = new GameObject("--- Coins ---").transform;
}
public void SpawnAll()
{
if (coinPrefab == null)
{
Debug.LogError("[PrefabSpawner] coinPrefab not assigned!");
return;
}
float startX = transform.position.x - (spawnCount - 1) * spacingX * 0.5f;
for (int i = 0; i < spawnCount; i++)
{
Vector3 spawnPos = new Vector3(startX + i * spacingX, transform.position.y, 0f);
// Create an instance; Quaternion.identity = no rotation
GameObject coin = Instantiate(coinPrefab, spawnPos, Quaternion.identity);
coin.transform.SetParent(coinContainer); // keep Hierarchy tidy
coin.name = $"Coin_{i:D2}"; // descriptive name for debugging
spawnedCoins.Add(coin); // track it
}
Debug.Log($"[PrefabSpawner] Spawned {spawnedCoins.Count} coins.");
}Reading SpawnAll:
coinPrefabis not created in code. It is assigned in the Inspector, so a null check comes beforeInstantiate.startXcalculates where the first coin should appear so the full row is centred around the spawner.- The
forloop runs once per coin.iis both the loop counter and part of the coin name. Instantiate(coinPrefab, spawnPos, Quaternion.identity)creates the scene instance and returns a reference to it.SetParent(coinContainer)keeps the Hierarchy readable without changing which prefab was spawned.spawnedCoins.Add(coin)stores the new instance so later code can destroy or recolour it.
The important distinction is prefab asset versus scene instance. coinPrefab points to the reusable asset. coin points to one spawned copy in the current scene.
Despawning all tracked instances:
public void DespawnAll()
{
// Iterate backwards — safe when removing by index
for (int i = spawnedCoins.Count - 1; i >= 0; i--)
{
Destroy(spawnedCoins[i]);
spawnedCoins.RemoveAt(i);
}
Debug.Log("[PrefabSpawner] All coins despawned.");
}Removing a single coin on collection:
public void OnCoinCollected(GameObject coin)
{
if (gameManager != null)
gameManager.AddScore(coinValue);
spawnedCoins.Remove(coin); // remove from list BEFORE Destroy
Destroy(coin);
if (spawnedCoins.Count == 0)
Debug.Log("[PrefabSpawner] Wave complete.");
}Batch-configuring spawned instances via GetComponent:
public void BatchSetColour(Color colour)
{
foreach (GameObject coin in spawnedCoins)
{
SpriteRenderer sr = coin.GetComponent<SpriteRenderer>();
if (sr != null)
sr.color = colour;
}
}Gotchas
- Always check
if (coinPrefab == null)inStartor beforeInstantiate. An unassigned prefab field only fails whenInstantiateis called (Unity reports that the object you want to instantiate is null), not when the scene loads — so the error can be hard to trace back to the empty Inspector field. Instantiatehas generic overloads that return the same type you pass in: “You can use generic types to instantiate objects so you don’t have to cast the result to a specific type” (Unity Technologies, Scripting API: Object.Instantiate). Passing aGameObjectprefab therefore returns aGameObject, which is why the code above needs no cast. Older tutorials that writeas GameObjectwere using the non-generic overload, which returnsObject. (This gotcha previously saidInstantiatealways returnsObject; corrected 2026-10-25.)- Never hold a list reference to a destroyed object. Call
list.Remove(obj)orlist.RemoveAt(i)beforeDestroy(obj), or the list will contain a null entry that throws on the next iteration. - When a prefab is edited in the Project window, all scene instances update automatically — unless an instance has been overridden in the Inspector (shown in bold). Be aware of this when debugging unexpected behaviour on a single instance.
SetParent(parent, true)preserves world position.SetParent(parent, false)treats the object position as local to the parent. Use the default carefully when a spawned object appears in the wrong place after parenting.
Worked example
The beginner 2D route spawns coins at hand-placed spawn points rather than in a row:
- PickupSpawner.cs — loops over a
Transform[]of spawn points, skips any that are missing with a warning, instantiates the coin prefab at each, parents it under apickupParent, and passes each new coin the scene’sGameManager - CoinPickup.cs — the script on the spawned coins; its
SetGameManagermethod receives that reference, because a prefab asset cannot store a reference to an object in the scene (scene set-up in the folder’s README)
Practice
Build a PrefabSpawner from the snippets on this page and drive it from the keyboard:
- S calls
SpawnAll()to make a centred row of five coins - D calls
DespawnAll() - C calls
BatchSetColourwith a colour of your choice
Success test:
- the row is centred on the spawner and the coins are named
Coin_00toCoin_04 - every spawned coin sits under the
--- Coins ---container in the Hierarchy - after D, the Hierarchy has no coins and
spawnedCoins.Countlogs0 - pressing S with the prefab field empty logs your error message instead of crashing
Extension:
- give the coin a trigger collider that calls
OnCoinCollected(gameObject)on the spawner, and log “Wave complete” when the last coin is collected
Self-test
- What does
Instantiate(coinPrefab, spawnPos, Quaternion.identity)return, and why does the spawner keep it? - Why is a
List<GameObject>a better fit than an array for spawned objects? - Why does
DespawnAllloop from the last index down to zero? - Why should a coin be removed from the list before it is destroyed?
- What does
Quaternion.identitymean? - A coin appears in the wrong place after
SetParent. What does the second argument ofSetParentcontrol?
Answers
- A reference to the new scene instance. The spawner adds it to
spawnedCoinsso it can recolour or destroy that coin later. - The number of spawned objects is not known in advance, and a list can grow and shrink with
Add,RemoveandRemoveAt. - Removing by index shifts the later items down; looping forwards would skip elements, looping backwards does not.
- So the list never holds a reference to a destroyed object, which would throw on the next loop.
- No rotation: the object spawns upright.
- Whether the world position is kept (
true) or the object’s position is treated as local to the new parent (false).
Related
- unity-prefabs — the editor and asset workflow behind the prefab instances used here
- unity-inspector-references — assigning the prefab field and GameManager reference
- unity-getcomponent — accessing components on spawned instances
- unity-object-communication — notifying GameManager when a coin is collected
- unity-collider2d-and-triggers — coin detection on the spawned instances
- csharp-variables-and-types —
List<GameObject>and array types - monobehaviour-lifecycle — Spawn timing: what runs during Instantiate (Awake/OnEnable vs Start)