CSCI 3213 - Game Programming
Encapsulating actions as objects - recording the past!
Implementing a Replay System with the Command Pattern
From: Game Development Patterns with Unity 2021 (2nd Edition)
By David Baron
Goal: Build a replay system that records bike movements and plays them back perfectly, frame by frame.
As we implement this pattern, we'll be creating multiple files in a specific order. You will see errors in Unity and your IDE until all files are complete.
As Apollo 13's Fred Haise said: "Never Panic Early"
This is a critical skill for software development. Note problems, but stay focused on the implementation path.
Problems: No recording capability, can't undo, can't replay, tightly coupled to Input system
A way to capture player actions, store them, and replay them exactly as they happened!
A behavioral pattern that encapsulates a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations.
Turn actions into objects! Instead of calling methods directly, create command objects that can be stored, queued, and executed later.
ââââââââââââââââââââ
â Invoker â â Triggers commands (TestPanel)
ââââââââââââââââââââ
â + Execute() â
ââââââââââŦââââââââââ
â uses
âŧ
ââââââââââââââââââââ âââââââââââââââââââ
â ICommand âââââââââââ BikeController â â Receiver
ââââââââââââââââââââ âââââââââââââââââââ
â + Execute() â â + TurnLeft() â
ââââââââââŦââââââââââ â + TurnRight() â
â implements âââââââââââââââââââ
â
ââââââ´âââââââââââââââââ
âŧ âŧ
ââââââââââââââââ ââââââââââââââââ
â TurnLeft â â TurnRight â â Concrete Commands
ââââââââââââââââ ââââââââââââââââ
â + Execute() â â + Execute() â
ââââââââââââââââ ââââââââââââââââ
Invoker: Handles recording/playback state
Commands: Store action + timestamp
Receiver: BikeController executes actions
Create a new file: Assets/Scripts/Patterns/Command/ICommand.cs
Base interface that all command classes must implement.
â ī¸ This code goes into your Unity project for Blade Racer.
Contract: Any class implementing ICommand
must have an Execute() method. The Invoker doesn't care
what the command does - just that it can be executed.
Polymorphism: Store TurnLeft, TurnRight, Jump, Fire -
all in one List<ICommand>. The list doesn't know or
care about specific types!
Decoupling: The Invoker only depends on ICommand, not on concrete classes. Add new commands without changing Invoker code.
Create a new file: Assets/Scripts/Patterns/Command/TurnLeft.cs
Command that turns the bike left when executed.
â ī¸ This code goes into your Unity project for Blade Racer.
âââââââââââââââ
â ICommand â â Interface (contract)
âââââââââââââââ
â + Execute() â
ââââââââŦâââââââ
â implements
âŧ
âââââââââââââââ âââââââââââââââ
â TurnLeft â â TurnRight â
âââââââââââââââ âââââââââââââââ
â _controller â â _controller â
â + Execute() â â + Execute() â
ââââââââŦâââââââ âââââââââââââââ
â calls
âŧ
âââââââââââââââââââ
â BikeController â â Receiver
âââââââââââââââââââ
â + Turn(dir) â
âââââââââââââââââââ
TurnLeft implements ICommand: This makes TurnLeft interchangeable with any other command (TurnRight, Accelerate, etc.).
Stores receiver reference: The command "knows" which BikeController to act on, passed via constructor.
Execute() delegates: When called, the command tells the receiver what to do. The caller never touches BikeController directly!
Create a new file: Assets/Scripts/Patterns/Command/TurnRight.cs
Command that turns the bike right when executed.
â ī¸ This code goes into your Unity project for Blade Racer.
New commands follow the same structure. Copy TurnLeft.cs and update
the highlighted spots:
1. Class name:
TurnLeft â TurnRight
2. Constructor name:
TurnLeft(...) â TurnRight(...)
3. Direction parameter:
Direction.Left â Direction.Right
This consistency is the power of the Command Pattern - adding new commands is predictable!
Create a new file: Assets/Scripts/Patterns/Command/Invoker.cs
Manages command execution, recording, and replay functionality.
â ī¸ This code goes into your Unity project for Blade Racer.
The Invoker is the "command manager" - it doesn't know what commands do, just how to execute and store them.
State Fields: Track if we're recording, replaying, and where we are in the replay sequence.
Tuple List:
List<(ICommand, float)> stores pairs of command + timestamp.
C# tuples let us group related data without creating a new class.
ExecuteCommand(): Always runs the command immediately, then saves it to the list if recording is active.
The Invoker accepts any ICommand - it doesn't know TurnLeft from TurnRight!
StopRecording() - Sets flag to stop saving commands
StartReplay() - Resets to beginning, stores start time
var - Type inference. Lets the compiler figure out
the types automatically instead of writing them explicitly.
(command, timestamp) - Tuple deconstruction.
Unpacks a tuple into two separate variables in one line.
Same as writing:
ICommand command = tuple.Item1;
float timestamp = tuple.Item2;
= _recordedCommands[_replayIndex] - Gets the tuple
at the current index. Each entry is (ICommand, float).
C# 7+ feature - cleaner than accessing .Item1 and .Item2!
Create a new file: Assets/Scripts/Enums/Direction.cs
Enum for turn directions (may already exist from State Pattern).
â ī¸ This code goes into your Unity project for Blade Racer.
Left = -1, Right = 1: These values map directly to movement direction. Cast to float and multiply by distance for instant lateral movement!
Shared Across Patterns: Used by State Pattern (BikeTurnState) and Command Pattern (TurnLeft/TurnRight). One definition, many uses.
Update existing file: Assets/Scripts/Controllers/BikeController.cs
Add Turn method if not already present from State Pattern.
â ī¸ This code goes into your Unity project for Blade Racer.
In the Command Pattern, BikeController is the Receiver - the object that actually performs the work.
Turn(Direction): A simple public method. Commands call this - BikeController doesn't know or care that commands exist!
Why This Matters: BikeController can be used with or without
the Command Pattern. Other systems can call Turn() directly if needed.
The Receiver is "dumb" about the pattern - it just does its job when asked!
Press 1: Start recording
Press A/D: Turn left/right (watch bike move)
Press 2: Stop recording
Press 3: Play back recording!
Add these fields and keyboard shortcuts to your existing TestPanel.cs.
New Fields: Store references to Invoker, BikeController, and pre-created command objects. Commands are created once in Start().
Start(): Find components in scene and create command instances. We reuse the same TurnLeft/TurnRight objects for all inputs.
Update(): Check for keypresses and route to Invoker. A/D execute turn commands, 1/2/3 control recording.
Add DrawCommandSection() and call it from DrawWindow().
Every TestPanel action has three parts:
1. GUI Button - GUILayout.Button("Turn Left (A)")
Visual click target in the TestPanel window
2. Key Command - Input.GetKeyDown(KeyCode.A)
Keyboard shortcut in Update() (previous slide)
3. Invoker Logic - _invoker.ExecuteCommand(_turnLeft)
Both button AND key trigger the same Invoker call!
Collapsible Header: Green toggle button expands/collapses section.
DrawCommandSection() in DrawWindow()!
Update the DrawKeymapWindow() method in TestPanel to include Command Pattern shortcuts.
As you progress through lectures, TestPanel grows:
Remember TestPanel.cs from the Event Bus lecture? Now we'll add Command Pattern support - keyboard shortcuts and GUI buttons for recording/replay.
TestPanel grows with each pattern you implement. By the end of the course, you'll have a comprehensive debug tool with collapsible sections for every system!
Interface Change: Add Undo() to ICommand.
Every command must now know how to reverse itself.
Reverse Logic: TurnLeft's Undo turns right! Each command's Undo does the opposite of Execute.
Replay Rewind: To play backwards, iterate the command
list in reverse order calling Undo() on each.
Implement the Command Pattern for replay:
In 1981, Walter Day founded Twin Galaxies, the world's first official video game scoreboard. For the first time, players could submit their high scores and be recognized globally. Life Magazine published the first official high score list in 1982, legitimizing competitive gaming.
But there was a problem: How do you prove a high score is real? Without recording systems, players had to submit photographs or have witnesses. This led to famous controversies like the Billy Mitchell vs. Steve Wiebe Donkey Kong rivalry (documented in "The King of Kong").
Modern competitive games like StarCraft use the Command Pattern to record every input as proof of skill. The replay file is just a list of commands executed with timestamps - exactly what we're building today!
StarCraft, League of Legends - record entire matches for playback
Photoshop, Unity Editor - undo/redo any action
Record player actions to train AI opponents
Record player movements to create in-game cutscenes
Log player actions for heatmaps and behavior analysis
Queue and execute actions in specific order
Combine multiple commands into one. Example: "Turbo Boost" = [SpeedUp, PlaySound, EmitParticles] all executed together!
Store commands in a queue, execute one per frame. Perfect for turn-based games or animation sequencing!
Serialize commands to send over network. Multiplayer games send command objects instead of raw input!
Due: Next class
Submission: Unity project + video to D2L
Homework Due Next Class:
Replay System + Pause + Rewind + 3-Scene Test
Video submission to D2L
Next: Object Pool Pattern for performance optimization! đ