Core Concepts
Understanding these core concepts will help you make the most of DCE and avoid common pitfalls.
This page explains the foundational ideas behind command pattern architecture and deterministic execution.
What is a Command?
A Command is an object that encapsulates a single action or operation in your game.
Instead of directly modifying game state, you create a command object that knows how to:
Execute → Perform the action Undo → Reverse the action Redo → Perform the action again
Example: Moving a Character
❌ Without Commands (Direct Execution)
void MovePlayer(Vector3 newPosition)
{
player.transform.position = newPosition;
// No way to undo this!
}✅ With Commands (Reversible Execution)
public class MoveCommand : ICommand
{
private Transform _target;
private Vector3 _oldPosition;
private Vector3 _newPosition;
public MoveCommand(Transform target, Vector3 newPosition)
{
_target = target;
_oldPosition = target.position;
_newPosition = newPosition;
}
public void Execute(Action executedCallback)
{
_target.position = _newPosition;
executedCallback?.Invoke();
}
public void Undo()
{
_target.position = _oldPosition;
}
public void Redo()
{
_target.position = _newPosition;
}
}Key Benefits
Undo/Redo → Every action can be reversed Recording → All actions can be saved and replayed Scheduling → Commands can execute now or later Testing → Easy to test individual operations Networking → Commands can be sent over the network
What is a CommandHandler?
The CommandHandler is the system that manages command execution and history.
Think of it as a "traffic controller" for all your game actions.
Responsibilities
- Execute commands
- Track history
- Undo/Redo
- Limit history
Example Usage
private CommandHandler _handler;
void Awake()
{
var tickSource = new SystemTickSource();
_handler = new CommandHandler(
tickSource: tickSource,
recorder: null,
maxCommands: 50,
debug: true
);
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
_handler.Execute(new MoveCommand(player, targetPosition));
}
if (Input.GetKeyDown(KeyCode.Z))
{
_handler.Undo();
}
if (Input.GetKeyDown(KeyCode.Y))
{
_handler.Redo();
}
}What is Command History?
Command history is the ordered list of all executed commands.
[Command 1] → [Command 2] → [Command 3] → [Command 4] ↑ Current Position How Undo / Redo Works
Undo:
[Command 1] → [Command 2] → [Command 3] → [Command 4] ↑ Move backward
Redo:
[Command 1] → [Command 2] → [Command 3] → [Command 4] ↑ Move forward
History Limits
new CommandHandler(tickSource, null, maxCommands: 10, debug: false);When the limit is reached, the oldest command is removed.
What is Determinism?
Determinism means:
The same inputs always produce the same outputs.
This is essential for:
- Replay systems
- Multiplayer synchronization
- Bug reproduction
- Automated testing
The Problem: Non-Deterministic Randomness
❌ Wrong (Breaks Replay)
public void Execute(Action executedCallback)
{
int damage = UnityEngine.Random.Range(1, 10);
player.TakeDamage(damage);
executedCallback?.Invoke();
}✅ Correct (Deterministic)
public void Execute(Action executedCallback)
{
int damage = _engine.Random.Range(1, 10);
player.TakeDamage(damage);
executedCallback?.Invoke();
}Seeded Randomness
// Game 1
var engine = new CommandEngine(ExecutionMode.TurnBased, seed: 12345);
var roll1 = engine.Random.Range(1, 6); // 4
var roll2 = engine.Random.Range(1, 6); // 2
var roll3 = engine.Random.Range(1, 6); // 5
// Game 2 (same seed)
var engine2 = new CommandEngine(ExecutionMode.TurnBased, seed: 12345);
var roll1b = engine2.Random.Range(1, 6); // 4
var roll2b = engine2.Random.Range(1, 6); // 2
var roll3b = engine2.Random.Range(1, 6); // 5Same seed = same results.
Execution Modes
- Immediate (CommandHandler)
var handler = new CommandHandler(tickSource, null, -1, true);
handler.Execute(new MoveCommand(player, newPos));✅ Undo/Redo ❌ No recording ❌ No replay
- Recorded (TurnBased)
var engine = new CommandEngine(ExecutionMode.TurnBased, seed: 12345);
engine.Execute(new PlayCardCommand(player, card));✅ Undo/Redo ✅ Recording ✅ Replay
- Scheduled (TickBased)
var engine = new CommandEngine(ExecutionMode.TickBased, seed: 12345);
engine.Scheduler.Schedule(new SpawnEnemyCommand(), tick: 100);
void FixedUpdate()
{
engine.Tick();
}✅ Scheduled execution ✅ Replay ✅ Full control
Recording vs Replay
Recording
engine.Execute(new PlayCardCommand(player1, card1));
engine.Execute(new PlayCardCommand(player2, card2));
engine.Execute(new EndTurnCommand());Replay
await engine.StartReplayAsync();Plays all commands back Uses same seed Produces identical results
Common Pitfalls
❌ Using UnityEngine.Random
int damage = UnityEngine.Random.Range(1, 10);✅ Fix
int damage = engine.Random.Range(1, 10);❌ Storing Unity References
private GameObject _enemy;✅ Fix
private int _enemyId;❌ Executing Commands Inside Commands
_engine.Execute(new OtherCommand());✅ Fix
Use composite commands instead.
❌ Forgetting Callback
public void Execute(Action executedCallback)
{
player.Move(position);
}✅ Fix
executedCallback?.Invoke();Next Steps
- 📖 Read the Quick Start Guides
- 🎮 Explore the included sample games
- 💬 Join our Discord community (opens in a new tab)
Understanding these fundamentals will help you build robust, maintainable, and replay-ready systems with DCE 🚀