The Command Pattern in Video Games: Mastering Flexibility with Unity and C#


Introduction: The Flexibility of Commands

You’re knee-deep in your favorite video game, directing your character as they traverse a complex world filled with challenges. Ever wondered how you could easily switch abilities, replay actions, or even undo mistakes? This is where the Command Pattern shines. Let’s delve into its intricacies and discover how Unity and C# can help you harness its power.



Decoding the Command Pattern

The Command Pattern encapsulates a request as an object, decoupling the sender from the receiver. This results in flexibility in terms of timing, queuing, and execution of requests.

Pseudo code:

// Command Interface
interface ICommand
{
    void Execute();
    void Undo();
}

// Concrete Command
class JumpCommand : ICommand
{
    void Execute() { /* Jump logic here */ }
    void Undo() { /* Undo jump logic here */ }
}

Why Store Commands in a Dictionary?

Storing commands in a dictionary, especially with a consistent identifier system, offers numerous advantages:

  1. Speed: Fetching commands from a dictionary using an identifier is fast.
  2. Flexibility: Easily swap commands, making features like ability switching or control re-mapping a breeze.
  3. Cleaner Code: Avoids hardcoding strings, reducing potential for errors.

Here’s an example of a command ID system:

public class PlayerCommandId
{
    public const string JumpCommand = "JumpCommand";
    public const string ShootCommand = "ShootCommand";
    public const string CrouchCommand = "CrouchCommand";
    public const string SprintCommand = "SprintCommand";
    public const string ReloadCommand = "ReloadCommand";
    public const string UseItemCommand = "UseItemCommand";
    public const string InteractCommand = "InteractCommand";
}

Using constants in a dedicated class avoids the pitfalls of raw strings, ensuring consistency and reducing the likelihood of typos.


Separation of Logic: The Real Power

When you separate commands from direct input handling, magic happens:

  1. Dynamic Abilities: Imagine a game where a player chooses abilities before starting. With our setup, switching abilities is as simple as re-mapping a button to a different command.
  2. Runtime Flexibility: Pick up a new skill or weapon in-game? Just swap out the command associated with a button.

Example:

Dictionary<string, ICommand> commands = new Dictionary<string, ICommand>
{
    { PlayerCommandId.JumpCommand, new JumpCommand() },
    { PlayerCommandId.ShootCommand, new ShootCommand() },
    { PlayerCommandId.CrouchCommand, new CrouchCommand() },
    { PlayerCommandId.SprintCommand, new SprintCommand() },
    { PlayerCommandId.ReloadCommand, new ReloadCommand() },
    { PlayerCommandId.UseItemCommand, new UseItemCommand() },
    { PlayerCommandId.InteractCommand, new InteractCommand() }
};

// Later in the game, for example, if the player picks up a jetpack
commands[PlayerCommandId.JumpCommand] = new JetpackFlyCommand();
  1. UI Integration: Linking UI buttons directly to commands makes UI logic straightforward. If a player selects a new ability, the UI button simply calls a different command.

Undo/Redo: Command Patternโ€™s Crown Jewel

With the Command Pattern, implementing undo/redo becomes a structured process. By maintaining a list, stack, or queue of commands, we can traverse through these commands to achieve undo and redo functionality.

Example:

List<ICommand> commandHistory = new List<ICommand>();

void ExecuteCommand(ICommand command)
{
    command.Execute();
    commandHistory.Add(command);
}

void UndoLastCommand()
{
    if(commandHistory.Count > 0)
    {
        commandHistory.Last().Undo();
        commandHistory.RemoveAt(commandHistory.Count - 1);
    }
}

Implementing Command Pattern in Unity with C#

Unity and C# make implementing the Command Pattern intuitive. Here’s a refined example:

public interface ICommand
{
    void Execute();
    void Undo();
}

public class JumpCommand : ICommand
{
    private Character _character;

    public JumpCommand(Character character)
    {
        _character = character;
    }

    public void Execute()
    {
        _character.Jump();
    }

    public void Undo()
    {
        _character.ReverseJump();
    }
}

public class PlayerController : MonoBehaviour
{
    private Dictionary<string, ICommand> _commands = new Dictionary<string, ICommand>();

    private void Start()
{
// In a real-world scenario, you'd likely use a factory for creating commands:
    // _commands[PlayerCommandId.JumpCommand] = _commandFactory.CreateCommand(PlayerCommandId.JumpCommand);
    // ... and so on for other commands

    _commands[PlayerCommandId.JumpCommand] = new JumpCommand(this);
    _commands[PlayerCommandId.ShootCommand] = new ShootCommand(this);
    _commands[PlayerCommandId.CrouchCommand] = new CrouchCommand(this);
    _commands[PlayerCommandId.SprintCommand] = new SprintCommand(this);
    _commands[PlayerCommandId.ReloadCommand] = new ReloadCommand(this);
    _commands[PlayerCommandId.UseItemCommand] = new UseItemCommand(this);
    _commands[PlayerCommandId.InteractCommand] = new InteractCommand(this);

    
}

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            _commands[PlayerCommandId.JumpCommand].Execute();
            // ... Store command for undo/redo if needed
        }
    }
}

Conclusion: Command Your Game Universe

The Command Pattern isn’t just a design pattern, it’s a transformative tool that reshapes how you craft game logic, offering unparalleled flexibility. From the dynamic swapping of abilities to clean UI integrations and the magic of undo/redo operations, the Command Pattern ensures your game logic remains modular and easily manageable.

Let’s delve deeper into real-world scenarios where the Command Pattern proves invaluable:

  1. Customizable Controls: Players often desire the ability to remap their controls. With the Command Pattern, changing a button’s functionality becomes as simple as binding it to a different command.
  2. Real-time Strategy Games: In RTS games, players frequently queue up several actions for units. The Command Pattern allows these actions to be easily ordered, modified, or even reversed.
  3. Replay Systems: Ever wanted to review that epic match or share it with friends? With the Command Pattern, you can record all the commands executed during gameplay and play them back later.
  4. Macro Recording: In some games, especially MMORPGs, players can record a series of actions (macros) and execute them with a single button press. The Command Pattern facilitates the recording and execution of these macros.
  5. AI Behavior Trees: AI can be programmed using a series of commands to follow certain behaviors. If the AI needs to change its strategy, you can easily replace one set of commands with another.
  6. Game Tutorials: Teaching a player new mechanics? Use the Command Pattern to demonstrate sequences of actions without the player’s input, then let them take control.
  7. Dynamic Narratives: In story-driven games where player choices matter, the Command Pattern can be used to record decisions. This record can then influence future events, dialogues, and endings.
  8. Modding and Extensions: For games that support modding, the Command Pattern can provide a standardized way for modders to introduce new actions and behaviors without drastically altering the core game code.

In essence, the Command Pattern is more than just theory; it’s a practical approach to solving many of the challenges faced in game development. By understanding and implementing it, you open up a world of possibilities, making your games more dynamic, flexible, and user-friendly.

Ready to dive deeper into the realm of game design? Keep exploring, keep learning, and always be ready to command your game’s universe!