Implementing Swappable Key-Function Mapping in Unity

“MARS: Mayhem-Assured Rover Simulator” is a game I made in 48 hours for the GMTK Jam 2020. With this year’s theme being “Out of Control”, I decided to make a game that either makes players physically run out of fingers to control, or make controls a “limited resource” so that they eventually run out.

It didn’t take long for me to associate these features with a robot, and it was almost immediately apparent to me that I’m going to make a game about a “unfortunately poorly-designed” robot. Basically, the interaction are as follows:

  1. There should be enough functions for almost each finger.
    For example: movement / weapon controls / robotic arm / radar
  2. Each function should be intentionally separated from each other to make players busy.
    For example, movement can be separated into “gas”, “reverse”, “turn left” and “turn right”; weapon controls can be separated into “rotate cannon left”, “rotate cannon right”, and “fire”.
  3. All functions should be “hold” instead of “switch”.
    The robot arm only picks up/drops down items, but players have to hold down the key for the arm to keep the item in hand.
  4. Each key must expire after a number of uses.
    So that controls become a limited resource.
  5. Because of 4, keys to each function should be swappable.

So let’s get to some designing.

My terrible handwriting on an iPad

Figuring out the interactions

What we’re talking about here is a system in which “robustness” is a necessity. The game must react to different keys when they’re pressed; each key must function differently when assigned different features. Basically, the interaction map looks a bit like this:

No description available.

Many of the decisions that’ll be made in game are known only in run-time, which means excessive hard-wiring is not a good answer. So, we need to address this design task with flexible components.

Listening to 45 keys

In MARS, players can input using 26 + 19 keys (symbols mostly) on the keyboard. The rover itself has around 7-8 functions. Naturally, each key is a prefab, and each prefab should know what key it stands for. I could tackle this problem by including this script on every key prefab:

if Input.GetKey(KeyCode.A) {/*...*/}
else if Input.GetKey(KeyCode.B) {/*...*/}
else if Input.GetKye(KeyCode.C) {/*...*/}

But not only is this approach inefficient (and inelegant), statements in each brackets must perform another check on which function it is assigned to, and then call their corresponding methods. I sense a high probability of not abiding with reusable code, and I need to find another way.

Fortunately, Input.GetKeyDown() takes in not only KeyCode as arguments, but also strings. Using this, I can designate the key each prefab is representing with a public property, and use that with the Input.GetKeyDown() condition check.

Beware: using public in the beginning is a bad habit. Games written for game jams normally wouldn't encounter serious security concerns, but if you're writing for working projects, establishing a property with the public keyword is a bad idea.
[SerializeField] private string key;

private void Update()
    if(Input.GetKey(key)) {/*Do something!*/}

When each key is placed into a slot, it needs to know which exact method in the Controls Hub to call. A Listener pattern will probably suffice to implement this feature; additionally, Unity has their own Listener Event system out of the box, so I decided to take advantage of that.

I enjoy the moment when the “bridge is connected”; that is why I decided to set aside this task first, and work on completing the Controls Hub and the individual components first. The structure looks a bit like below:

public class ControlsHub
    public static ControlsHub Instance;
    [SerializeField] private MovementController movementControls;
    [SerializeField] private WeaponsController weaponsControls;
    /** Basically components of the rover **/

    private void Awake() { // implement singleton code here // }

    public void Gas()
    public void TurnLeft()
    /** Each component's individual methods **/

public enum RobotFunctions
public class MovementController
    /** This is where you set up individual parameters 
        for your component.
    [SerializeField] private Rigidbody rb;
    [SerializeField] private MaxSpeed;
    [SerializeField] private MaxAcceleration;

    private acceleration;
    private speed;

    private void FixedUpdate()
        speed = speed + acceleration;
        rb.MovePosition(rb.position + YOUR_FORWARD_VECTOR * speed * Time.fixedDeltaTime);

    public void Gas
        acceleration = MaxAcceleration;
    // so on so forth 

You may have noticed that the implementation of these methods don’t really work with how Input.GetKey() works — GetKey() is fired every frame the key is being pressed, but if that’s the case then the MovementController.Gas() method would be repeatedly called, resulting in a robot moving in impossible speeds.

This is because we need to consider other functions of the robot and how they operate as well. An immediate example would be the robotic arm that picks up items when the key is pressed down. It is not a continuous action like speeding up a car, but rather one with 2 states (pick/loose). While Input.GetKey() detects if a key is pressed down, but it doesn’t detect when a key is let go.

This is why we need to refactor our methods to work with the Input.GetKeyDown() / Input.GetKeyUp() pair, which leads to the eventual implementation of our key controller:

public class KeyController
    public string Key;
    private UnityEvent enterEvent;
    private UnityEvent exitEvent;

    private void Update()
        else if(Input.GetKeyUp(Key))
    // Invoked when key is placed in slot
    public SubscribeToComponent(RobotFunction component)
        switch (component)
            case RobotFunction.FORWARD:


So, how did we solve our problems?

  1. There should be enough functions for almost each finger.
  2. Each function should be intentionally separated from each other to make players busy.
    These design questions are achievable through our robust implementation.
  3. All functions should be “hold” instead of “switch”.
    We used Input.GetKeyDown() and Input.GetKeyUp() to achieve similar effects of Input.GetKey(). This implementation allows state-and-stateless functions to operate through the same interface.
  4. Each key must expire after a number of uses.
  5. Because of 4, keys to each function should be swappable.
    To achieve expirable keys, we just need to add in a few statements in KeyController that ignores input if the key has expired; for swappable keys, we avoided hard-wiring keys to each function through using the overload of Input.GetKeyDown().


You can play the game here!

Note: the implementation here is most likely not the optimal way; after all, it is a jam game!

This game jam has deeply educated me of my physical age — I’m 25. When I was 20, I could stay up all night and hack through the entire game without rest. Now I feel dizzy already at 1AM.

Moreover, I started this game alone, but it could never be completed without the help of Joey Yeo and her awesome pixel art. Thank you!

Published by

Brian Teng

Game Designer | San Jose, CA

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s