Making a RTS game #28: Adding some shortcuts (Unity/C#)

Let’s continue our RTS game and implement a custom input manager for some shortcuts!

This article is also available on Medium.

In the last couple of tutorials, we’ve been working on our units and the upgrade system. Today, we’re going to switch gears and go back to something a bit more “meta”: adding shortcuts to make some actions quicker to access.

In this tutorial, we’ll see how to create our own custom input system – it shall be easy to define for programmers and customisable in the settings panel by the players (so it needs to allow for re-mapping).

Let’s get to it!

What is an input system?

The basics: inputs, actions, events…

Inputs are essential to any game. There are the way the player communicates with the software, they are the door that programmers opened to have you impact the game and be part of this world.

You can usually differentiate between two types of inputs:

  • buttons are basic inputs that are pressed and released (like keyboard keys or gamepad buttons)
  • axes give back continuous values for analog inputs (like a joystick or a mouse position)

Note that some controllers mix the two types of inputs: for example, a mouse has both buttons (the left-, right- and optionally middle-clicks) and axes with its position. Also, a button can differentiate between a single-press and a multi-press (like a double-click).

The most basic form of input is one that directly maps a specific piece of hardware, so a button or an axis value, to a function in the game:

For example, in Unity, it could look like this – if I press the Space bar, then I jump:

The problem with that method is that it completely depends on the device and it is pretty hard to re-configure afterwards. If you want to handle another type of input controller, you’ll have to add another check in your if-statement… and if the player wants to re-map the controls to better suit their style, it’s simply impossible!

That’s why, usually, you don’t do direct binding but rather add an intermediary component: the action.

The idea is that instead of referencing a specific device button or axis, you say that your function will be triggered by an event that corresponds to an action. This action is purely abstract. The important thing is that it can be caused by one or more inputs and that these inputs can change throughout the game, without this change impacting the action-event-function part of the chain!

Let’s take back our jump example – you could have a gamepad or a keyboard run the “Jump” action, and then this action triggers the “Jump” event that, in turn uses the “Jump” function as callback:

Or you could allow the players to do some re-mapping and specify their own controls instead of the default ones:

Having an input system is way more flexible than direct bindings and it is not that hard to implement – especially given we already have an event system ready to use 😉

Re-inventing the wheel?

Now – it’s worth pointing out that Unity has input systems.

There is the “old” input system (the one we are currently using in our code) that relies on an input manager and then the Input class, and that lets you get keyboard, mouse or gamepad controls via scripting like this:

And then, there is the “new” input system (for Unity 2019.1+) that is slowly growing on the community, even though it is more complex to wrap your head around as a beginner. To be honest, I have looked at it and I do see its benefits (in particular: multiple action maps for different types of controllers and easy re-mapping, even in-game) but I think it is still lacking some important features and overall adds a whole bunch of complexity to your game project.

It works in a similar way to the old system, but the properties you access are a bit different:

And, most importantly, it requires you to setup quite a lot of stuff in your project to properly configure the inputs, the actions, the bindings and whatnot.

For an in-depth study of the two systems, how they compare and whether it’s better to use community assets like Rewired, you can check out this really great article by John French 🙂

All in all, what I take from his article is that the main advantages of the new input system are that it allows for in-game remapping (contrary to the old system that “bakes” the inputs when you build your game), it natively handles multi-platforms (e.g. PC, console, mobile…) and it makes it easy to separate different players if you have a local multiplayer feature.

Since here we are working on a single-player PC game, those benefits aren’t that great compared to the overhead of the system.

So I decided I would rather show you how to implement our own basic input system 🙂

Designing our input system

A very important note: I decided to go with a custom system because:

  • I thought it would be interesting to see how to code one from scratch
  • we won’t have to change anything to our current inputs handling (whereas, if we switch to Unity’s new input system, we’ll be forced to replace all of our current usage of the Input class or enable “Both” systems in our Project Settings…)
  • I don’t plan on using gamepads or joysticks for my game, only mouse and keyboard
  • I won’t be using axes (I will only focus on buttons) since this is primarily meant to implement shortcuts and I’ve never seen those use axes… 😉

If you feel like any of those points are going to be issues for your game, then you should probably look at Unity’s new input system instead 🙂

Ok, with that said – let’s see how to make our own input system!

We’ll consider that our input system is a game parameters asset, just like our sound or player parameters, and so we’ll create a new GameInputParameters Scriptable Object class that inherits from our GameParameters class:

Basically, this object will contain just one variable: an array of InputBindings.

The InputBinding is a custom data structure that we’ll define in a serialisable class and that contains a display name (for pretty UI display in our settings panel), the input key to listen to and the name of the event to send if the input is enabled:

Now that we have our InputBinding class, we can create our array in the GameInputParameters class:

Adding a default keyboard mapping with building shortcuts

Creating the input system asset

We’re now ready to instantiate this new Scriptable Object class and create a default input manager. I’ll call it “Keyboard Mapping” because it contains references to keyboard keys – but it could also use mouse buttons with the codes “mouse 0”, “mouse 1” or “mouse 2”.

For a list of all available inputs, you can look at Unity’s docs.

Here is a basic keyboard mapping that defines two actions (two input bindings) that are shortcuts to place a specific type of building quickly:

I made this bindings variable visible in my in-game settings using our custom UI “+” button, and I then created and filled a few elements.

Note: I also added a little prefix to my game parameters assets to order them properly in the game settings panel, because the order of the assets in the folder determines the order the sections appear on the left of the game settings panel.

Notice that I am sending events with two parts (separated by the colon : sign): the main command (here: “Build”) and a sub-parameter to tell what particular building type to select (“house” or “tower”).

This is quite unique to my game: the GameInputParameters class is pretty generic but this asset is a specific instance that uses predefined building codes and particular events that my code will be able to handle. So this is typically something that might be different on your end, depending on your game, your building codes, etc.

Checking for inputs in our GameManager

For now, we have prepared our input manager but we are not really using it anywhere. What we’ll do is add a reference to it in our GameManager as well as a bit of logic in the Update() so that whenever we press a button, we check if it corresponds to a binding in our game input parameters asset.

Here are the updates in the GameManager class:

(Don’t forget to drag your game input parameters Scriptable Object instance to the new gameInputParameters slot 😉 )

And here is the CheckForInput() function in our new GameInputParameters class:

As you can see, we simply iterate through our mapping and check for the current button input: if we don’t find any match, we ignore the key input; else, we trigger the corresponding event. We also separate the main command from the sub-parameter to make it easier to use in the callback function. (Once again, this could be different depending on how you structured your events in your input parameters asset…)

I’ve decided to automatically prefix all input-related events with an easily-recognisable <Input> prefix – this way, when I go through my listeners in other scripts, I will quickly spot the ones that depend on this input system!

Reacting to the actions in our BuildingPlacer script

All that’s left to do is actually listen to and react to those events! Those are shortcuts for our building placement logic, so we’ll add this event handling in our BuildingPlacer script:

And this _OnBuildInput method just needs to get back the sub-parameter (i.e. the code of the building type to select), find the index of the matching building in our global building types list and set it as the currently selected type for building placement:

Now, if I start my game and press either the <h> or the <t> key, I directly get a little “phantom” building of the right type, ready to be placed on the terrain 🙂

Note: I added a little debugger to my game to show the input key if there is any in the top-left corner, which also requires me to send a basic “<Input>” event whenever a key is pressed (whether or not it is within my input mapping table).

You see that when I press a non-registered key, nothing happens – only the registered inputs trigger an event! And I still have my “cancel placement” behaviour when I press the <Escape> key.

Re-mapping controls in our game settings panel

The final step in implementing our custom and re-mappable input system is to give the players access to these controls so they can change the shortcuts to their liking!

We’ll do this in our game settings panel.

Because of all the automated logic we put in place a while ago, we actually already have an empty settings panel for the “Controls” section:

This is because we are listing all of the GameParameters-derived class assets in our “Scriptable Objects/Parameters” folder, and so the script simply found a new one to show.

But now, we need to populate it with some labels and buttons to show the current controls and let the players change them easily!

Showing up the current shortcuts

As usual with UI stuff, I won’t dive into too much details – if you want to see my scene setup, you can check out the Github repository 🚀

I simplified a bit the panel instantiation logic by using more layouts and auto-content resizers, so my code for the _SetGameSettingsContent() panel (in the UIManager script) so far looks like this now:

To handle our brand new section, we just have to add some references to UI prefabs and check for a field of type “array of InputBindings”:

My UI consists of a vertical layout of items, one per input binding. And each of those items is a row with the display name of the input on the left and a clickable button on the right that shows and edits the bound key.

I’m sure we could make something prettier but it is readable and it has all the components we need to finish up our input system logic 😉

Re-mapping controls from the settings panel

Ok so – the real tricky part is in the _AddInputBindingButtonListener().

What we want is for the players, whenever they click a button in this interface, to enter a “waiting for key” mode; the game will wait until they’ve pressed a button, and as soon as they’ve enabled an input, this will become the new key for this action.

To do this, we’ll rely on a simple coroutine, and on our GameManager Singleton instance.

Basically, our GameManager will have two new variables to toggle this “waiting for key” mode on and off, and to store the new input string:

Our coroutine will switch the “waiting for key” mode on at the beginning of its logic and then wait until the new input string has been filled by something.

When it’s filled, we’ll retrieve this value and assign it to the InputBinding instance (plus update the associated UI button text).

Also, while the “waiting for key” mode is on, we’ll have a special display on the button (<?>) to indicate that we are expecting an input.

This gives us the following code (the various parameters I added are just to access all the data and UI elements I need):

Our inputs are now re-mappable! Try and run the game, then navigate to your settings “Controls” sub-panel and click on one of the control key: you’ll get the special display, then as soon as you press a button (on your keyboard or your mouse) you’ll get the new value assigned to your InputBinding instance, and the UI will refresh:

If you take a look at your Scriptable Object asset, you’ll see that it updates instantly when you change the parameter in-game, and it retains the data in-between each session – it’s just like the rest of the parameters, it benefits from the load/save data system 🙂

Note: but remember that, once again, this custom input system doesn’t handle all possible devices – we don’t have gamepads or joysticks re-mapping!

Checking for previously assigned inputs

A last improvement we can make to this system is checking for previously assigned inputs so that, if you pick an input that was already bound to another action, the previous link is removed and you don’t have duplicate bindings.

This is pretty straight-forward:

  • first, we’ll add a little function to our GameInputParameters class to easily get an InputBinding instance and its index from a given input reference (if there is one):

Note: here, I’m using a nice C# feature that allows you to return multiple values by sending back a tuple of variables and then deconstructing it on the other end 😉

  • and then, in our UIManager, we’ll update the coroutine to perform this quick check and optionally update the previous InputBinding instance (and its matching UI button):

Now, if you try and re-assign the same input multiple times, the previous binding will be cleared out and your new assignment will take precedence:

If you look carefully at this recording, you might notice that the Scriptable Object that’s shown in the Inspector doesn’t have its default display…

Bonus: making a custom editor for our input parameters assets!

Because if we want to make our lives easier as game designers, it would be nice to have a slightly more condensed editor for our input mappings.

The current Inspector we have is the one provided by Unity by default, and it works, but it’s a bit tiresome to have to open and close each item to edit its parameters, and it takes quite a lot of space…

What we can do is create a new custom PropertyDrawer for our InputBinding class, just like we did for the PlayerData some tutorials ago. I’ll just create a new C# file, InputBindingDrawer.cs, in my “Editor” folder (at the root of the assets directory).

And the code is very similar to the one for the PlayerDataDrawer, actually – the only difference is that I also had to override the GetPropertyHeight method to tell Unity that my custom drawer takes not one but two rows, and that we have to adapt the name of the properties to look for:

After adding this code to my project, I get much more likeable and handy editors for my InputBinding fields! 🙂

If you want, you can even change the key field to use a dropdown and make sure you always pick a valid input string…

Conclusion

Today was a quick standalone episode on how to make our own custom input system to add basic keyboard (or mouse) shortcuts, re-map them dynamically in our in-game settings panel and configure them easily in the Unity editor.

Next time, we’ll go back to working on our player ownership system, and more specifically we will refactor our game resources to properly handle multiple players on the same map. We’ll also take a little detour over to Unity animations to have a small banner indicating our player colour move in the corner of the UI…

Leave a Reply

Your email address will not be published. Required fields are marked *