Unity's New Input System
/Version 1.0.2 of the input system was used along with Unity 2020.3
Warning! If you are looking for a quick 5-minute explanation of Unity’s new input system - this isn’t going to be it - and you aren’t going to find one! The new system is more complex than the old system. Especially when it comes to simple things like knowing when the spacebar has been released.
I’m going to do my best to be concise and get folks up and running, but it will take some effort on your part! You will likely need to dive into the admittedly opaque Unity documentation if you have a special use case. It’s just the way it is. Input is a complex topic and Unity has put together a system that can nicely handle that complexity.
So Why Use Unity’s New Input System?
I’ve got three reasons. Three reasons I’ve stolen, but they are good reasons to use the new Input System.
If you want players to be able to use multiple devices OR you are developing for multiple platforms the new system makes it very very easy to do so. Frankly, I was shocked how easily I could add a gamepad and easily switch back and forth between it and a keyboard.
It’s Event-Based! When an action is started, completed (performed), or canceled an event can be called. While you will still need to “poll” values every frame for things like player or camera motion, button presses for other bits such as jumping or shooting no longer need to clog an update function! While this adds some perceived complexity - especially if you don’t feel comfortable with events - but it is an awesome feature.
Input debug system! Unity provides an input debugger so you can see the exact values, in real-time, of your system’s input. This makes it so much easier to see if a device is recognized and functioning properly. AND! In the case that you do need to do some polling of an input value (think similar to the old system in an update function), it’s much easier to see what buttons are being pressed and what those input values look like.
So yeah! Those are pretty fantastic reasons. The new input system does take some time and patience to learn - doubly so if you are used to the old system, but hopefully, you’ll agree the effort is worth it.
Setting It Up
To get started, you’ll need Unity version 2019.1 or newer and the system is added via the package manager. When importing the system you will likely get a popup with a warning to change a setting. This setting is all about which system Unity will use to get input data from. You can make further changes in Project Settings > Player > Active Input Handling. From there, you can choose to use either the new system, the old system, or both.
If you can’t get the new system to function, this setting would be worth checking.
Next, we need to create a new “Input Actions” asset. This is done like any other asset, by right-clicking in a project folder or using the asset menu. Make sure to give the asset a good name as you’ll be using this name quite often.
With the asset created you can select it and then in the inspector press “edit asset.” This will open a window specific to THIS input action asset.
So if you have more than one input action asset, you will need to open additional windows - there is no way to toggle this window to another asset. Personally, I found this a bit confusing when first getting started as it feels different than other Unity windows and functionality.
Inside the Input Action Window
This is where all the setup happens and there’s a lot going on! There are way more options in this window than could possibly be covered in this video or even several more videos. But! The basics aren’t too complex and I’m going to try and look at some of the more common use cases.
On the left, you’ll see a column for “Action Maps.” These are essentially a set of inputs that can be grouped together. Each Input Action asset can have multiple action maps. This can be useful for different control schemes for example if your player can hop in a car or maybe on a horse and the controls will be different. This can also be used for UI controls - so that when a menu is opened the player object stops responding and the controls for a gamepad now navigate through a menu.
To be honest, I haven’t yet figured out a nice clean way to swap action maps but it might be the topic of a future post/video so let me know (comment below) if you are interested in seeing that.
To create a new action map simply press the plus at the top right of the column and give the action map a good name. I’ve called mine “Player.”
The middle column is where our actions get defined. These are not the buttons or keys that will be pressed - those are the bindings - but these are the larger actions that we want the player to be able to do such as move, jump, or shoot.
To get started I’m going to create two actions one for movement and one for jumping.
Each action has an “action type” and a “control type” - you can see these to the right in the image above. These options can easily feel ambiguous or even meaningless as they can seemly have little to no impact on how your game plays - but when you want to really dial in the controls they can be very useful
Actions types come in three flavors value, button and passthrough. The main difference between these three is when they call events and which events get called.
Link: Unity Action Type Documentation
Value Action
The Value action type will call events whenever a value is changed and it will call the events started, performed, and canceled (more on these events later).
The “started” event will get called when the control moves away from the default value - for example, if a gamepad stick moves away from (0,0).
The “performed” event will then get called each time the value changes.
The “canceled” event will get called when the control moves back to the default value - i.e. the gamepad stick going back to (0,0).
This would seem like a good choice for movement. However, the events are only called when the values change, so it won’t get called if the player holds down the W key or keeps the gamepad stick in the same position. That’s not to say it’s not useful, but there are potentially other problems that need to be solved for creating player motion if this action type is used.
Button Action
The button action type will call events based on the state of the button and the interactions assigned to the action itself. The interactions, which we will get to, will define when the performed and canceled events are called. In the end, the Button action type is what you want when events should be called when a button is pressed, released, or held. So far in my experience, this covers the majority of my use cases and is what I’ll be using throughout this tutorial.
PassThrough
The PassThrough action type is very similar to the value action type. It will call the performed event any time the control changes value. BUT! It will not call started or canceled.
The passthrough action also does not do what Unity calls disambiguation - meaning that if two controls are assigned Unity won’t be smart and try to figure out which one to use. If this sounds like something you might need to know about, check out the Unity documentation.
If your head is starting to spin and your getting lost in the details. That’s fair. This system is far more powerful than the old system, but as a trade-off, there are way more bits and pieces to it.
Interactions
I’m not going to go too deep into the weeds on interactions, but this is where we can refine the behavior a bit more. This is where we can control when the events get invoked. We have options to hold, press (which includes release options), tap, slow tap, and multi-tap. All of these interactions were possible with the old system, but in some cases, they were a bit challenging to realize.
For the most part, I found that interactions are fairly self-explanatory with some potentially confusing nuance between tap and slow tap. The documentation while a bit long does a great job of clarifying some of that nuance.
Processors
Sometimes you need or want to make some adjustments to the input values such as scaling or normalizing vectors. Essentially processors allow you to do some math with the input values before events are called and values are sent out. These aren’t terribly complex and are going to be very use case specific.
Link: Unity Documentation on Processors
Adding Bindings
Still with me? Now that we have our actions set up we need to add bindings - these are the actual inputs from the player! Think key presses or gamepad stick movements. I’m going to create bindings for both the keyboard and a gamepad for all the controls. This is a tiny bit more work, but once we get to the code, the inputs will be handled the same which is really powerful!
Movement
The first binding will be for the keyboard to use the WASD keys for movement. We need to add a 2D Vector Composite. To find this option you’ll need to right-click on the movement action. This will automatically add in four new bindings for the four directions.
Composite bindings essentially allow us to combine multiple inputs to mimic a different input device, i.e. using the WASD in the same way as a gamepad stick. You may notice that there is a mode option, but for our use case either digital option will work.
Notice also, that interactions and processors can be assigned to individual bindings allowing more customization! These interaction and processors work the same for bindings as for actions.
Link: Composite Mode Documentation (scroll down just a bit)
With the WASD binding created we then need to assign keys or the input path. We can do this by clicking on what looks like a dropdown next to “path.” If this dropdown is not present click the T button which toggles between the dropdown and typing.
Then you can select the correct key from the list. OR! Press the listen button and then press the button you want for the binding. It couldn’t be much easier.
The second binding will be for the gamepad. You can simply click on the plus for the movement action and choose “Add Binding.” Selecting this binding you will see options to the right. Once again you can use the “listen” option and move the gamepad stick, but it only allows one direction on the stick. Maybe there’s a way around this but I haven’t found it! So select any direction and we’ll edit the path manually to take in all values from the stick.
Once you have a path, click the T button to manually edit the path. From there we’ll remove the direction-specific part. In my case this will look like <Gamepad>/leftStick with this done you can click the T button again and the path should be just the left stick.
Jump
I’ll repeat the process of adding bindings for the jump action. Adding in a binding for the spacebar and the “south” button on my gamepad. Unity has been pretty clever here with the gamepad buttons. Rather than give control specific names they have cardinal directions so that the “south” button will work regardless of whether it is an Xbox or Playstation controller.
Now that we have the basic actions and binding implemented. We’re almost ready to get into the code. But first! We need to make sure the asset is saved. At the top right there is a save asset button. This has caught me out a few times, make sure you press it to save changes.
There is also an auto-save feature, which is great until you generate C# code (which will talk about in a minute). In that case, the autosave tends to make the interface laggy and a bit harder to use.
Implementation
There is a default player controller that comes with the input system. It has its place, but in my opinion, if you’ve come this far it’s worth digging deeper and learning how to use the input system with your own code. It’s also important to know that the input system can communicate by broadcasting messages, drag and drop Unity Events, or my preferred method C# events.
If you aren’t familiar with events, check out my earlier tutorial. Events aren’t easy to wrap your head around at first but are hugely powerful and frankly are at the core of implementing the new input system.
To get access to the C# events we first need to generate a C# code for the actions we just created.
Thankfully, Unity will do that work for us!
In the project folders, select the Input Action Asset that we created at the beginning. In the inspector, you should see a toggle labeled “Generate C# Class”. Toggle this on and press “apply.”
This should create a new C# script in the same location as the input action asset and with the same name - unless you changed the default settings. You can open it up, but there’s no need to edit it or do any work on it so I’m just going to leave it be.
Custom Class
Next, we’ll need a custom player controller class.
This class will need access to the namespace UnityEngine.InputSystem.
Then we’ll need two new variables. The first is of the type of our newly created Input Action Asset, in my case this is “Farmer Input Actions.” And the second is of type Input Action and will hold a reference to our movement input action.
You can create a variable for each input action and cache a reference to it - I’ve seen many videos choose to do it this way. I have chosen not to do this with most of the input actions to keep the code compact for the purposes of this tutorial - it’s up to you.
Also, for most event trigger actions you don’t need to reference the input action outside of the OnEnable and OnDisable functions. Which for me lessens the need for a cached reference.
Before we start working with the input actions and events. We need to create a new instance of the Input Action Asset.
I’ve chosen to do this in the Awake function. The fact that this player controller class will have its own instance is important! The Input Action Asset is not static or global!
With the instance created, we need to wire up the events and enable the input actions and this is best done in the OnEnable function.
For the movement input action, I’ll cache a reference and you can see that this is accessed through the instance of the Input Action Asset, then the Player action map, and finally the movement action itself. I am choosing to cache this reference because we will need access to it in the fixed update function.
With the reference cached, we need to enable the input action with the “Enable” function. Do note that there is an "enabled” property that is not the same as the “Enable” function. If you forget to call this function, the input action will not work. Like a few other steps, this one caught me out a few times too.
The steps for the jump input action are similar, but in this case, I won’t be caching a reference. Instead, I will be subscribing a function to the performed event on the jump input action. This subscribed function will get called each time the jump key or button is pressed.
There is NO need to constantly check whether the jump button is pressed in an update function! This is one of the great improvements and advantages of the new system. Cleaner code albeit at the cost of a bit more complexity.
To create the jump function you can do it manually, or in Visual Studio, you can right-click and choose “Quick Actions and Refactoring” and then choose “Generate Method.” This will ensure that the input parameter is of the correct type. Then inside the function, we can simply add a debug message to be able to test it.
The next step to the setup is to disable both the movement and jump input actions. This should be done in the OnDisable function. This may not be 100% needed but ensures that the events won’t get called and thus throw errors if the object is disabled. Also note, that I did not unsubscribe. While in most cases this won’t be a problem or throw an error, but if the object is turned on and off the jump function will get called multiple times. This was spotted by a YT viewer (THANKS DAVE).
The final step for testing is to read the movement values in the fixed update function. I’m using fixed update because I’ll use the physics engine to move and control the player the object. Reading the values is pretty straightforward. To keep things simple, I’ll use another debug statement, and to get the values we simply call “Read Value” on the movement input action, give it a generic parameter of the type Vector2 since we have both X and Y values for movement.
Testing
At this point, we can test out our solution to make sure everything is wired up correctly. To do this we simply need to put our new player controller class on a gameObject and go into play mode.
Pressing the WASD keys or moving the gamepad stick should show values in the console. While pressing the spacebar or the south button on the gamepad should display our jump message.
Whew!
If you’re thinking that was a lot of work to display some debug messages your right. It was. But! We have a system that works for both a keyboard and a gamepad AND the code is really quite simple and clean. While the old system was quick to use the keyboard or mouse, adding in a gamepad was a huge pain, not to mention we would need to code both inputs individually.
With the new system, the work is mostly at the front end creating (and understanding) the Input Action Asset. Leaving the implementation in the code much simpler. Which in my opinion is a worthy trade-off.
So What’s Next?
I still want to look at a few more implementations of the new input system, but frankly, this is getting long already. In the intro GIF you may have noticed a lot more functionality than the project currently has. ALL of the extra functionality is based on what I’ve shown already, but I think is worth covering - in another tutorial.
For now, if you want to see my full implementation of the 3rd person controller (minus the camera) you can find it here on PasteBin. I will transition all the project code to GitHub once the series is complete.
Topics I’d still like to look at:
Creating the 3rd Person Controller
Controlling a Cinemachine Camera
Triggering UI and SFX with the new System
Shooting!!
“Charging Up” for a power shot
Player rebinding during playmode
Swapping action maps
UI? Boat? Car?
If you’d like to see one or all of those topics, leave a comment below. They’re only worth doing if folks are interested.