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!

Trying to Design for Overwatch

I love Overwatch. I am fascinated by how took the role-playing shooting genre to the next level with so many different heroes and abilities to choose from; I’m also amazed by how they did so well in telling background stories of the roster with so little effort. In a role-playing competitive game, stories are clearly not the most important, but yet I find myself deeply invested into the universe.

As a game designer and a fan, I often think about the current state of Overwatch, and how to improve the experience and bring more fun to the players. So in January 2019, I’ve started a little project called “Notice me Papa Jeff”. I gathered a few of my friends, who are also game designers, and started brainstorming ideas on a new hero. We had a few meetings, and came up with a list of abilities that we’d like to mix-n-match.

Unfortunately, as we all attend the Entertainment Technology Center, our schedules were swiftly populated by project meetings, assignments and work deadlines, and so we had to put this project aside from our queues; nevertheless, I really liked what we were doing, and especially what I was doing: designing for a game that I love.

So I kept working on it as a side-project on my own, using all the spare time that I have; and now, as the basic have been established, I feel like it is a good time to open it up to the public and receive some feedback.

So here it is! Ranyatta, or, at least for now. 

From the name you could tell that the character is a derivative from Zenyatta. He is a support class, like his twin, but does not focus on healing or individual assists; he is focused on toppling teamfights and reversing the odds at clutch situations. Ranyatta is a hero that requires extensive knowledge and observations to play with, but is very rewarding for the whole team once his abilities are executed correctly.

You can read the document by clicking me! Let me know what you think.

Bakery Rivalry: A Game Concept Aimed To Simulate Real-World Trading Scenarios

I started playing RuneScape in 2005, shortly after it’s second version just launched. My experience with MMORPG was very little, and the only other MMORPG I played was Maple Story (when online games are deemed “useless, harmful and cost unnecessary cash” by my mother, which are all painfully true). After completing all the free missions and countless hours of grinding I made myself a “considerable” fortune; the problem was that this fortune was barely usable, since at that time the only ways to spend that money were either purchasing items from NPCs or trading with random players, who will probably only accept the request if you offer ridiculously more than expected.

Then in 2007, a new feature was implemented in to the game — the Grand Exchange. Essentially it collects all the needs and offers across all servers, and let’s players to purchase items through a price decided by the market. This allows players to get what they want for a reasonable price, and also allows sellers who want really wanted to sell items an easier way to find the correct customers instead of wandering around in the world inefficiently.


MMORPGs nowadays all have similar trading systems. For example, Blade and Souls has the exactly same market system that allows players to put their items on sale for the entire community.

Continue reading Bakery Rivalry: A Game Concept Aimed To Simulate Real-World Trading Scenarios

“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

“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