PlayerInput component executing bound action after SwitchActionMap

Hello,

I have a Player with a PlayerInput component and it’s Input Action Asset has two defined action maps, “Movement” and “UI”. The two share an action with the same binding, “Movement” has “Interact” on E and “UI” has “Next” on E. After the player interacts with something in the scene, a message pops up in the UI.

In the script, after the message pops up, Player’s PlayerInput component gets switched to “UI” with .SwitchCurrentActionMap(“UI”). This works fine and disables player movement while the message is on screen.

However, after pressing E again, and executing “UI”'s “Next”, the message gets dismissed but the player interacts with the object again, making the message pop up again. It also throws an assertation error in the console:

Assertion failed
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass10_0:<set_onBeforeUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType)
UnityEngineInternal.Input.NativeInputSystem:NotifyBeforeUpdate (UnityEngineInternal.Input.NativeInputUpdateType) (at /home/bokken/build/output/unity/unity/Modules/Input/Private/Input.cs:105)

I found two fixes, none of which are really acceptable:

  1. Delay enabling “Movement” with a coroutine
  2. Make “Next” only execute if the callback context is Cancelled, but this makes the UI feel unresponsive

Relevant code:

    public void DisplayMessage(string message)
    {
        GameController.paused = true;
        SwitchToUIInput();
        _chatPanel.SetText(message);
        activeChat = _chatPanel as IChatComponent;
    }

    public void OnUINext(InputAction.CallbackContext context)
    {
        Debug.Log(context);
        if (!context.performed)
            return;

        if (activeChat.Next())
        {
            GameController.paused = false;
            SwitchToPlayerInput();
        }
    }

    private void SwitchToPlayerInput()
    {
        _playerInput.SwitchCurrentActionMap("Movement");
    }

    private void SwitchToUIInput()
    {
        _playerInput.SwitchCurrentActionMap("UI");
    }

Logs of interacting with an object and dismissing it’s popup:

{ action=Movement/Interact[/Keyboard/e] phase=Started time=2.22968155699982 control=Key:/Keyboard/e value=1 interaction= }
UnityEngine.Debug:Log (object)
InteractionHandler:OnInteract (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/Player/InteractionHandler.cs:61)
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass7_0:<set_onUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType,UnityEngineInternal.Input.NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate (UnityEngineInternal.Input.NativeInputUpdateType,intptr) (at /home/bokken/build/output/unity/unity/Modules/Input/Private/Input.cs:120)

{ action=Movement/Interact[/Keyboard/e] phase=Performed time=2.22968155699982 control=Key:/Keyboard/e value=1 interaction= }
UnityEngine.Debug:Log (object)
InteractionHandler:OnInteract (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/Player/InteractionHandler.cs:61)
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass7_0:<set_onUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType,UnityEngineInternal.Input.NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate (UnityEngineInternal.Input.NativeInputUpdateType,intptr) (at /home/bokken/build/output/unity/unity/Modules/Input/Private/Input.cs:120)

{ action=Movement/Interact[/Keyboard/e] phase=Canceled time=2.23924555899976 control=Key:/Keyboard/e value= interaction= }
UnityEngine.Debug:Log (object)
InteractionHandler:OnInteract (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/Player/InteractionHandler.cs:61)
UnityEngine.InputSystem.PlayerInput:SwitchCurrentActionMap (string)
UIChatController:SwitchToUIInput () (at Assets/Scripts/UI/UIChatController.cs:50)
UIChatController:DisplayMessage (string) (at Assets/Scripts/UI/UIChatController.cs:25)
InteractionHandler:OnInteract (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/Player/InteractionHandler.cs:78)
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass7_0:<set_onUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType,UnityEngineInternal.Input.NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate (UnityEngineInternal.Input.NativeInputUpdateType,intptr) (at /home/bokken/build/output/unity/unity/Modules/Input/Private/Input.cs:120)

{ action=UI/Next[/Keyboard/e] phase=Started time=3.34359519099962 control=Key:/Keyboard/e value=1 interaction= }
UnityEngine.Debug:Log (object)
UIChatController:OnUINext (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/UI/UIChatController.cs:32)
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass7_0:<set_onUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType,UnityEngineInternal.Input.NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate (UnityEngineInternal.Input.NativeInputUpdateType,intptr) (at /home/bokken/build/output/unity/unity/Modules/Input/Private/Input.cs:120)

{ action=UI/Next[/Keyboard/e] phase=Performed time=3.34359519099962 control=Key:/Keyboard/e value=1 interaction= }
UnityEngine.Debug:Log (object)
UIChatController:OnUINext (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/UI/UIChatController.cs:32)
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass7_0:<set_onUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType,UnityEngineInternal.Input.NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate (UnityEngineInternal.Input.NativeInputUpdateType,intptr) (at /home/bokken/build/output/unity/unity/Modules/Input/Private/Input.cs:120)

{ action=UI/Next[/Keyboard/e] phase=Canceled time=3.34687694100012 control=Key:/Keyboard/e value= interaction= }
UnityEngine.Debug:Log (object)
UIChatController:OnUINext (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/UI/UIChatController.cs:32)
UnityEngine.InputSystem.PlayerInput:SwitchCurrentActionMap (string)
UIChatController:SwitchToPlayerInput () (at Assets/Scripts/UI/UIChatController.cs:45)
UIChatController:OnUINext (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/UI/UIChatController.cs:39)
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass7_0:<set_onUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType,UnityEngineInternal.Input.NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate (UnityEngineInternal.Input.NativeInputUpdateType,intptr) (at /home/bokken/build/output/unity/unity/Modules/Input/Private/Input.cs:120)

{ action=Movement/Interact[/Keyboard/e] phase=Started time=3.37343205099933 control=Key:/Keyboard/e value=1 interaction= }
UnityEngine.Debug:Log (object)
InteractionHandler:OnInteract (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/Player/InteractionHandler.cs:61)
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass10_0:<set_onBeforeUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType)
UnityEngineInternal.Input.NativeInputSystem:NotifyBeforeUpdate (UnityEngineInternal.Input.NativeInputUpdateType) (at /home/bokken/build/output/unity/unity/Modules/Input/Private/Input.cs:105)

{ action=Movement/Interact[/Keyboard/e] phase=Performed time=3.37343205099933 control=Key:/Keyboard/e value=1 interaction= }
UnityEngine.Debug:Log (object)
InteractionHandler:OnInteract (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/Player/InteractionHandler.cs:61)
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass10_0:<set_onBeforeUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType)
UnityEngineInternal.Input.NativeInputSystem:NotifyBeforeUpdate (UnityEngineInternal.Input.NativeInputUpdateType) (at /home/bokken/build/output/unity/unity/Modules/Input/Private/Input.cs:105)

{ action=Movement/Interact[/Keyboard/e] phase=Canceled time=3.37500846799958 control=Key:/Keyboard/e value= interaction= }
UnityEngine.Debug:Log (object)
InteractionHandler:OnInteract (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/Player/InteractionHandler.cs:61)
UnityEngine.InputSystem.PlayerInput:SwitchCurrentActionMap (string)
UIChatController:SwitchToUIInput () (at Assets/Scripts/UI/UIChatController.cs:50)
UIChatController:DisplayMessage (string) (at Assets/Scripts/UI/UIChatController.cs:25)
InteractionHandler:OnInteract (UnityEngine.InputSystem.InputAction/CallbackContext) (at Assets/Scripts/Player/InteractionHandler.cs:84)
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass10_0:<set_onBeforeUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType)
UnityEngineInternal.Input.NativeInputSystem:NotifyBeforeUpdate (UnityEngineInternal.Input.NativeInputUpdateType) (at /home/bokken/build/output/unity/unity/Modules/Input/Private/Input.cs:105)

Assertion failed
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass10_0:<set_onBeforeUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType)
UnityEngineInternal.Input.NativeInputSystem:NotifyBeforeUpdate (UnityEngineInternal.Input.NativeInputUpdateType) (at /home/bokken/build/output/unity/unity/Modules/Input/Private/Input.cs:105)

The actions on line 44 and forwards should not have happened.

I seem to have found a solution, although I’m not sure its the correct one.

Instead of calling SwitchToPlayerInput() directly, I add the method as an event handler to the action’s canceled event. This keeps the UI responsive but only switches back to player input after the action is “canceled”:

    public void OnUINext(InputAction.CallbackContext context)
    {
        if (!context.performed)
            return;

        if (activeChat.Next())
        {
            GameController.paused = false;
            _playerInput.currentActionMap.FindAction("Next").canceled += SwitchToPlayerInput;
        }
    }

    private void SwitchToPlayerInput(InputAction.CallbackContext context)
    {
            _playerInput.currentActionMap.FindAction("Next").canceled -= SwitchToPlayerInput;
            _playerInput.SwitchCurrentActionMap("Movement");
    }

I don’t particularly like this solution, so if anyone knows of a better one that’d be great.