Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Feedback Allowing input after re-enabling / Feature Request: Subscribe to Enable/Disable Events

Discussion in 'Input System' started by BTStone, Apr 21, 2020.

  1. BTStone

    BTStone

    Joined:
    Mar 10, 2012
    Posts:
    1,418
    Hey folks and @Rene-Damm

    (Using Unity 2019.3.2f1 and Input System: preview.6 - 1.0.0)

    in the last couple of days I spent lots of time on structuring my Inputhandling with the new input system and although there are apparently still some bugs here and there generally I'm very happy with the whole input system and how fast and easy I can setup different maps and actions, well done. Looking forward to future versions of this :)

    But, let's tackle a problem I have. I can't pinpoint if this is something I have to deal with on my end or if this maybe even a bug and/or missing feature. So here it goes:

    I have a control scheme, called PC_Scheme. This PC_Scheme has a bunch of ActionMaps. My idea is:
    Only one action map can be active at a time. So I have a dedicated InputHandler class responsible for handling this with a Stack<InputActionMap>

    Then I have so called InputBehaviours which implement the generated interfaces of the respective InputActionMap. For example this one:

    Code (CSharp):
    1.     public class PlayerEntityDialogueInputBehaviour : MonoBehaviour,
    2.                                                       IInputBehaviour,
    3.                                                       TestInputActions.IPlayerDialogue_MapActions
    4.     {
    5.         [SerializeField]
    6.         private PlayerEntityDialogueInput playerEntityDialogueInput = new PlayerEntityDialogueInput();
    7.  
    8.         private PlayerEntityInputRegistrator playerEntityInputRegistrator;
    9.  
    10.         public void InitInputRegistrator(AGameInputRegistrator gameInputRegistrator)
    11.         {
    12.             playerEntityInputRegistrator = (PlayerEntityInputRegistrator) gameInputRegistrator;
    13.         }
    14.  
    15.         public void RegisterInputCallbacks()
    16.         {
    17.             playerEntityInputRegistrator.GameInputActions.PlayerDialogue_Map.SetCallbacks(this);
    18.         }
    19.  
    20.         public void StartInputBehaviour()
    21.         {
    22.             playerEntityDialogueInput.OnStart(playerEntityInputRegistrator.PlayerEntity);
    23.         }
    24.  
    25.         public void OnProgressDialogue(InputAction.CallbackContext context)
    26.         {
    27.             playerEntityDialogueInput.OnProgressDialogue(context);
    28.         }
    29.  
    30.         public void OnEnterMainMenu(InputAction.CallbackContext context)
    31.         {
    32.             playerEntityDialogueInput.OnEnterMainMenu(context);
    33.         }
    34.     }
    When specific conditions are met, like when the Player hits a key in front of an object to inspect it this is what happens:

    - The current active actionmap (Peek in stack) gets disabled
    - the new actionMap gets pushed and enabled

    In this case, when the inspection is done/the dialogue is over this happens:

    - The current active actionmap gets disabled
    - The current active actionmap gets popped
    - The peek in stack (which was the previous active map) gets enabled again

    This works pretty great so far. There is only one tiny problem:
    (For clarification, I'm using a Keyboard as a device to describe the problem)

    Let's assume the game is running and and the so called "PlayerGeneral_Map" is the active actionmap. This map allows for movement and different kinds of interactions with objects, entering the mainmenu and starting the pause screen and so on.
    For now I'm using WASD and the ArrowKeys for movement. In order to interact with Objects in the scene I have to hit the "SPACE"-Key

    Let's say I hit and HOLD the "D"-Key in order to move to the right, since there is an object of interest.
    Entering the Trigger of the Object I hit the "D"-key. In this very frame I STILL HOLD the "D"-Key.
    Now the PlayerGeneralMap gets disabled and the PlayerDialogueMap is active. A Dialogue pops up and I have to hit the "SPACE"-Key again in order to progress the dialogue. Once done the PlayerGeneralMap gets activated once again. I STILL hold the "D"-Key but the player doesn't move, which implies the Callback doesn't get called. I have to release my finger from the key and hit it again in order to move again.

    The Movement callback looks like this:
    Code (CSharp):
    1. protected internal void OnMovement(InputAction.CallbackContext context)
    2.         {
    3.             Log.Info($"Is Action Enabled: {context.action.actionMap.enabled}");
    4.             Vector2 input;
    5.             switch (context.phase)
    6.             {
    7.                 case InputActionPhase.Started:
    8.                     break;
    9.                 case InputActionPhase.Performed:
    10.                     input = context.ReadValue<Vector2>();
    11.                     SetPlayerDestination(input);
    12.  
    13.                     break;
    14.                 case InputActionPhase.Canceled:
    15.                     input = Vector2.zero;
    16.                     SetPlayerDestination(input);
    17.  
    18.                     break;
    19.                 case InputActionPhase.Disabled:
    20.                     Log.Info("Movement is disabled");
    21.                     break;
    22.             }
    23.         }

    My desired behaviour is:
    While holding the "D"-key once the DialogeMap gets disabled and the GeneralMap gets enabled the input gets basically picked up again.

    What's interesting, the moment I hit the "SPACE"-Key in the object of interest while holding the movement key the value of .enabled of the actionmap is still true. When I debug that and go through the stacktrace the code does traverse all the necessary steps:

    InputActionMap.Disable()
    InputActionState.DisableAllActions()
    InputActionState.ResetActionState()
    [...]
    PlayerEntityMovement.OnMovement()

    So .enabled being still true is kind of weird and InputActionPhase.Disabled in my OnMovement never gets called, the log is never printed. But InputActionState.DisableAllActions implies the phase should be called:

    Code (CSharp):
    1. for (var i = 0; i < actionCount; ++i)
    2.             {
    3.                 var actionIndex = actionStartIndex + i;
    4.                 if (actionStates[actionIndex].phase != InputActionPhase.Disabled)
    5.                     ResetActionState(actionIndex, toPhase: InputActionPhase.Disabled);
    6.             }

    But in InputActionState.ResetActionState apparently the phase goes directly to "Canceled"?:


    Code (CSharp):
    1. else
    2.                 {
    3.                     // No interactions. Cancel the action directly.
    4.  
    5.                     Debug.Assert(actionState->bindingIndex != kInvalidIndex, "Binding index on trigger state is invalid");
    6.                     Debug.Assert(bindingStates[actionState->bindingIndex].interactionCount == 0,
    7.                         "Action has been triggered but apparently not from an interaction yet there's interactions on the binding that got triggered?!?");
    8.  
    9.                     ChangePhaseOfAction(InputActionPhase.Canceled, ref actionStates[actionIndex]);
    10.                 }


    So my questions are basically:
    - Do I have to change my code to get the desired behaviour, like saving the last input I got before getting disabled? If so: How should I do that? I can't trust the Cancel-Phase, since the phase is also called when I lift my finger from the keys and not only when I disable the current active map. I guess in order for this to better work it would great if there would be Eventcallbacks for Enabling/Disabling ActionMaps (and Controlschemes while we're at it :p )

    - If this is something I can do without changing my code but through Interactions or Processors in the InputAsset itself, how has the setup to look like?
     
  2. BTStone

    BTStone

    Joined:
    Mar 10, 2012
    Posts:
    1,418
    Bump.
     
  3. BTStone

    BTStone

    Joined:
    Mar 10, 2012
    Posts:
    1,418
  4. Rene-Damm

    Rene-Damm

    Unity Technologies

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Sorry to leave you hanging for so long. Looks like I've let quite the stack accumulate...

    Wouldn't necessarily classify as a bug but IMO it definitely classifies as a missing feature. We have a number of improvements to actions on the list and having action stacks in some form is on that list.

    So, internally, there is a mechanism called "initial state check". There used to be an explicit toggle for this on each action but the public configuration aspect has been collapsed into the action type. A "value" type action will perform an initial state check whereas other types will not.

    Initial state check means that as soon as the action is enabled, it'll check its bound controls for whether they are already actuated and if so, will trigger the action.

    If your movement action is a "value" action of, say, Vector2, this initial state check *should* be performed and the action should immediately respond to whether the D key in a WASD setup is pressed. If that is not the case, I'd consider that most likely to be a bug.
     
    BTStone likes this.
  5. BTStone

    BTStone

    Joined:
    Mar 10, 2012
    Posts:
    1,418
    Hey @Rene-Damm
    Thanks for the reply! :)

    Oh sorry, I think you misunderstood that. Me talking about it being a bug or missing feature was not the thing with the Action Stack, this is just me clarifying how I setup my game input. What I meant was the process of trying to kind of "store" the last input before disabling the map. So:

    - Walking around while holding the respective keys
    - Triggering a Dialogue with someone while pressing the Dialogue Key (we still hold the movement key)
    - Progressing through the Dialogue by pressing the Dialogue Key (we still hold the movement key)
    - Dialogue is Finished, Movement-Map gets enabled and we immediately can move again since we were holding the Key all along without the need to lift the finger and press/hold again

    Essentially I wanted to know how to pull this off with the current system and if it is possible at all. To be honest, if anything it all it's also a missing feature to me not a bug ;)


    Mhm, would you mind pointing me to how I should check for that in order to report the bug?
    Here would be my OnMovement Callback:

    Code (CSharp):
    1.  protected internal void OnMovement(InputAction.CallbackContext context)
    2.         {
    3.             Vector2 input = context.ReadValue<Vector2>();
    4.             switch (context.phase)
    5.             {
    6.                 case InputActionPhase.Started:
    7.                     playerEntity.playerEntityAnimation.SetCurrentAnimationClip(walkAnimClip);
    8.  
    9.                     break;
    10.                 case InputActionPhase.Performed:
    11.                     facingDirection                  = GetInputDirection(input);
    12.                     SetPlayerDestination(input);
    13.  
    14.                     break;
    15.                 case InputActionPhase.Canceled:
    16.                     input                            = Vector2.zero;
    17.                     SetPlayerDestination(input);
    18.                     playerEntity.playerEntityAnimation.SetCurrentAnimationClip(idleClip);
    19.  
    20.                     break;
    21.                 case InputActionPhase.Disabled:
    22.                     break;
    23.             }
    24.            
    25.         }