Unity New Input System: How to Swap Action Maps
Setup
Unity’s New Input Systems uses Action Maps as containers, which groups related input actions together. They allow you to define different sets of controls for specific gameplay contexts, such as:
- Gameplay: Movement, jumping, shooting.
- UI Navigation: Menu navigation, selection, back actions.
- Vehicle Controls: Steering, acceleration, braking.
For my example, I have an InputAction Assets (PlayerInputActions) that will have two different sets of controls or Action Maps to move a ball.
- Player Action Map — The Movement Action will use a Vector2 to roll the ball, and the Jump Action will apply a force to make the ball jump.
- World Action Map — The TiltX & TiltZ Actions will use a 1D float value to tilt the world along the X & Z axis. A Jump Action will apply force to the ball to make it jump.
Player Action Map
World Action Map
Objective
To ensure that inputs are correctly directed, you’ll need to disable one Action Map and enable another to switch between them.
We’ll examine how to enable and disable these Action Maps during runtime, using the C# Class and the PlayerInput Component to easily switch between control schemes.
C# Class
Setup
A C# Class script can be created to enable referencing the Input Action Asset and its Action Maps with Actions to a script (e.g., PlayerInput) that is attached to the object (e.g., Player) that you want to control using those Actions.
Script
In order to make use of the Actions from the different Actions Maps from the InputActionAssets, three things must be down.
🟨 Get a reference at start and an instance of our input actions
🟥 Enable Input Action Map
🟩 Subscribe/Unsubscribe to the Input Event functions
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem; //need this to access the Input Action Asset
public class PlayerInput : MonoBehaviour
{
[Header("Ball Movement")]
[SerializeField] private float _speed = 10f;
[SerializeField] private float _jumpForce = 5f;
Vector2 _move;
[Header("World Movement")]
[SerializeField] Transform _world; //level to be tilted
[SerializeField] float _tiltSpeed = 5f; //Speed the world tilts
[SerializeField] float _moveSpeed = 10f; // Force applied to move the ball
[SerializeField] float _tiltMax = 15f; //Max tilt in degrees
private bool _isGrounded;
PlayerInputActions _input;
Rigidbody _rb;
private void Awake()
{
_input = new PlayerInputActions();
_rb = GetComponent<Rigidbody>();
if(_rb == null)
{
Debug.LogError("Rigidbody is null");
}
}
private void OnEnable()
{
_input.Player.Enable();
_input.World.Disable();
_input.Player.Jump.performed += Jump_performed;
_input.World.Jump.performed += Jump_performed1;
}
private void OnDisable()
{
_input.Player.Jump.performed -= Jump_performed;
_input.World.Jump.performed -= Jump_performed1;
}
public void Jump_performed(InputAction.CallbackContext context)
{
if (_isGrounded)
{
_rb.AddForce(Vector3.up * _jumpForce, ForceMode.Impulse);
}
}
public void Jump_performed1(InputAction.CallbackContext context)
{
if (_isGrounded)
{
_rb.AddForce(Vector3.up * _jumpForce, ForceMode.Impulse);
}
}
Since we are dealing with two ActionMaps, we will need to make sure at least one is enabled while the rest are disabled.
Another thing to make note of is that a Jump Action is part of both Action Maps, so when the second Jump event was subscribed to, Unity automatically changed it to Jump_performed1 to differentiate it from the other Jump_performed.
You can rename these methods to make it easier to read.
private void OnEnable()
{
_input.Player.Enable();
_input.World.Disable();
_input.Player.Jump.performed += Player_Jump_performed;
_input.World.Jump.performed += World_Jump_performed;
}
private void OnDisable()
{
_input.Player.Jump.performed -= Player_Jump_performed;
_input.World.Jump.performed -= World_Jump_performed;
}
public void Player_Jump_performed(InputAction.CallbackContext context)
{
if (_isGrounded)
{
_rb.AddForce(Vector3.up * _jumpForce, ForceMode.Impulse);
}
}
public void World_Jump_performed(InputAction.CallbackContext context)
{
if (_isGrounded)
{
_rb.AddForce(Vector3.up * _jumpForce, ForceMode.Impulse);
}
}
The New Input System’s Keyboard.current._Key.wasPressedThisFrame, which is similar to Input.GetKey() in the Legacy System, can be used in Update to enable the World Action Map and disable the Player Action Map and vice-versa.
private void Update()
{
if (Keyboard.current.qKey.wasPressedThisFrame)
{
_input.Player.Disable();
_input.World.Enable();
}
if (Keyboard.current.eKey.wasPressedThisFrame)
{
_input.Player.Enable();
_input.World.Disable();
}
Simply, with one key press use the .Enable() and .Disable() method to active and deactivate that Action Map. I added Debug messages to show that the different Jump methods are being called when the Action Maps are switched.
We can check if an Action Map is currently Enabled using _input.Player.enabled or _input.World.enabled in an if-statement. This is extremely useful since the movement controls for the Player and World both happen in Update() and FixedUpdate().
This allows us to only apply the movement logic when that Action Map is active.
private void Update()
{
if (Keyboard.current.qKey.wasPressedThisFrame)
{
_input.Player.Disable();
_input.World.Enable();
}
if (Keyboard.current.eKey.wasPressedThisFrame)
{
_input.Player.Enable();
_input.World.Disable();
}
if (_input.Player.enabled)
{
//Input for Player Action Map
_move = _input.Player.Movement.ReadValue<Vector2>();
}
if (_input.World.enabled)
{
//Input & Movement for World Action Map
float tiltX = _input.World.TiltX.ReadValue<float>() * _tiltSpeed * Time.deltaTime;
float tiltz = _input.World.TiltZ.ReadValue<float>() * _tiltSpeed * Time.deltaTime;
// Get the current rotation in the -180 to 180 range using Mathf.DeltaAngle
float currentX = Mathf.DeltaAngle(0, _world.transform.localEulerAngles.x);
float currentZ = Mathf.DeltaAngle(0, _world.transform.localEulerAngles.z);
// Clamp the rotation within the tiltMax range
float newX = Mathf.Clamp(currentX + tiltX, -_tiltMax, _tiltMax);
float newZ = Mathf.Clamp(currentZ + tiltz, -_tiltMax, _tiltMax);
_world.transform.rotation = Quaternion.Euler(newX, 0, newZ);
}
}
private void FixedUpdate()
{
if (_input.Player.enabled)
{
//Force for Player Action Map
_rb.AddForce(new Vector3(_move.x, 0, _move.y) * _speed, ForceMode.Force);
}
if (_input.World.enabled)
{
//Force for World Action Map
// Apply force to move the ball in the direction of gravity
Vector3 gravityDirection = -_world.up; // The downward direction of the world
_rb.AddForce(gravityDirection * _moveSpeed, ForceMode.Acceleration);
}
}
Player Input Component
Using the Player Input component you can use the SwitchCurrectActionMap() method and pass in the name of the Action Map you want to enable and it will disable the rest of the Action Maps.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem; //need this to access the Input Action Asset
public class PlayerInputController : MonoBehaviour
{
[Header("Ball Movement")]
[SerializeField] private float _speed = 10f;
[SerializeField] private float _jumpForce = 5f;
Vector2 _move;
[Header("World Movement")]
[SerializeField] Transform _world; //level to be tilted
[SerializeField] float _tiltSpeed = 5f; //Speed the world tilts
[SerializeField] float _moveSpeed = 10f; // Force applied to move the ball
[SerializeField] float _tiltMax = 15f; //Max tilt in degrees
private bool _isGrounded;
PlayerInputActions _input;
//PLAYER INPUT COMPONENT
PlayerInput _playerInput;
Rigidbody _rb;
private void Awake()
{
_input = new PlayerInputActions();
_rb = GetComponent<Rigidbody>();
//Reference the Player Input Component
_playerInput = GetComponent<PlayerInput>();
if(_playerInput == null)
{
Debug.LogError("Player Input Component is null");
}
if(_rb == null)
{
Debug.LogError("Rigidbody is null");
}
}
private void OnEnable()
{
//Switch the Action Maps
_playerInput.SwitchCurrentActionMap("World");
_input.Player.Jump.performed += Player_Jump_performed;
_input.World.Jump.performed += World_Jump_performed;
}
Summary
Action Maps are containers that group related input actions together, such as movement, jumping, or UI navigation. To switch between these maps at runtime, you can use two methods: using a C# script with `Enable()` and `Disable()` methods, and using the `PlayerInput` component’s `SwitchCurrentActionMap()` method. Managing which Action Map is active is crucial for correctly directing inputs in Unity.