The fully featured input system in World to the West
Published 2 years ago
We wanted World to the West to be supported as well on the PC as the consoles.
Hi I'm Fredrik Ludvigsen, and I'm a tools programmer, among other things, for Rain. I'm the lead programmer for Teslagrad, but nowadays, I'm mostly helping Baste Buanes and the rest of our developers in creating World to the West.
I got the responsibility for creating a proper input system, roughly 1½ years into the development.
At that time, the input was collected in one class called WWInput. It was responsible for keeping track of which character was playing, and they each had to inherit a class called MesmerianBody.
We wanted World to the West to be supported as well on the PC as the consoles. This brought up an old problem that we became familiar with during the development of Teslagrad: most common keyboards don't support a lot of simultaneous key presses. As a game developer, the only truly valid guarantee that you have is that the keyboard will support 2 simultaneous key presses. So we felt the need to add mouse input into the equation.
Teri controlling a blamling
The last itch that made Baste request a proper system was the Teri character. She is able to control almost any other creature in the game... and we feared that this would impact every such creature script significanly in a negative way.
So I had a lot of different sub-goals:
- Support for a combined keyboard+mouse mode
- Support for temporary input take-overs
- Support for in-game input remapping
- Minimal script impact
- Performance (always)
Level 1: Using HardwareInputController directly.
Welcome the HardwareInputController! It's concern is to maintain input subscriptions.
So the basics goes something like this... any class can claim that it supports a certain input by subscribing to it. TheHardwareInputController, which is a singleton, stores all such subscriptions in a Dictionary<GameInputTypeAction>.
Each frame, every possible GameInputType is checked once, and only once, and the results are passed on to delegates that have subscribed to the given input type.
So far this should enable:
- Support for a combined keyboard+mouse mode
- Support for in-game input remapping
- Performance
Our temporary input mapping UI
Supporting a keyboard+mouse mode in this way was only a matter of defining one of the input types as an in-game vector type of input, which did not care if the input came from keys, mice or analog input.
Input remapping was handled internally in HardwareInputController, and any perfomance issues, linked to callingInput.GetKey() etc. would be limited upwards by the number of possible different input types.
GameInputController's input domains
On top of HardwareInputController we created a layer concerned with game state. GameInputController, another singleton, was created to look like HardwareInputController on the outside, but instead of passing delegates on directly, new delegates were created, that could ignore / filter out the signal based on game state.
GameInputController subscriptions were organized in different domains, so that menu-type input would get priority over any in-game-type input etc.
The different game input domains were called:
- System
- InGame
- Menu Stack
- InGame Stack
Level 2: Using GameInputController manually
A stack in this context means that there only can be one active subscriber in the stack at any given time. This is perfect for creatures controlled by Teri, but also any other script that needs to get the exclusive access to the input.
But it's also appropriate for menus that are organized in submenus. In a way, you can say that the menu stacks layers on top of itself anyway.
Dialogs get exclusive access to the in-game input domain.
Another example is our dialogs. They briefly need to have exclusive access to the in-game input, and then return it to whichever script had it before, and they do that implicitly, just by being activated and deactivated.
So now we have Support for temporary input take-overs covered.
Next: Minimized script impact
Level 3: Inheriting InputSubscriber to enable subscription attributes.
For this we created a reflection based utility, that could go through any given class and locate given attributes, and make sure that those methods were subscribed appropriately to GameInputController.
Inheriting from InputSubscriber makes the instance automatically subscribe all the methods with the Subscribeattribute in OnEnable() and unsubscribe them in OnDisable().
So that's our current input system.
Clonington philosophizing in the meadow.
But there's more.
What if we could also "record all input, and play it back". It turns out... we can.
Input recording is a whole topic all by itself, but I just want to highlight how neatly it fits within the patterns of theHardwareInputController.
So the HardwareInputController at this point knows the state of the input api's at any given point in time, and whether or not any method is using it. That is a very powerful combination, because now HardwareInputController only needs to collect and store the input that has any impact on the game.
As an extra confidence boost, I recognized the following item on the Unity Roadmap "Create a better, more adaptable input system for Unity."
And looking at the Asset Store, it's quite obvious that Rewired is the king of the input hill. I'm clearly not the only one that got this idea.
Both of these systems feature a state machine that can cope with control schemes.
I wouldn't be surprised if any of you readers recognize a similar system from your own projects too.