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
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}");
}
}
}namespace Patterns.Creational.Prototype
{
/// <summary>
/// Strongly typed alternative to System.ICloneable.
/// Implementations promise a deep, independent copy.
/// </summary>
public interface IPrototype<T>
{
T Clone();
}
}using System;
using System.Collections.Generic;
using UnityEngine;
namespace Patterns.Creational.Prototype
{
/// <summary>
/// Plain C# enemy configuration. Clone() is deep: the abilities list
/// is copied, so clones never share it with the prototype.
/// </summary>
[Serializable]
public class EnemyData : IPrototype<EnemyData>, ICloneable
{
public string name = "Slime";
public Color color = Color.green;
public float size = 1f;
public int health = 30;
public List<string> abilities = new() { "Bounce" };
/// <summary>Deep copy: value fields via MemberwiseClone, then new collections.</summary>
public EnemyData Clone()
{
var copy = (EnemyData)MemberwiseClone();
copy.abilities = new List<string>(abilities);
return copy;
}
/// <summary>
/// Shallow copy, shown for contrast. The returned object shares
/// the same abilities list instance as this one.
/// </summary>
public EnemyData ShallowClone() => (EnemyData)MemberwiseClone();
object ICloneable.Clone() => Clone();
public override string ToString() =>
$"{name} hp:{health} size:{size:0.0} [{string.Join(", ", abilities)}]";
}
}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
Instantiategive 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()returnsobjectand does not say whether it is deep or shallow.
Tips
- 01Prefer a generic
IPrototype<T>withT Clone()overICloneable— it is typed and you can document that it is a deep copy. - 02Start from
MemberwiseClone()for the value fields, then explicitlynewevery collection and nested reference type. - 03For MonoBehaviours, just
Instantiate(configuredObject); Unity deep-copies serialized fields and child objects for you. - 04Treat ScriptableObjects as shared data; if each clone needs its own mutable copy,
Instantiatethe ScriptableObject too. - 05Write a quick test: mutate a clone and assert the prototype is unchanged — it catches shallow-copy regressions immediately.