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()
    {
        movementControls.Gas();
    }
    
    public void TurnLeft()
    {
        movementControls.Turn(-1);
    }
    /** Each component's individual methods **/
}

public enum RobotFunctions
{
    FORWARD, BACKWARD, RIGHT, LEFT, BAY, PICK, CANNONRIGHT, CANNONLEFT, FIRE, BOOST
}
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()
    {
        if(Input.GetKeyDown(Key))
        {
            enterEvent.Invoke();
        }
        else if(Input.GetKeyUp(Key))
        {
            exitEvent.Invoke();
        }
    }
    
    // Invoked when key is placed in slot
    public SubscribeToComponent(RobotFunction component)
    {
        switch (component)
        {
            case RobotFunction.FORWARD:
                enterEvent.AddListener(ControlsHub.Instance.Gas);
                exitEvent.AddListener(ControlsHub.Instance.StopGasBrake);
                break;
            //...
        }
    }
}

Conclusion

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().

Notes

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!

ETC 2020 Museum — Diving Deep Into A VR Graduation Event

Over the past few days I have been working on the ETC 2020 Museum, a Virtual World for my grad school’s (unofficial) virtual commencement. The world was built on the VRChat platform, and had a maximum attendance of around 32 people. Before we dive into the details, here’s a little digest video I made:

Looks pretty simple, but a lot of work has been done in the background to ensure a satisfying experience and technically optimal execution. Here’s a little write-up to detail these features.

Continue reading ETC 2020 Museum — Diving Deep Into A VR Graduation Event

“Mind Game” Postmortem: Lessons Learned From My First Public Project

For the past few months my classmates and I were working on a project in collaboration with Carnegie Mellon University CyLab. This project aims to create a game interface that wraps around picoCTF, an international cybersecurity competition for middle/high school students. We worked to provide an alternate means of participating in the event that would appeal to the more general public (i.e. people who have some interest in the topic but little to no prior knowledge).

Continue reading “Mind Game” Postmortem: Lessons Learned From My First Public Project

“Unto The Horizon” Postmortem – Week 2

Hello! You’re at the week 2 postmortem for “Unto the Horizon”, an ETC BVW Round 4 project at Carnegie Mellon University. Here’s a quick glance of what we did for week 1:

  • Round 4 is a story-telling round.
  • We’re using the Cave Autonomous Virtual Environment (CAVE) room. It is a room with 3 big screens and a moving floor.
  • We’re telling a story about launching a rocket. We aim to invoke emotions of accomplishment and the joy that comes with it.
  • It’s an asymmetrical experience: One team will be in the CAVE as pilots, and the other team in another space as Mission Control. Plays like Keep Talking and Nobody Explodes.
  • Feedback: Lack of story, no interest curve, not intense enough.

You’re always welcome to go back to the previous blog post to know more about this project! Without further or due, let’s begin our week 2 development!

Continue reading “Unto The Horizon” Postmortem – Week 2

“Unto The Horizon” Postmortem – Week 1

Hello, commander! You have arrived at the postmortem blog for “Unto the Horizon”, an ETC BVW Round 4 project. BVW, acronym for Building Virtual Worlds, is a class taught at the Entertainment Technology Center (ETC), where students go through 5 rounds of distinctly themed projects and learn game development fundamentals and soft skills such as cooperation and communication.

Continue reading “Unto The Horizon” Postmortem – Week 1

“Hero, RUN!” Postmortem

Hi! And welcome to the postmortem of “Hero, RUN!”. This project is a 8-week long school project, and I’d like to give a big shout out to Ian Chang and Marvin Yang: without them this project would never be possible.

To begin with, we started out with nothing but one requirement: make a VR / AR game. For us, there wasn’t really a debate on whether we should go with VR or AR; we were lucky to have TWO HTC Vives, so it really wasn’t some sort of a hard decision. 

Continue reading “Hero, RUN!” Postmortem

“The Poorly-lit Exploration Project” Postmortem: 3

Welcome back to another postmortem post for TPEP……this name is too long to input, I’ll change a name next time. Last time we talked about audio and subtitles. They helped building the story of the game, and added color and liveliness to the whole environment.

In this final post, we’ll go into some other features in the game, including the items to be picked, the terrain and some optimization.

Continue reading “The Poorly-lit Exploration Project” Postmortem: 3

“The Poorly-lit Exploration Project” Postmortem: 2

Last time, we mentioned…

  • What this project is: This is an independent project to push my skills further. The goal is to pickup 20 items, invoke particle systems and tell a simple story.
  • Setting Up: I’ve decided that the story should take place in the remains of a deserted village, and the protagonist is revealed with her past, and her destiny as she explores the ruins.
  • Character: How I approached the mechanism to control our character.

So, without further ado, let’s continue.

Continue reading “The Poorly-lit Exploration Project” Postmortem: 2

“The Poorly-lit Exploration Project” Postmortem: 1

“The Poorly-lit Exploration Project” was an independent project set out to push my own limits and learn new knowledge in Unity.
My primary objective was to pick up at least 20 objects, and invoke a particle system whenever the player picks them up. I also wanted to test my skills as a narrative design, so my other goal is to tell a simple story through this experience.

Continue reading “The Poorly-lit Exploration Project” Postmortem: 1