Changing Action Maps with Unity's "New" Input System
/If you missed my first post (and video) on Unity’s new input system - go check that out. This post will build on what that post explored.
Why Switch Actions Maps?
Action maps define a series of actions that can be contextual.
For example, a 3rd person controller might use one action map, driving a vehicle may use another, and using the UI might use yet another.
With the new input system, it’s easy to control which set of actions (i.e. action map) is active and being used by a player. You can easily toggle off your player’s motion while navigating the UI or prevent the player from casting a spell while riding a horse…
Whatever.
You have more control and the code that gives you that control, while more abstract, is generally far cleaner than it would be with the old input system.
But First, A Problem To Fix
As mentioned in the last post, the simplest implementation of the new input system has each object create an instance of an Input Action Asset. This works great if there is only one object that is reacting to input, but if there is more than one object listening to input (UI, SFX, vehicles, etc) this gets messy. Exponentially more so if you intend on switching action maps as all those objects will need to know which action map is currently in use. Forget one object, and something strange or goofy might start happening - like shooting sound effects while driving a tractor (not that that happened to me - nope, not all).
To be honest, I’m not sure what the best solution for this is. Maybe there is some clever programming pattern - and if there is PLEASE LET ME KNOW - but for now my solution is to fall back and use an input manager.
Why? This allows a single and static instance of the Input Action Asset to be created and accessed by any other class that needs to be aware of player input.
I don’t love this dependence on a manager script, but I think it’s far tidier than trying to keep a bunch of scripts in the scene up to date. The manager stays in charge of enabling and disabling action maps. And! When a map is disabled it won’t invoke events so the scripts that are subscribed to those events will simply have nothing to respond to.
Input Manager
The input manager is pretty simple and straightforward. It has a public static instance of the Input Action Asset and an action that will get called when the action map is changed.
The real magic happens in the last function.
The ToggleActionMap function is again public and static and will be called by scripts that need to toggle the action map (duh!).
Inside the function, we first check to see if the requested action map is already enabled. If it is we don’t need to do anything. However, if it’s not active, we toggle off all action maps by calling Disable on the Input Action Asset itself. This has the same effect as calling Disable on each and every action in the action map.
Next, we invoke the Action Map Changed event. This allows things like the UI to be aware of changes and give the player a visual indication of the change. This could also be used to toggle cameras or SFX depending on the action map activated. This step is optional, but I think will generally prove to be pretty useful.
The final step is to enable the desired action map. And that’s it. We now have the ability to change action maps! Say what you will about the new input system, but that’s mighty clean!
Examples of Implementation
For my use case, the player can change between a normal 3rd person controller and driving a very janky tractor (the jank is in my control code, not the tractor itself). The change to controlling the tractor happens when the player walks near the tractor and enters a trigger surrounding the tractor. The player can then “exit” the tractor by pressing the escape key or the “north” button on a gamepad.
You can see the player and tractor actions maps.
Then in the tractor controller class, there are a handful of movement-related variables, but most important is the Input Action variable that will hold a reference to the movement action that is on the tractor action. We get a reference to this Input Action in the OnEnable function by referencing the static instance of the Input Action Asset in the Input Manager class then going through the tractor action map and lastly to the movement action itself.
Also in the OnEnable, we subscribe the ExitTractor function to the “Exit” action. This allows the player to press a button and switch back to the 3rd person controller.
In the OnDisable function, we unsubscribe to prevent any redundancy of calls or errors in the case of the object being turned off or destroyed.
The Exit Tractor function then calls the public static ToggleActionMap function on the Input Manager to change the active action map to the player action map.
Likewise, in the OnTriggerEnter function, the ToggleActionMap is called to activate the tractor action map.
It’s actually pretty simple. Of course, the exact implementation of how and when action maps are changed depends on your game.
Final Thoughts
I don’t love that any class in the game can switch the active action map, but I’m honestly not sure how to get around. The input manager could easily have some filters in the Toggle Action Map function, but that will absolutely depend on the implementation and needs of your game. Or you might be able to come up with some wrapper class that wraps the Input Action Asset and only gives access to the features (likely just the events) that you want to have widely available.
Also, this approach doesn’t directly work for having multiple players since there is only one instance of the Input Action Asset. There would need to be some additional cleverness and that… that I’ll save for another tutorial (maybe).