How to properly stop a disabled Action Map from sending events from when it was disabled?

Button South (A button) is set up in Player Controller to trigger the player to jump. A button also selects UI buttons in my pause menu. When in the pause menu, the player controller has input disabled. When I press A button to select the Resume UI button, the menu is disabled and an event is triggered to let the player controller class know to re-enable input. So I press A to resume the game, the moment I pressed A, the player control input should still be disabled, but a jump event still is triggered. The effect is that every time I resume gameplay from the pause menu, the player character jumps. It seems the event is queued or something, but I cannot figure out how to stop the event from being interpreted.

I have an input action asset GameInputs. I have a player controller script and a UI Manager script. For the player controller script I unsubscribe from the jump input and disable input at the same time when either the player controller game object is disabled (OnDisable) or when the UI Manager triggers a “DisablePlayerControls” event.

public class PlayerController2D : MonoBehaviour
{
GameInputs input;

private void OnEnable() // Subscribe to relevant events
{
  EnableInput();
  UIManager.EnablePlayerControls +=  EnableInput;
  UIManager.DisablePlayerControls += DisableInput;
}

private void OnDisable() // unsubscribe to events
{
  DisableInput();
  UIManager.EnablePlayerControls -=  EnableInput;
  UIManager.DisablePlayerControls -= DisableInput;
}

 // input events are move to their own functions to enable/disable them on Menu events
private void EnableInput()
{
  if (input == null) input = new GameInputs(); 
  input.Player.Jump.performed += JumpPerformed;
  input.Player.Jump.canceled += JumpCanceled;
  input.Enable();
}

private void DisableInput()
{
  input.Player.Jump.performed -= JumpPerformed;
  input.Player.Jump.canceled -= JumpCanceled;
  input.Disable();
}
//...
// Implementation of jump action and the rest of the class 
//...
}

My UIManager class will resume the game if it tries to go to a previous menu, but there are no previous menus to return to:

 private void GoToPreviousMenu()
    {
        // disable the current menu and pop it from the stack
        if (menuStack.Peek().activeInHierarchy) menuStack.Pop().SetActive(false);
        // if the stack is now empty we should return to gameplay, re-enabling player control
        if (menuStack.Count == 0)
        {
            // Player controller subscribed to this and will re-enable it's GameInputs instance.
            EnablePlayerControls?.Invoke();
            return;
        }
        // if the stack was not empty, activate the previous menu
        menuStack.Peek().SetActive(true);

    }

For more context, the UIManager only uses a single action from GameInputs to enable the menu when the player presses esc/start. Otherwise, I let the UI system automagically do its navigation via DefaultInputActions on the EventSystem/Input System UI Input Module, where it somehow just knows that dpad and left stick navigates, and south button (A) is select (don’t understand how this is working really either)

private void OnEnable()
    {
        if (input == null)
        {
            input = new GameInputs();
        }
        input.Enable();
        input.Menuing.Pause.performed += EnableMainMenu;
        MenuBase.Proceed += GoToNewMenu;
        MenuBase.Return += GoToPreviousMenu;
    }

So my confusion as previously stated is that at the moment I press A to resume, the player controller should be unsubscribed from jump events and the input should be disabled until it receives the “EnablePlayerControls” event from the UI Manager. however it still triggers “started” and “performed” events the moment control is returned.

What can I do to fix this?
Full Player Controller Class
Full UI Manager Class

EDIT (HACKY SOLUTION):
I found an incredibly hacky solution that I refuse to believe is the cleanest approach, but it technically works. I essentially just eat the first (A) input using a bool wasInMainMenu. Still seeking a proper explanation of how to do this correctly.

Delayed Input Enabling sets the bool to true:

void EnableInputOnDelay()
{
  StartCoroutine(EnableInputIEnumerator());
}

IEnumerator EnableInputIEnumerator()
{
  wasInMainMenu = true;
  yield return new WaitForEndOfFrame();
  EnableInput();
}

private void EnableInput()
{
  if (input == null)
  {
    input = new GameInputs();
  }
  input.Player.Movement.performed += MovementPerformed;
  input.Player.Movement.canceled += MovementCanceled;
  input.Player.Attack.performed += AttackPerformed;
  input.Player.Jump.performed += JumpPerformed;
  input.Player.Jump.canceled += JumpCanceled;
  input.Enable();
}

Jump Event Code does nothing except reset the bool to false if it was true:

private void JumpPerformed(InputAction.CallbackContext obj)
{
  if(wasInMainMenu)
  {
    wasInMainMenu = false;
    return;
  }
  jumpInput = true;
  jumpBufferCounter = jumpBufferTime;
}

I noticed this happening in the newer Input system versions, after updating from 1.8.1 to 1.8.2 from memory. So something changed internally for this behaviour to start.

I honestly just worked around it by having a one frame delay between enabling one input action and disabling the other, and vice versa. Relatively simpler to do with async code, such as Unity’s Awaitable’s or UniTask.

So unless I misunderstood the question at hand; You have one input manager dealing with UI and gameplay, using your A to select as the example. You go to the UI and want the input from game to be disabled.

To do this you can do it in a few ways, one is to make it set input related to movement at ZERO.
if its cursor related you can do CursorLockMode
Another way would be to do a SetActive(false) on the parts you dont want to work. This basically “Disables” it.

If this does not help, feel free to repost to me I will get back into the forum and check

So I tried doing 1 frame delay. Didn’t work. I will point out this is how my action map is set up:
I create an Input Action Asset called GameInputs that one action map called “Player”, including an “A button” gamepad binding set to a “Jump” action.

In addition, I am using a second, default, Input Action Asset for my UI (the one that is attached to the Input System UI Input Module component on the generated EventSystem GameObject). That Input Action Asset also has a binding for “A Button” set to “Submit” which selects UI buttons. Consequently, it when you hit the “Resume” ui button with the “A Button” it returns to gameplay.

The difference here maybe being that I’m using two Input Action Assets, a custom one and the default one. And I only enable/disable the custom one that I am using, GameInputs. I leave the other default one alone, as it is only active if UI with buttons are active (which are only active when I am in the menu).

I might need to revisit this and reduce it to a single Input Action Asset and try your solution. I am unfamiliar with Awaitable’s or UniTask.

CrimsonMediaProductions,

Hardcoding player input logic to “zero” out movement and other things is not extensible, as if my player controller evolves, there will be more and more cases where I have to revisit it to ensure I don’t allow player movement when in menu. I want to avoid this.

I do not know what you mean by do a SetActive(false) on the parts I don’t want to work. Do you mean disable the entire player controller script component? Something else would need to re-enable it, and i prefer to keep my code decoupled where possible by using events.

How complicated do you want to make your character, you could have UI on its own interface, enabling and disabling that interface alone, you could then create a base character script, adding the methods and components you would like, and use other scripts that inherit from that and not monobehaviour. I have one such script where I can have all types of EnemyAI and my player using the exact same script each having its own input etc. Just a matter of inheritence and setting up the files

What do you mean exactly by “have UI on its own interface”?

So, you create a script for an interface, designing your UI elements in this, then at the top of your (PlayerController as example) after monobehaviour, you would have this

using UnityEngine;

public class PlayerController : MonoBehaviour, IInventory{

}

General naming conventions dictate an Interface’s name starts with I in this instance I am calling an interface IInventory (inventory)

to make an interface you would make an emtpy C# script and write:

using UnityEngine;

public interface IInventory{
 //INSERT YOUR CODE HERE
}

this is a special inheritable script you can attach to different scripts and call their methods (a simple form of inheritence)

I’m not connecting the dots on how this aids in ensuring that my input action callbacks that share the same binding but separate action maps don’t trigger a callback upon re-enabling of one of the input action maps.

I mean I use two input action maps and have had no issues with my solution. Hell I have over a dozen and this pattern works fine (project has lots of menus so they’re all individually rebindable, and the player has their own action map too).

To clearly outline the process:

  1. Player presses input to close menu
  2. Process to close menu starts and UI inputs is disabled
  3. One frame delay (using UniTask.NextFrame())
  4. Re-enabled player inputs, re-subscribing to any inputs

Had no issues after that.

@CrimsonMediaProductions None of what you’re posting really corresponds to the issue OP is having.

  1. Player presses input to close menu - got it
  2. Process to close menu starts and disables UI inputs - i recognize this is what i should be doing, but instead im fully disabling the UI related Game Objects that would react to the UI inputs so I would think it shouldn’t matter. But does it matter if my UI Inputs remain active? Not from the perspective of best practices, but is there some way that leaving another Input Action Asset’s Action Map enabled somehow queue that binding on another Input Action Asset’s Action Map? Or something else? Again, I understand disabling it is the clean thing to do, but for the sake of understanding what’s going on under the hood with action maps, I ask this question.
  3. One frame delay ( using UniTask.NextFrame() ) - I will look into trying this.
  4. Re-enabled player inputs, re-subscribing to any inputs - i am doing this. But does it look like from my code snippets that I am not enabling/disabling the inputs correctly?

Looking at your code, you’re using WaitForEndOfFrame. End of frame is not the next frame. yield return null; I believe will wait a whole frame.

tried the null method. with a log at the start and end of my update method (idk why i did it twice, i don’t think I needed to). It seems like the started action always happens right after I return from the UI menu, namely, after the last frame it was disabled, but before the update method begins on the following frame where it is enabled. This is leading me to believe that its queuing it or something.

You can test what frame each action is happening on with Time.frameCount.

So The frame after I enabled input (frame 64), jump started and was performed (frame 65). But I held down the A button from before this, during the period of which it was disabled. And I’m not frame perfectly hitting A a second time instantly the frame after I exit the menu. Maybe in my younger CS:GO days I could’ve been that fast, but not anymore (lol)


EDIT: Thank you a lot for the frameCount thing. Didn’t know about that. Super useful.

Bumping (idk if thats a thing on here) this thread because I still don’t have a clear answer to what is causing my event callbacks to trigger after the input is re-enabled.

I am unsure if this is even valuable information for anyone. For reasons unknown, at some point in the past, in my action map I set the Jump Action’s “Action Type” to Value and “Control Type” to Any.

When I set the Action Type to “Button”. My issue is resolved when I do this.

Proposed solution was not working in my case as I have all buttons marked as “button”.

	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.

I’m glad you found a solution that works for you. I would be curious to look at your original implementation attempt since my self-fix didn’t work for you and I’d like to gain a greater understanding of the input system.

I’m curious the impact your solution will have on the rest of the game as well, as in, depending on the type of game 100ms of delay could be pretty noticeable to a player. I suppose one frame didn’t work?

	public IEnumerator SwitchActionMap ( string name )
	{
		_player_input.currentActionMap.Disable();
	//	yield return new WaitForSeconds(0.01f);
		yield return 0;
		_player_input.SwitchCurrentActionMap( name );
		_player_input.currentActionMap.Enable();	
	}

Yes, 1 frame pause also works.
In other thread somebody wrote thet 1 frame is not the correct answer so my idea was how to make it working wothout using “non working” solutions.
So if 100ms pause works maybe shorter pause will work too ? I testes 0.01s as pause time and it is “good”.
I tested it today and indeed one frame gap between enabling and disabling input maps is enough to have if instant and stable.