Action Map is still working even when disabled

I have two controllers (player and arrow), that are using action maps and an input manager script. i switch between controllers by pressing a button using EnableActionMap() method.

 public static void EnableActionMap(InputActionMap actionMap)
{
     if (actionMap.enabled) return;
    
     playerInput.Disable();
     actionMap.Enable();
}

It worked completely fine until i’ve decided to trigger a transition through animation. I’ve made another script with TriggerTransition() method (which is triggered on certain animation frame) and attached it to player controller (because player’s model plays the animation).

[SerializeField] Transform arrow;

public void TriggerArrow()
{
    arrow.gameObject.SetActive(true);
    arrow.transform.parent = null;
    InputManager.EnableActionMap(InputManager.playerInput.Arrow);
    CameraSwitcher.Instance.SwitchCameraPriority();
}

And there it stopped working properly, two controllers are active at the same time, even though log shows that player controller is disabled ( Debug.Log(playerInput.Player.enabled); ). Any suggestions why?

Alright, after 4 days of suffering I’ve finally struggled through this nightmare and found a solution. So I guess answer is closed!

Anyways, feel free to ask how i fixed it (if it will help u)

How did you do it?

I Tried it like this

SettingsManager.Instance.ActionAsset.FindActionMap("Gameplay", true).Disable();

and in my Update for player movement, I have a debug that shows it it disabled.

In future, if you find a solution to something, please post it, so people struggling with the same problem can reference your thread and find a solution, thank you.

1 Like

Same problem though, I kind of hacked the problem away with these methods, the problem is when switching the actionmap on the same frame to an action map with a similar mapping.

for example:
OpenMenu is mapped in PlayerControlsActionMap
OpenMenu triggers on pressing ESC on the keyboard
CloseMenu is mapped in MenuControlsActionMap
CloseMenu also triggers on pressing ESC on the keyboard, but only if the right action map is active and current

SwitchActionMap(PlayerControlsActionMap)->ESC->OpenMenu->SwitchActionMap(MenuControlsActionMap)
CloseMenu triggers too, I suppose I could set the action map on the next frame, but I would prefer if I could use the input system in an intuitive way.

// Switches the actionmap on the PlayerInput component
public void SwitchActionMap(string actionMapName)
{
_playerInput.currentActionMap.Disable();
_playerInput.SwitchCurrentActionMap(actionMapName);
_playerInput.currentActionMap.Enable();
}

/* Checks if the ctx command we received is on the actionmap that we currently have enabled
Also checks a static bool GameState.Instance.mouseOverUi that gets changed on some mouseOver and mouseLeave events
*/
private bool IsCommandValid(InputAction.CallbackContext ctx, bool respectUI = true)
{
var uiBlocks = respectUI && GameState.Instance.mouseOverUi;
var valid = ctx.performed && ctx.action.actionMap.name == _playerInput.currentActionMap.name && !uiBlocks;
if (valid)
{
Log($"{ctx.action.name}");
}

return valid;
}

Which I then use in my UnityEvent listeners, for ex.

public void OnOpenMenu(InputAction.CallbackContext ctx)
{
if (!IsCommandValid(ctx,false)) return;

_gameManager.uiManager.IngameMenuController.Show();
}

EDIT: Managed to improve the behaviour a little bit by disabling all inputActionMaps onStart and changing the CommandValid method to check enabled.
Please see the complete code, dont use the code as is, rather copy paste some useful methods from it

using Lib.BaseMonoBehaviours;
using Lib.Utilities;
using Singletons;
using UnityEngine;
using UnityEngine.InputSystem;

namespace Managers.Controls
{
    [RequireComponent(typeof(PlayerInput))]
    public class ControlsManager : MonoBehaviour
    {
        public static readonly string PlayerControls = "PlayerControls";
        public static readonly string BuildingControls = "BuildingControls";
        public static readonly string DialogueControls = "DialogueControls";
        public static readonly string InGameMenuControls = "InGameMenuControls";

        [SerializeField] public bool debugMode;
        private GameManager _gameManager;
        private PlayerInput _playerInput;

        private InputActionMap currentActionMap;

        private void Awake()
        {
            _playerInput = GetComponent<PlayerInput>();
            _gameManager = GameState.Instance.GetGameManager();
        }
    
        private void Start()
        {
            InitializeActionMaps();
            _gameManager.uiManager.hudController.SetSelectedGameObject(_gameManager.playerGameObject);
            RegisterEventHandlers();
        }

        // This is a workaround to the current bug with he input system ignoring the active action map
        //TODO: remove once unity fixes
        private void InitializeActionMaps()
        {
            currentActionMap = _playerInput.actions.FindActionMap(_playerInput.defaultActionMap);
            foreach (var playerInputAction in _playerInput.actions)
            {
                if (playerInputAction.actionMap.name != _playerInput.defaultActionMap)
                {
                    _playerInput.actions.FindActionMap(playerInputAction.actionMap.name).Disable();
                }
            }

            SwitchActionMap(currentActionMap.name);
        }

        public void SwitchActionMap(string actionMapName)
        {
            Log($"Switching action map {currentActionMap.name}->{actionMapName}");
            currentActionMap.Disable();
            currentActionMap = _playerInput.actions.FindActionMap(actionMapName);
            currentActionMap.Enable();
            _playerInput.SwitchCurrentActionMap(actionMapName);
        }

        // Update is called once per frame

        private void RegisterEventHandlers()
        {
            if (_gameManager.buildingPlacementManager is not null)
            {
                _gameManager.buildingPlacementManager.OnStartPlacingBuilding += (e, a) =>
                {
                    SwitchActionMap(BuildingControls);
                };
                _gameManager.buildingPlacementManager.OnCancelPlacingBuilding += (e, a) =>
                {
                    SwitchActionMap(PlayerControls);
                };
                _gameManager.buildingPlacementManager.OnSuccessPlacingBuilding += (e, a) =>
                {
                    SwitchActionMap(PlayerControls);
                };
                _gameManager.dialogueManager.OnDialogueStarted += (e, a) => { SwitchActionMap(DialogueControls); };
                _gameManager.dialogueManager.OnDialogueComplete += (e, a) => { SwitchActionMap(PlayerControls); };
                _gameManager.dialogueManager.OnDialogueInterrupted += (e, a) => { SwitchActionMap(PlayerControls); };
            }
        }

        #region PlayerControls

        public void OnSelect(InputAction.CallbackContext ctx)
        {
            if (!CommandValid(ctx)) return;

            var hitObject =
                MouseUtils.GetObjectAtScreenPosition(Camera.main, Mouse.current.position.value,
                    _gameManager.entityLayer);

            if (hitObject != null && hitObject.GetComponent<BaseEntity>() != null)
            {
                if (GameState.Instance.SelectedGameObject != hitObject)
                    _gameManager.uiManager.hudController.SetSelectedGameObject(hitObject);
            }
            else
            {
                if (GameState.Instance.SelectedGameObject != _gameManager.playerGameObject)
                    _gameManager.uiManager.hudController.SetSelectedGameObject(_gameManager.playerGameObject);
            }
        }

        public void OnCommand(InputAction.CallbackContext ctx)
        {
            if (!CommandValid(ctx)) return;

            var selectedGameObject = GameState.Instance.SelectedGameObject;
            if (!selectedGameObject || selectedGameObject != _gameManager.playerGameObject)
            {
                _gameManager.uiManager.hudController.SetSelectedGameObject(_gameManager.playerGameObject);
            }
            else
            {
                var selectedEntity = GameState.Instance.SelectedGameObject.GetComponent<BaseEntity>();
                if (selectedEntity) selectedEntity.RightClickAction();
            }
        }

        public void OnRotateCamera(InputAction.CallbackContext ctx)
        {
            if (!CommandValid(ctx)) return;
        }

        public void OnOpenMenu(InputAction.CallbackContext ctx)
        {
            if (!CommandValid(ctx, false)) return;

            _gameManager.uiManager.IngameMenuController.Show();
        }

        #endregion

        #region BuildingControls

        public void OnPlace(InputAction.CallbackContext ctx)
        {
            if (!CommandValid(ctx)) return;
            _gameManager.buildingPlacementManager.PlaceBuilding();
        }

        public void OnCancel(InputAction.CallbackContext ctx)
        {
            if (!CommandValid(ctx, false)) return;
            _gameManager.buildingPlacementManager.CancelPlacingBuilding();
        }

        public void OnRotateBuildingClockwise(InputAction.CallbackContext ctx)
        {
            if (!CommandValid(ctx)) return;
            _gameManager.buildingPlacementManager.RotateBuildingClockwise();
        }

        public void OnRotateBuildingCounterClockwise(InputAction.CallbackContext ctx)
        {
            if (!CommandValid(ctx)) return;
            _gameManager.buildingPlacementManager.RotateBuildingCounterClockwise();
        }

        #endregion

        #region DialogueControls

        public void OnNextLine(InputAction.CallbackContext ctx)
        {
            if (!CommandValid(ctx, false)) return;
            _gameManager.dialogueManager.NextLine();
        }

        public void OnCancelDialogue(InputAction.CallbackContext ctx)
        {
            if (!CommandValid(ctx, false)) return;
            _gameManager.dialogueManager.InterruptDialogue();
        }

        #endregion

        #region InGameMenuControls

        public void OnCloseMenu(InputAction.CallbackContext ctx)
        {
            if (!CommandValid(ctx, false)) return;
            _gameManager.uiManager.IngameMenuController.Hide();
        }

        #endregion

        #region Helpers

        private bool CommandValid(InputAction.CallbackContext ctx, bool respectUI = true)
        {
            var uiBlocks = respectUI && GameState.Instance.mouseOverUi;
            var valid = ctx.performed && ctx.action.actionMap.enabled &&
                        _playerInput.currentActionMap.name == ctx.action.actionMap.name && !uiBlocks;
            if (valid)
            {
                Log($"{ctx.action.name}");
            }

            return valid;
        }

        private void Log(string message)
        {
            if (debugMode) Debug.Log(message);
        }

        #endregion
    }
}

When switching to a map with the same binding, for example ESC to open a menu and ESC to close the menu do the switch on the following frame, this will avoid other actions from triggering on the actionmaps you are switching to until you have completed handling the current action

public IEnumerator SwitchActionMapOnNextFrame(string actionMapName)
{
yield return new WaitForEndOfFrame();
SwitchActionMap(actionMapName);
}
	public PlayerInput _player_input;
	void Start ()
	{
		StartCoroutine(SwitchActionMap( "player" ));
		StartCoroutine(SwitchActionMap( "UI" ));		
	}
	IEnumerator SwitchActionMap ( string name )
	{
		Debug.Log(name + " - " + _player_input.currentActionMap.ToString());
		_player_input.currentActionMap.Disable();
		yield return new WaitForSeconds(0.1f);
		_player_input.SwitchCurrentActionMap( name );
		_player_input.currentActionMap.Enable();
		Debug.Log(name + " - " + _player_input.currentActionMap.ToString());		
	}

I hate this type of sollutions but here I am, with something which actually WORKS.
When I read about " action map change" at the same frame I thought, why not postpone such change by “some” frames rather than just 1 ?
I assume this solution is lame as hell, but this is not executed every frame so I guess, for simple swap between gameplay and ui maps is not a big deal.
In my case “player” is "gameplay map.
I set it as current to disable it a bit later, so any previous map is disabled ( whatever it is )
then I set “UI” map as current one and disabling “player” map.

As I said, this is lame as hell, but it works at least.