← All patterns
Behavioral

Command

Wrap an action in an object so it can be queued, logged, replayed or undone.

What Is It?

The Command pattern turns a request into a standalone object. Instead of calling unit.Move(Vector2Int.up) directly, you create a MoveCommand that remembers who to act on and what to do, then hand it to an invoker that decides when to run it.

Every command implements the same tiny interface — usually Execute() and Undo(). Because the invoker only sees ICommand, it can push commands onto a history stack, pop them to undo, and push them onto a redo stack without knowing anything about movement, building or inventory.

Think of a tactics game on a grid: each tile step the player takes becomes a MoveCommand. Undo walks the unit back exactly one tile, redo replays it, and the same history can drive a replay system or a network log of player inputs.

When Is It Used?

Use it when actions must be reversible: level editors, puzzle games, turn-based tactics, and anything with an undo button.

It is also a natural fit for input remapping, macro recording, deterministic replays and sending player intent over the network, since a command is just data plus a method.

Skip it for continuous, per-frame actions such as analog movement in an action game. Wrapping every Update tick in an object adds garbage and complexity with little payoff.

Interactive Demo

Click the controls and watch the objects collaborate. The console mirrors what the C# code below would log.

Code

Assets / Scripts / Behavioral/ Command ›InputHandler.cs
using UnityEngine;

namespace Patterns.Behavioral.Command
{
    /// <summary>
    /// Client: turns key presses into command objects and hands them
    /// to the invoker. It never moves the unit itself.
    /// </summary>
    public class InputHandler : MonoBehaviour
    {
        [SerializeField] private Transform unit;
        [SerializeField] private RectInt board = new(0, 0, 5, 5);

        private readonly CommandInvoker invoker = new();

        private void Update()
        {
            if (Input.GetKeyDown(KeyCode.W)) Move(Vector2Int.up);
            if (Input.GetKeyDown(KeyCode.S)) Move(Vector2Int.down);
            if (Input.GetKeyDown(KeyCode.A)) Move(Vector2Int.left);
            if (Input.GetKeyDown(KeyCode.D)) Move(Vector2Int.right);

            if (Input.GetKeyDown(KeyCode.Z)) invoker.Undo();
            if (Input.GetKeyDown(KeyCode.Y)) invoker.Redo();
        }

        private void Move(Vector2Int direction)
        {
            invoker.Execute(new MoveCommand(unit, direction, board));
        }
    }
}
4 files · namespace Patterns.Behavioral.CommandC# · UTF-8 · LF

Advantages & Disadvantages

+ Advantages

  • Undo and redo become a matter of two stacks instead of special-case code everywhere.
  • Decouples the code that triggers an action (input, AI, UI) from the code that performs it.
  • Commands are data: they can be queued, delayed, serialized, logged or replayed.

− Disadvantages

  • Each action needs its own class, which can mean a lot of small files.
  • Undo must restore state exactly; commands that touch physics, randomness or other systems are hard to reverse correctly.
  • Long histories hold references to objects, so you may need to cap the stack or clear it when objects are destroyed.

Tips

  1. 01Clear the redo stack whenever a brand-new command is executed — otherwise redo can replay a branch of history that no longer makes sense.
  2. 02Store the result of an action in the command (e.g. the position before moving) so Undo does not have to recompute anything.
  3. 03Validate before executing: if a move is blocked, do not push it onto the history at all.
  4. 04For replays, record the frame number alongside each command and re-execute them in order from a known starting state.
  5. 05Keep commands plain C# classes rather than MonoBehaviours so they are cheap to allocate and easy to test.