New Input System Tab/Shift-Tab conflict - shift tab also triggers tab.

I’m trying to create a navigation scheme here, tab to select the next button or input field, and shift-tab to go backwards. The problem I’m having is when I press shift-tab, tab is also triggered, so it’s canceling itself out. I used debug.log to verify I was getting both events. Is there a way I can set this up so the tab event will be ignored when shift-tab is pressed?

Edit: I’ve tried the modifiers swapped in the other order, didn’t change anything. The system works in reverse, as in if I hold down tab first, then press shift repeatedly, it’ll select the buttons in reverse, but holding down shift then tab does nothing.

Edit 2: Using Behavior: Invoke Unity Events

2 Likes

At that point you should probably just make shift it’s own action and then in your input code just expand the logic that occurs when Tab is hit. When tab is hit, you can just detect if shift is held, use a branch statement, and call the Event System select method accordingly, or whatever you need to do. Essentially, just add another layer of indirection between the select firing and the input for Tab.

Thanks for the reply, but it’s over my head, can you dumb it down a little? :stuck_out_tongue:

To narrow things down: How are you using this to navigate? Are you using the Input Module provided by InputSystem? i.e. How are you getting Tab to work the way you expect it to?

The idea is simply to write code that runs when you press Tab, which then detects if shift is held. If shift is held you will navigate one direction, else you will navigate the other direction. If/else statements are known as conditional branching in programming.

Well, I’m using the new input system, so there is an event for when Tab is pressed, and an event for when shift tab is pressed. Each event runs code that causes the next (or previous) buttons to be selected. For testing, I just bound shift by itself, not shift tab, so I got the whole code working apart from the input system. Seems to me the input manager ought to be able to handle this in some way.

The code for the tab/shift tab mechanism looks like this. The tabDirection bool is set by which key binding is pressed, which just flips the order of the button selection.

 private void TabMenuCalculator(GameObject menuPanel, GameObject[] menuButtons, bool tabDirection)
    {
        if (menuPanel.activeSelf) 
        {
            GameObject[] selectedMenu = menuButtons; 
            int arrayLength = menuButtons.Length; 
            int menuCounter = _menuCounter; 

            if (tabDirection == false) 
            {
                menuCounter++;
                if (menuCounter == arrayLength)
                {
                    menuCounter = 0;
                }
                _menuCounter = menuCounter;
            }
            else if (tabDirection == true)
            {
                menuCounter--;
                if (menuCounter == -1)
                {
                    menuCounter = arrayLength - 1;
                }
                _menuCounter = menuCounter;
            }
            EventSystem.current.SetSelectedGameObject(selectedMenu[menuCounter]);
        }
    }
1 Like


The inputs just look like this. If there is a way to check if Shift is held down inside of the Tab input, I don’t know how to do that, and again, it feels like this is something that can or should be handled in the input manager.

Maybe I’ll cover a little more what’s happening. I added debug.logs to the above Tab and ShiftTab methods

Press Tab once, it is registered, and the menu advances to the next button
5924174--633128--upload_2020-5-31_19-27-0.png

Now, if I press Shift-Tab in that order, I get both events called and the button stays selected as it was before (I presume it’s impossible to see, but judging by the code, it’s selecting forward and then back immediately after)

5924174--633134--upload_2020-5-31_19-29-1.png

Now, if I hold down tab first (I advance one button forward), then start hitting shift, I start selecting buttons in reverse.

5924174--633137--upload_2020-5-31_19-29-57.png

It’s almost right, but it needs to be shift-tab, not tab-shift. Reversing which button is the modifier, and which button is the button doesn’t correct the problem.

Thanks for all the info. Your code looks well thought out, but it seems like the InputSystem API is tripping you up.

Unfortunately I don’t see how the input manager could know something like this. It detects that both shift and tab are pressed, so it fires. The other action detects tab, so it fires. If actions start requiring conditions from other actions it kind of destroys some of the simplicity/streamlined structure of the InputAction. Logistically, it seems like they are supposed to be completely independent, each doing their own job so as to make life easier for the programmer. Something more complicated is really meant to be handled in code after receiving the raw inputs from the system.

You can absolutely do that. Take a look at the docs for InputAction. You will see that you can access any of the properties related to your actions at any point in time, including InputAction.phase and ReadValue(), both of which can tell you if a button is held down. ReadValue() returns 1 when a button is held, and 0 when it is not held. Knowing this, you can get rid of the ShiftTab action and public void associated with it, and replace the action with just Shift. No callback is necessary, because we can poll it at will at any point in time. Polling is the idea that you can check the state of an action without requiring an event to be triggered. Now change your Tab function to this:

if(context.performed)
{
    //if the value of Shift is > .9, we know it is held down
    bool backwards = MyInputActionAsset.MyInputMap.Shift.ReadValue<float>() > .9f;
    TabMenuCalculator(_menuPanelHolder, _menuButtonHolder, backwards);
}

This is assuming you have generated a C# file for your asset. All the C# file does is just wrap the same functions of the generic InputAction into unique names. If you don’t have a C# generated file, you can access the generic representation of your actions by acquiring an instance of the InputActionAsset and using FindAction() to string search for your action name. Here is what that looks like:

if(context.performed)
{
    InputAction shiftAction = myInputActionAsset.FindAction("Shift");
    //if the value of Shift is > .9, we know it is held down
    bool backwards = shiftAction.ReadValue<float>() > .9f;
    TabMenuCalculator(_menuPanelHolder, _menuButtonHolder, backwards);
}

Where myInputActionAsset is a variable defined above. Then you just have to drag your asset into the variable so it has an instance to reference. And ta-da, you’ve polled the state of the “Shift” action, with any sort of setup you want! Hope that helped.

Indeed that’s the case. Much of the time, I fail to comprehend the documentation, like now. I’m still pretty new to all this. But, I’m on the right track, thanks to your help I’m just about there. I have one last hiccup.

I started with this;
5926394--633524--upload_2020-6-1_8-35-19.png

Which gave me this;
5926394--633527--upload_2020-6-1_8-36-10.png
I wasn’t quite expecting that, but there it was. So I bound it to the shift key and gave it a try.

I got rid of the shift-tab input and void and modified my tab code to look like this;

public void Tab(InputAction.CallbackContext context)
    {      
        bool shiftHeld = _shiftAction.ReadValue<float>() > .9f;

        Debug.Log($"The value of shiftHeld is {shiftHeld}, and the return value of _shiftAction is {_shiftAction.ReadValue<float>()}");
        if (context.performed)
        {          
            if (shiftHeld == false)
            TabMenuCalculator(_menuPanelHolder, _menuButtonHolder, false);
            Debug.Log("Tab pressed");
        }
        else if (context.performed && shiftHeld == true)
        {
            TabMenuCalculator(_menuPanelHolder, _menuButtonHolder, true);
        }
    }

This was the first hangup, because I wasn’t able to follow along with your example, where you have MyInputMap which looked like it should be another serialized field…I’ll come back to that in a second.

Anyway, trying to access the new Shift Action on the UI Manager Script was fruitless, the value returned 0 whether shift was held or not. So I guess that’s the wrong way to go about polling the shift key.

So I added a Shift Action to my Input manager and tried this;
5926394--633536--upload_2020-6-1_8-40-19.png
and;
5926394--633539--upload_2020-6-1_8-40-47.png

But doing that, I still can’t access the _inputMap like your example…which just means I’m doing it wrong.
5926394--633545--upload_2020-6-1_8-42-53.png

So anyway, I feel like I’m on the right track, I must missed something important, and I can’t figure out what it is I got wrong.

No problem at all, everyone starts somewhere and you’re doing great so far. You have the right ideas but there’s still a bit of confusion about the setup of the InputSystem, which is probably a common trend due to the confusing documentation on this matter.

Let’s take a step back and look at the basics. The InputSystem workflow structure is like this:

  • First you make an InputActionAsset which sits in your assets folder. You have already done this.

  • Each InputActionAsset has a list of InputActionMaps. This is the stuff on the left hand side of the panel when you edit the asset. Typically in examples they separate out Gameplay, UI, etc. as different InputActionMaps. However, the maps themselves are not any special. They are just data holders, much like the asset itself.

  • Each InputActionMap has a list of InputActions. InputActions are what you are interested in, as they will contain data about your button presses when a binding for an action is actuated. A binding is the mapping between an action and the actual input mechanism that controls it. Actuation is the amount/whether or not the mechanism is being used. As an example, your Tab action is bound to the Tab key on the keyboard. When the Tab key is actuated, the action will be “performed” because it is of the type “button”, and the performed event will be triggered. The wording here is specific for a reason–to avoid confusion and make clear every aspect of getting inputs for your game.

By default I think a C# file is generated along with your Asset. I haven’t seen any evidence that you’ve done this so I will ignore it for now. This also tends to trip people up, but in reality the C# file is just a mirror of your actual InputActionAsset with manually assuming names that reflect the same functions of the Asset, maps, and actions. Essentially, it is for convenience to reference certain names without being generic about it.

So instead we will only look at the usage of InputActionAssets in their “generic form”. In that case, you want to be acting on a single Instance of an InputActionAsset. Otherwise, if you had two assets, how would it know which one to use? You have realized this in your second experiment, which we will get to later. The point is that we have full reference to all of our actions when we have a reference to the asset we set up in our project folder.

A solid idea, but this is kind of removing the convenience of the InputActionAsset in the first place. What you are doing here is creating a new InputAction which is not bound to any Map or Asset. In which case, I’m not sure if it’ll really work properly. All of my work with inputs has been done with InputActions that were created from an InputActionAsset, not floating out in global space. Which means it may not be initialized correctly, and it’s certainly not easy to edit if you ever need to change the controls. Another problem could be that the Action is “disabled” by default, and thus won’t fire any events.

Really, what you should be doing is grabbing the Shift action from your Asset so that it’s organized in the same place as the rest of your buttons.

Although InputActions are typically made from InputActionAssets, I feel like this probably should’ve worked as long as you set up the binding correctly. My guess is that it is “disabled” or the type was not set correctly due to it being floating here without the proper UI window. Though as I said this isn’t what you should be doing anyways so let’s not worry about it.

The usage of caps here was specific. I had two examples: one where I accessed a C# file version of the Asset/Map combo. And another where I accessed the “generic form” of the Asset/Map combo. In the C# file version, the names are capital because they are uniquely generated types from the InputSystem. In other words, it would name a custom class InputManager.WhateverYouPutAsMapName, etc. But we have decided not to use the C# file, so there is no “custom class” which is named according to your Asset.

Also, MyInputMap is another data holder that really shouldn’t be made on its own as a serialized field. Instead, you should grab the corresponding map from the instance of InputActionAsset that it belongs to.

Now you are on the right track. You have a reference to an InputActionAsset and have dragged it into the variable. However, you’re hitting an issue here with some general programming logic. You have two variables: _shiftAction, and _inputMap (which is poorly named, it is not a map, it is an asset). The first mistake is we don’t need _shiftAction since we intend to pull the action from the map, not reference it directly. The second is that you are trying to get one value by using a “.” from another. This is not allowed. The period lets you access members of a class–your own variable name could never be a member of class made by Unity devs unless it coincidentally matches with something else. In this case it does not. As you can see, _inputMap is not a valid child of the InputAction class.

Furthermore, even if you had a reference to an InputActionMap, ReadValue() is not an available method from a Map. ReadValue() can only be called from Actions. Why? Because ReadValue() is supposed to measure the actuation of a specific action, based on which bindings are pressed. If Shift is held, ReadValue() will return 1. Otherwise, it will return 0. Both results are from the Shift Action itself, not the Map.

Okay, so what does InputActionAsset have for us to use? Let’s take a look at the docs.

We have FindAction(), and FindActionMap() as methods. Okay! So we can get an action directly from the Asset reference itself, or we can be more specific and narrow down our Map, then get the Action from there. How? Well, InputActionMap also has a FindAction() method, look it up!

So, our final code will look like this (using your variable names):

public void Tab(InputAction.CallbackContext context)
{
    //Make a local copy of InputAction for ease of use
    InputAction testShiftAction = _inputMap.FindActionMap("The Name of My Action Map").FindAction("The Name of My Shift Action that I set in the Asset's UI Edit window");
    //Now check if the action is actuated
    bool shiftHeld = testShiftAction.ReadValue<float>() > .9f;
   
    //...
}

If the name corresponding to your Map/Action is not found, it will be a null reference exception. Note here that _inputMap is actually a reference of InputActionAsset and one that you have to drag the asset into. You should get rid of your _shiftAction as you don’t want to be making a new Action for this behavior.

One last thing. It’s possible that your InputActions are all disabled when the game starts. Don’t ask why, it’s just a thing that seems to happen. So the fix is simply to enable the asset itself, which just calls enable on all maps and actions. So in your Awake() method, just write this:

_inputMap.Enable();

That is assuming you keep the name _inputMap for your InputActionAsset reference, which imo is not good. I hope you understand it now, let me know if that makes sense! Feel free to ask more questions if you’re confused about anything specific.

1 Like

Bingo, Bango, Bongo, that did the trick

Thank you for all the help and the in depth explanation. That was super helpful. I took your advice and renamed my variable to something more appropriate. I also threw in an Enable to void Start, as I don’t have an Awake method. Now, with a few tweaks, I have a functioning tab/shift-tab method.

public void Tab(InputAction.CallbackContext context)
    {
        InputAction testShiftAction = _inputActionAsset.FindActionMap("Default").FindAction("Shift");
        bool shiftHeld = testShiftAction.ReadValue<float>() > .9f;

        if (context.performed && shiftHeld == false)
        {          
            TabMenuCalculator(_menuPanelHolder, _menuButtonHolder, false);
        }
        else if (context.performed && shiftHeld == true)
        {
            TabMenuCalculator(_menuPanelHolder, _menuButtonHolder, true);           
        }
    }

5927375--633749--Menu.gif

1 Like

Glad I could help. One last thing I should note here is that unless you have a good reason not to, it’s probably better to just make one function call to TabMenuCalculator, and pass in shiftHeld instead manually writing out the true/false possibilities. I know I said before that the solution is branching, but that was meant more as a specific implementation for the actual selection itself. You’ve already covered that in TabMenuCalculator, so here it’s faster just to write code that takes advantage of what you know.

You know that TabMenuCalculator(yadda yadda, false) will occur when shiftHeld is false, and you know that TabMenuCalculator(true) will occur when shiftHeld is true. Therefore, you can simplify your logic to this:

public void Tab(InputAction.CallbackContext context)
    {
        InputAction testShiftAction = _inputActionAsset.FindActionMap("Default").FindAction("Shift");
        bool shiftHeld = testShiftAction.ReadValue<float>() > .9f;

        if (context.performed)
        {         
            TabMenuCalculator(_menuPanelHolder, _menuButtonHolder, shiftHeld);
        }
        
    }

It doesn’t make a difference now since you’ve already written it. But if you’re new to programming these are things to keep in mind for the future that will help you write more efficiently. You may already know this, but you should always avoid redundancies where possible as it saves time and lets you get more work done. Best of luck!

I do, I use the mouse scroll wheel also, which uses that TabMenuCalculator function. However, that was a slick little piece of code clean up, I’m totally taking that :stuck_out_tongue: Thanks again.

1 Like

I had the same issue as the original post, and there’s a fairly easy solution if your setup allows for a MonoBehaviour to handle the inputs. I use this in my UI (other actions removed for clarity on this specific issue) so anything that needs to control UI elements just finds this MonoBehaviour (it’s on my main game manager object) and “polls” for the input.

Action: NavigateNext
Binding: Tab
Action NavigatePrevious
Binding: (ButtonWithOneModifier) Shift + Tab

public class UIActionManager : MonoBehaviour
{
    public InputActionAsset inputActionAsset;

    private InputAction navigateNextAction;
    private InputAction navigatePreviousAction;

    void Awake()
    {
        this.inputActionAsset.Enable();
        this.navigateNextAction = this.inputActionAsset.FindAction("UI/NavigateNext", true);
        this.navigatePreviousAction = this.inputActionAsset.FindAction("UI/NavigatePrevious", true);
    }

    /* NAVIGATE NEXT */
    public bool GetNavigateNext()
    {
        return this.navigateNextAction.triggered && !this.navigatePreviousAction.triggered;
    }

    /* NAVIGATE PREVIOUS */
    public bool GetNavigatePrevious()
    {
        return this.navigatePreviousAction.triggered;
    }
}

I have a simple alternative solution:

private bool _prevFieldInProgress;

public void OnPrevField(InputAction.CallbackContext context)
{
    if (context.started)
    {
        _prevFieldInProgress = true;
        return;
    }
    else if (context.canceled)
    {
        _prevFieldInProgress = false;
        return;
    }
    if (!context.performed) return;

    // Do the thing...
}

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

    // Do the thing...
}

However, none of these answers are really the right answer to the problem, because we’re in semantic action space, at this point, and this problem isn’t really about the semantic actions “previous field” vs. “next field” (which could plausibly be rebound to something else entirely, by the player). The problem is with the input system, itself. If we allow key rebinding, the player could create some entirely unrelated modified and unmodified input action pairing, like this, at any point, and we have no tidy way to anticipate this, or defensively program around it, on the receiving end.

In short, I think that this is a problem with the Input System that really does need to be addressed by Unity, in the long run.

2 Likes

This is what i used today. 7432394--910406--412.PNG
The lines before the blank line are the ones i used to move the character in a 2d space. thought of swapping the “Vector2” part with “float” and it worked. if shift is not pressed it stays with value 0 and if you press it it not 0 anymore.

I have another sulotion.

Action: Next [button]
Binding: Tab
Action Prev [button]
Binding: (ButtonWithOneModifier) Shift + Tab
Action: Shift [must be value]
Binding: Shift

       private void OnNext(InputValue inputValue) {       
            if(shift != 0) {
                return;
            }
       
        }

        private void OnPrev(InputValue inputValue) {
        
        }

        private float shift;
        private void OnShift(InputValue inputValue) {
            shift = inputValue.Get<float>();
        }

Still an issue in 2023. This should be handled by the input system. A super-easy solution is to base it on the order of the actions within an action-map. If an action meets the key bindings, don’t keep evaluating others that contain the same pattern of keys.