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) in Start or before Instantiate. A missing prefab reference throws a NullReferenceException at spawn time, not at assignment time — it can be hard to trace back to the Inspector.
  • Instantiate returns Object, not GameObject. Assign it to a GameObject variable (as above) to access transform, GetComponent, etc. without casting.
  • Never hold a list reference to a destroyed object. Call list.Remove(obj) or list.RemoveAt(i) before Destroy(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.