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.
Sometimes, the Player wants to change the controls in runtime. So, how to make this change fast and dynamic?
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.
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;
}
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!