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);
}