What Is It?
The Memento pattern has three roles. The originator is the object whose state matters (a Player). The memento is an immutable snapshot of that state. The caretaker stores mementos and hands one back when asked, but never looks inside.
The key idea is encapsulation: only the originator knows how to build a memento from its fields and how to apply one back. A save system, checkpoint manager or rewind feature can keep dozens of snapshots without depending on what a player actually contains.
In a Unity game this looks like player.Save() returning a PlayerMemento record with position, health and level, a SaveHistory caretaker that keeps a list of them, and player.Restore(memento) snapping everything back when the player dies or loads a checkpoint.
When Is It Used?
Use it for checkpoints, quick-save/quick-load, undo in editors, or a short “rewind time” mechanic where you need to return an object to an earlier state.
It is especially useful when the state is private: the originator stays in charge of what is captured, so you do not have to add public setters just so a save system can poke at it.
Be careful with large or frequently changing state. Snapshotting a whole level every frame is expensive; store deltas, sample less often or limit the history length instead.
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.Behavioral.Memento
{
/// <summary>
/// Client: saves and loads checkpoints. It shuttles mementos
/// between the player and the history without inspecting them.
/// </summary>
public class CheckpointTester : MonoBehaviour
{
[SerializeField] private Player player;
private readonly SaveHistory history = new(capacity: 5);
private void Update()
{
if (Input.GetKeyDown(KeyCode.F5))
{
PlayerMemento snapshot = player.Save();
history.Push(snapshot);
Debug.Log($"Saved {snapshot}");
}
if (Input.GetKeyDown(KeyCode.F9) && history.Latest != null)
{
player.Restore(history.Latest);
Debug.Log($"Restored {history.Latest}");
}
// Mess with the state so there is something to restore.
if (Input.GetKeyDown(KeyCode.D)) player.TakeDamage(25);
if (Input.GetKeyDown(KeyCode.L)) player.LevelUp();
if (Input.GetKeyDown(KeyCode.M)) player.Move(Random.insideUnitSphere * 3f);
}
}
}using UnityEngine;
namespace Patterns.Behavioral.Memento
{
/// <summary>
/// Immutable snapshot of the player's state. Once created it can
/// never change, so stored history is always trustworthy.
/// </summary>
public sealed class PlayerMemento
{
public Vector3 Position { get; }
public int Health { get; }
public int Level { get; }
public float Timestamp { get; }
public PlayerMemento(Vector3 position, int health, int level)
{
Position = position;
Health = health;
Level = level;
Timestamp = Time.time;
}
public override string ToString() =>
$"[{Timestamp:0.0}s] pos {Position}, hp {Health}, lvl {Level}";
}
}using UnityEngine;
namespace Patterns.Behavioral.Memento
{
/// <summary>
/// Originator. Only the player knows how to turn its state into a
/// memento and how to apply one back.
/// </summary>
public class Player : MonoBehaviour
{
[SerializeField] private int maxHealth = 100;
private int health;
private int level = 1;
public int Health => health;
public int Level => level;
private void Awake() => health = maxHealth;
public void TakeDamage(int amount) => health = Mathf.Max(0, health - amount);
public void LevelUp() => level++;
public void Move(Vector3 delta) => transform.position += delta;
public PlayerMemento Save()
{
return new PlayerMemento(transform.position, health, level);
}
public void Restore(PlayerMemento memento)
{
transform.position = memento.Position;
health = memento.Health;
level = memento.Level;
}
}
}using System.Collections.Generic;
namespace Patterns.Behavioral.Memento
{
/// <summary>
/// Caretaker. Stores snapshots and hands them back on request,
/// but never reads or modifies their contents.
/// </summary>
public class SaveHistory
{
private readonly List<PlayerMemento> slots = new();
private readonly int capacity;
public SaveHistory(int capacity = 5) => this.capacity = capacity;
public int Count => slots.Count;
public void Push(PlayerMemento memento)
{
if (slots.Count == capacity)
slots.RemoveAt(0); // forget the oldest save
slots.Add(memento);
}
public PlayerMemento Get(int index) => slots[index];
public PlayerMemento Latest => slots.Count > 0 ? slots[^1] : null;
}
}Advantages & Disadvantages
+ Advantages
- Preserves encapsulation — the caretaker treats snapshots as opaque tokens.
- Immutable snapshots are safe to share and easy to reason about.
- Simplifies the originator: no undo bookkeeping, just
Save()andRestore().
− Disadvantages
- Snapshots cost memory; long histories of large objects add up quickly.
- References to other objects (targets, inventory items) are tricky to snapshot and restore correctly.
- The caretaker cannot tell what changed between snapshots without help from the originator.
Tips
- 01Use a C#
recordor areadonly structfor the memento so nobody can mutate a saved state by accident. - 02Cap the history with a ring buffer for rewind features; drop the oldest snapshot when it is full.
- 03For save files, serialize the memento with
JsonUtility— it is already a plain data object. - 04Store IDs instead of object references inside a memento, and resolve them on restore.
- 05Combine with Command: a command can take a memento before executing and restore it in
Undo()when the action is hard to reverse by hand.