← All patterns
Creational

Prototype

Create new objects by copying a configured example instead of building each one from scratch.

What Is It?

With the Prototype pattern you set up one object exactly the way you want — colour, size, stats, loadout — and then produce new objects by cloning it. The clone starts as a copy and can be tweaked independently.

The subtle part is shallow vs deep copying. MemberwiseClone() copies fields one by one: value types are duplicated, but reference types such as a List<string> of abilities are shared. Mutate the list on a clone and every sibling (and the prototype) sees the change. A deep clone duplicates those inner objects too.

Unity already ships the most famous prototype implementation: Instantiate(original). Every prefab is a prototype, and instantiating a configured scene object copies its whole hierarchy and serialized fields. For plain C# data you implement cloning yourself.

When Is It Used?

Use it when an object is expensive or fiddly to configure and you need many similar copies: a tuned elite enemy, a bullet with modifiers, a procedurally generated item to duplicate.

It is a natural fit for "spawn more like this" mechanics — splitting slimes, mirror images, copy/paste in a level editor — where the source object is chosen at runtime.

Avoid hand-written cloning for deep, cyclic object graphs; it is easy to miss a field. Prefer serialisation-based copies or redesign towards immutable data.

Interactive Demo

Click the controls and watch the objects collaborate. The console mirrors what the C# code below would log.

Code

Assets / Scripts / Creational/ Prototype ›CloneSpawner.cs
using UnityEngine;

namespace Patterns.Creational.Prototype
{
    /// <summary>
    /// Client. Uses Instantiate (Unity's built-in prototype) for the
    /// GameObject, and EnemyData.Clone() for its plain C# data.
    /// </summary>
    public class CloneSpawner : MonoBehaviour
    {
        [SerializeField] private GameObject prototypeObject; // a configured scene object or prefab
        [SerializeField] private EnemyData prototypeData = new();
        [SerializeField] private float spread = 3f;

        private void Update()
        {
            if (Input.GetKeyDown(KeyCode.C))
                SpawnClone();
        }

        private void SpawnClone()
        {
            Vector3 offset = Random.insideUnitSphere * spread;
            offset.y = 0f;

            // 1. Unity copies the whole hierarchy and serialized fields.
            GameObject go = Instantiate(prototypeObject, transform.position + offset, Quaternion.identity);
            go.transform.localScale = Vector3.one * prototypeData.size;

            // 2. Our own deep copy for runtime data.
            EnemyData data = prototypeData.Clone();
            data.abilities.Add("Split"); // safe: prototype's list is untouched

            if (go.TryGetComponent(out Renderer r))
                r.material.color = data.color;

            Debug.Log($"Clone: {data}");
            Debug.Log($"Prototype: {prototypeData}");
        }
    }
}
3 files · namespace Patterns.Creational.PrototypeC# · UTF-8 · LF

Advantages & Disadvantages

+ Advantages

  • New objects inherit a runtime-configured state without knowing how it was built.
  • Fewer subclasses: variants are just differently configured prototypes.
  • In Unity, prefabs plus Instantiate give you the pattern for free with editor tooling.

− Disadvantages

  • Shallow copies share references, causing spooky action-at-a-distance bugs.
  • Deep cloning must be kept in sync by hand whenever a new reference field is added.
  • ICloneable.Clone() returns object and does not say whether it is deep or shallow.

Tips

  1. 01Prefer a generic IPrototype<T> with T Clone() over ICloneable — it is typed and you can document that it is a deep copy.
  2. 02Start from MemberwiseClone() for the value fields, then explicitly new every collection and nested reference type.
  3. 03For MonoBehaviours, just Instantiate(configuredObject); Unity deep-copies serialized fields and child objects for you.
  4. 04Treat ScriptableObjects as shared data; if each clone needs its own mutable copy, Instantiate the ScriptableObject too.
  5. 05Write a quick test: mutate a clone and assert the prototype is unchanged — it catches shallow-copy regressions immediately.