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.");
}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. A missing prefab reference throws aNullReferenceExceptionat spawn time, not at assignment time — it can be hard to trace back to the Inspector. InstantiatereturnsObject, notGameObject. Assign it to aGameObjectvariable (as above) to accesstransform,GetComponent, etc. without casting.- 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.
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