Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

Little Big Tips Joystick > Patterns / Algorithms > command pattern

The Dungeon

Feel free to try this behaviour on the playable demonstration / prototype: The Dungeon.
Note: The purpose of this demonstration is to evaluate this gameplay mechanic. The FPS shooter gameplay mechanic itself, the scenario and the props are free assets from the Asset Store.

Problem description

Sometimes, the Player wants to change the controls in runtime. So, how to make this change fast and dynamic?

Solution simplified concept

With the command pattern, we are able to decoupling the button (or the key of the keyboard) and the method call (the Player action in this case). In other words, the button itself doesn't call directly the method anymore. A new class or "layer" between both will be responsible for it and each command is implemented in an individual and separated class.

Solution suggestion

In this case, our player has a lot of actions.
Note: To keep this example simple, we will map only 4 actions to use our command pattern: UseKnife, Reload, Loot and Interact. After that, you will get the idea and will be able to implement the other actions with no problem.

In the hierarchy, create 2 game objects and name them as Player and Input Handler:

Hierarchy:
- Player
- Input Handler

Create a C# script Player.cs and attach this script to the Player game object:

public class Player : MonoBehaviour
{
    ...

Create a C# script InputHandler.cs and attach this script to the Input Handler game object:

public class InputHandler : MonoBehaviour
{
    ...

Create another C# script ICommand.cs. This will be our interface which each command (class) will implement:

public interface ICommand
{
    void Execute(Player player);
}

Create the class of each command and implement the ICommand.cs interface. You can use the same file if you wat (ICommand.cs):

public interface ICommand
{
    ...
}

public class UseKnifeCommand : ICommand
{
    public void Execute(Player player)
    {
        // TODO: impl.
    }
}

public class ReloadCommand : ICommand
{
    public void Execute(Player player)
    {
        // TODO: impl.
    }
}

public class LootCommand : ICommand
{
    public void Execute(Player player)
    {
        // TODO: impl.
    }
}

public class InteractCommand : ICommand
{
    public void Execute(Player player)
    {
        // TODO: impl.
    }
}

Now, let's make this work!

In the Player.cs script, implement the actions:

public void UseKnife()
{
    Debug.Log("impl. the action use knife"); // TODO: remove
}

public void Reload()
{
    Debug.Log("impl. the action reload"); // TODO: remove
}

public void Loot()
{
    Debug.Log("impl. the action loot"); // TODO: remove
}

public void Interact()
{
    Debug.Log("impl. the action interact"); // TODO: remove
}

Now, let's finish our action classes:

public class UseKnifeCommand : ICommand
{
    public void Execute(Player player)
    {
        player.UseKnife();
    }
}

public class ReloadCommand : ICommand
{
    public void Execute(Player player)
    {
        player.Reload();
    }
}

public class LootCommand : ICommand
{
    public void Execute(Player player)
    {
        player.Loot();
    }
}

public class InteractCommand : ICommand
{
    public void Execute(Player player)
    {
        player.Interact();
    }
}

As you can see, our Player has the specific action methods implemented and we have each related action (class) calling these specific methods.

Now, let's implement the "layer" between both, I mean, our InputHandler.cs and map the commands. First, define the fields:

ICommand qCommand;
ICommand rCommand;
ICommand xCommand;
ICommand kCommand;

ICommand[] defaultCommands;

bool isDefaultCommands = true;

Start the process:

void Start()
{
    defaultCommands = new ICommand[4];

    defaultCommands[0] = new UseKnifeCommand();
    defaultCommands[1] = new ReloadCommand();
    defaultCommands[2] = new LootCommand();
    defaultCommands[3] = new InteractCommand();

    qCommand = defaultCommands[0]; // use knife
    rCommand = defaultCommands[1]; // reload
    xCommand = defaultCommands[2]; // loot
    kCommand = defaultCommands[3]; // interact
}

Create the method that will handle the inputs:

ICommand HandleInput()
{
    if (Input.GetKeyDown(KeyCode.Q))
    {
        return qCommand;
    }
    else if (Input.GetKeyDown(KeyCode.R))
    {
        return rCommand;
    }
    else if (Input.GetKeyDown(KeyCode.X))
    {
        return xCommand;
    }
    else if (Input.GetKeyDown(KeyCode.K))
    {
        return kCommand;
    }

    return null;
}

As you can see, in this case, the default commands are mapped like so:
Q -> UseKnife | R -> Reload | X -> Loot | K -> Interact

Finally, let's handle our commands:

void Update()
{
    ICommand command = HandleInput();
    // TODO: get access to the Player script
    command?.Execute(GameManager.Instance.Player);
}

Note: In this case, I'm using the singleton pattern to access the Player script.

The HandleInput() method returns the command and we only need to call the Execute() method of it.

To see the commands changing in runtine, implement this final method and call it from wherever you want:

public void ToggleInput()
{
    if (isDefaultCommands)
    {
        // alternative commands
        qCommand = defaultCommands[1]; // reload
        rCommand = defaultCommands[2]; // loot
        xCommand = defaultCommands[3]; // interact
        kCommand = defaultCommands[0]; // use knife
    }
    else
    {
        // default commands
        qCommand = defaultCommands[0]; // use knife
        rCommand = defaultCommands[1]; // reload
        xCommand = defaultCommands[2]; // loot
        kCommand = defaultCommands[3]; // interact
    }
    isDefaultCommands = !isDefaultCommands;
}

Scripts:

Player.cs, ICommand.cs, InputHandler.cs

Again, feel free to try the behaviour of this Little Big Tip on The Dungeon.

More Little Big Tips? Nice, let's go!