Quick Start: CommandEngine
The CommandEngine is a complete system for deterministic turn/tick-based games with built-in replay, recording, scheduling, and event broadcasting.
When to Use CommandEngine
- Turn-based games (card games, strategy games)
- Tick-based simulations
- Deterministic gameplay with seeded randomness
- Full command recording and playback
For simple undo/redo without these features, see the CommandHandler Quick Start.
Core Components
The CommandEngine combines four powerful subsystems:
- CommandHandler - Executes commands with undo/redo
- CommandScheduler - Schedules commands for specific ticks
- CommandRecorder - Records all commands for replay
- SeededRandomSource - Deterministic random generation
Installation
Import the DCE asset from the Unity Asset Store into your project.
Step 1: Choose Execution Mode
public enum ExecutionMode
{
TurnBased, // Commands execute immediately, tick advances per command
TickBased // Commands scheduled by tick, manual Tick() calls required
}TurnBased: Ideal for card games, board games, RPG battles
TickBased: Ideal for real-time simulations, physics-based games
Step 2: Initialize CommandEngine
using JFCreative.DCE.Core;
using UnityEngine;
public class GameManager : MonoBehaviour
{
private CommandEngine _engine;
void Awake()
{
int seed = CommandEngine.GenerateRandomSeed();
_engine = new CommandEngine(
mode: CommandEngine.ExecutionMode.TurnBased,
seed: seed,
maxCommandHistory: -1,
debug: true
);
}
void FixedUpdate()
{
if (_engine.Mode == CommandEngine.ExecutionMode.TickBased)
{
_engine.Tick();
}
}
}Step 3: Create Commands
Commands are identical to CommandHandler examples:
using JFCreative.DCE.Core;
using System;
public class PlayCardCommand : ICommand
{
private CardGamePlayer _player;
private Card _card;
private int _previousHealth;
public PlayCardCommand(CardGamePlayer player, Card card)
{
_player = player;
_card = card;
}
public void Execute(Action executedCallback)
{
_previousHealth = _player.Health;
_card.ApplyEffect(_player);
executedCallback?.Invoke();
}
public void Undo()
{
_player.Health = _previousHealth;
}
public void Redo() => Execute(null);
}Step 4: Execute Commands
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
_engine.Execute(new PlayCardCommand(currentPlayer, selectedCard));
}
}Important: The engine automatically records all executed commands for replay.
Deterministic Randomness
Always use the engine's Random property for deterministic behavior:
var damage = _engine.Random.Range(1, 10);
var chance = _engine.Random.Value();DO NOT use UnityEngine.Random - it breaks replay determinism!
Replay System
Start Replay (Async)
public async void StartReplay()
{
_engine.SetReplaySpeed(CommandEngine.ReplaySpeed.Fast);
bool success = await _engine.StartReplayAsync();
if (success)
{
Debug.Log("Replay completed!");
}
}Replay Speeds
- CommandEngine.ReplaySpeed.Instant - No delay (0ms)
- CommandEngine.ReplaySpeed.Fast - 4x speed (250ms)
- CommandEngine.ReplaySpeed.Normal - 1x speed (1000ms)
- CommandEngine.ReplaySpeed.Slow - 0.5x speed (2000ms)
Stop Replay
_engine.StopReplay();
Event System
Publish and subscribe to game events without tight coupling:
public struct TurnEndedEvent
{
public CardGamePlayer CurrentPlayer;
public CardGamePlayer NextPlayer;
}
void Awake()
{
_engine.Events.Subscribe<TurnEndedEvent>(OnTurnEnded);
}
void OnTurnEnded(TurnEndedEvent evt)
{
Debug.Log($"{evt.NextPlayer.Name}'s turn!");
}
public void Execute(Action executedCallback)
{
_engine.Events.Publish(new TurnEndedEvent
{
CurrentPlayer = _current,
NextPlayer = _next
});
executedCallback?.Invoke();
}
void OnDestroy()
{
_engine.Events.Unsubscribe<TurnEndedEvent>(OnTurnEnded);
}Undo/Redo During Gameplay
void Update()
{
if (Input.GetKeyDown(KeyCode.Z))
{
_engine.Undo();
}
if (Input.GetKeyDown(KeyCode.Y))
{
_engine.Redo();
}
}Note: Undo/redo is disabled during replay mode.
Reset and Clear
_engine.Reset(CommandEngine.GenerateRandomSeed());
_engine.Reset();
_engine.ClearRecording();Sample: Card Game
Check out the included Card Game Sample for a complete example:
void Awake()
{
var seed = CommandEngine.GenerateRandomSeed();
_commandEngine = new CommandEngine(
CommandEngine.ExecutionMode.TurnBased,
seed,
-1,
false
);
}
public void PlayerPlaysCard(CardGamePlayer owner, Card card)
{
_commandEngine.Execute(new PlayCardCommand(owner, GetOpponent(owner), card, this));
}
public void PlayerEndsTurn(CardGamePlayer player)
{
_commandEngine.Execute(new EndTurnCommand(this, _commandEngine.Events, player));
}Best Practices
Do's
- Always use _engine.Random for randomness
- Call executedCallback?.Invoke() when execution completes
- Use events for decoupled system communication
- Test replays frequently during development
Don'ts
- Don't run other commands from inside a command's Execute() method (breaks replay)
- Don't use UnityEngine.Random (breaks determinism)
- Don't store references to Unity objects that might be destroyed (store id's or indexes)
Composite Commands Pattern
For multi-step operations, create composite commands:
var setPositionCommand = new SetPropertyCommand<PuzzleBox, Vector3>(
this,
x => x.transform.position,
transform.position + direction
);
var setRotationCommand = new SetPropertyCommand<PuzzleBox, Quaternion>(
this,
x => x.transform.rotation,
Quaternion.LookRotation(direction)
);
var cmd = new CompositeCommand(setPositionCommand, setRotationCommand);
engine.Execute(cmd);Next Steps
- Explore the Card Game Sample for a complete implementation and TickBased scheduling
- Check out the Puzzle Game Sample for TurnBased scheduling
- Implement your own commands for game-specific logic
- Set up replay UI and controls
- Test determinism by comparing multiple replays
Need simpler undo/redo? Check out the CommandHandler Quick Start for lightweight command execution without replay features.