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. Dismiss Notice

Can't navigate UI with arrow keys after enabling/disabling the Canvas??

Discussion in 'UGUI & TextMesh Pro' started by Teejay5, Feb 14, 2015.

  1. Teejay5

    Teejay5

    Joined:
    Feb 26, 2010
    Posts:
    106

    okay so yeah I made this pause menu with the 4.6 UI right?
    I set the navigation for both of the above buttons to 'Automatic', so I should be able to highlight them using arrow keys right?
    Well when I pause and try to do so, nothing happens. I can highlight and press the buttons with my mouse just fine.
    The canvas containing these buttons are disabled on game start, and are enabled when the player presses pause. (I use GameObject.SetActive() to enable/disable the canvas)
    HOWEVER, when I leave the Canvas enabled on start, navigation with arrow keys works just fine.
    Also if I click a button, then drag my mouse off of the button, it stays highlighted and then I can navigate with arrow keys.



    Any ideas how I can get arrow key navigation working on this pause menu?
    Maybe there's a way to highlight a button automatically through code?
     
    murp35 likes this.
  2. huulong

    huulong

    Joined:
    Jul 1, 2013
    Posts:
    223
    Exactly. I have been investigating this the past weeks. But apparently a bugfix on highlighting has created another bug making selection by script harder. I am trying to solve this in the source code Unity4.6 UI, but there seem to be pretty busy these days so even if I commit the fix they may not accept it before a few months.

    So just in case I will first show you the normal solutions, then potential issues and finally how to hack around the problem. I will also give a stub of my fix for the UI in case you want to recompile it at home.

    Select a button in the inspector

    The simplest solution is to link the "First Selected" field of the EventSystem component of the EventSystem game object to the default option of your menu (for example, the Play object). When starting the game, the EventSystem component will look for an InputModule (probably the StandaloneInputModule) which will select the object put in First Selected if there is nothing else. From there, the object will be highlighted (or rather, follow the hover animation you have set as in your game) and you can control the menu "cursor" with your keyboard (provided you enabled some Navigation) and press your submit button, or just select an object with your mouse.

    First Selected is fine, but it works only once. If you deactivate and re-activate your menu object you will see that your last selected object is still selected. Actually it will not be highlighted but if you press a directional key you the next button will be correctly selected and highlighted so you will be able to deduce which button was selected. You can also inspect EventSystem.

    So now we need to select, for example, the Play button every time we open the menu so that the player does not inadvertently exit the game by pressing enter or something. (Depending on your game, you may want to make the last menu selection persistent, in which case only the no-highlighting bug can bother you)

    Select a button by script

    This works for any game object "selectableObject" with a Selectable component. Selectable is the base class of Button, which implements all interfaces related to menu selection. Button only adds reaction to clicking and submitting.
    What you do after activating your menu parent is to access your instance of EventSystem (an "EventSystem" object has popped up automatically in your hierarchy, and it has an EventSystem component; that's what we are looking for) with EventSystem.current, then call SetSelectedGameObject(GameObject) method on selectableObject. (do not bother with the 2-parameter variant)

    Code (CSharp):
    1. using UnityEngine.EventSystems;
    2.  
    3.         EventSystem.current.SetSelectedGameObject(selectableObject);
    where selectableObject could be EventSystem.current.firstSelectedGameObject or EventSystem.lastSelectedGameObject for a persistent selection

    This code can fail for two reasons:
    1. You did not activate your menu/buttons before selecting the object. In this case, the object will be considered as selected by the EventSystem but not highlighted (OnSelect() of the Selectable script will not be called)
    2. The default menu option was already considered as selected by EventSystem (because it was set as First Selected or because you closed your menu with that option still selected). EventSystem will see this as an attempt to select the same object a second time and will ignore it.
    It seems to be a bug, but the issue has been marked as "By Design" (http://issuetracker.unity3d.com/iss...ted-gameobject-does-not-appear-as-highlighted). In any case there is some work to do, whether you want to restore the last menu selection or reset it.

    Possible hack

    First select null to ensure you are not already selecting the object. Then select the actual object.

    Code (CSharp):
    1. using UnityEngine.EventSystems;
    2.  
    3. EventSystem.current.SetSelectedGameObject(null);
    4. EventSystem.current.SetSelectedGameObject(selectableObject);
    If you think it is awkward (the object is considered as selected while being inactive), you can select null as soon as you close the menu. When opening the menu, you can simply select the default option. But be careful because of point 2 above, you should not set the default object as First Selected because it would make it selected on game start, without the guarantee of closing the menu to reset things. So you should put the reference to the default object to select somewhere else (in your MenuManager or something). Otherwise you can also artificially close your menu at the beginning of the game. (or, rather, call a Reset() method in Start() that is also called in OnDisable()).

    Selecting null when closing the menu seems more natural to me. It gives some responsibility to the programmer but a clear documentation would solve it, without the need for a fix in Unity UI source code.

    The last selection is kept in EventSystem.lastSelectedGameObject so once more you can select this if you prefer.

    Possible UI fix
    • Always reset the selection
    Each time a Selectable is disabled, if it is the current selected, select null, or select some neighbouring Selectable object. (the tricky case in which the current selection is disabled... probably won't happen in your game, though) When the last Selectable has been disabled (you can check this using the list of current active Selectables on the scene), select null anyway.

    This would replace a manual set to null in your menu manager. I don't think many games feature the tricky case I am talking about, so just selecting null when in the last OnDisable() should be enough.

    You can always select the Last Selected too.

    • Always keep Selectable state
    Remove "hasSelection = false" line in Selectable.InstantClearState() itself called by Selectable.OnDisable(). After disabling the selected object, it would still be selected while being inactive, and when it is reactivated it would be correctly highlighted (thanks to a check on hasSelection's value in Selectable.OnEnable()). If you did not try to select something else in between, the object should also be "selected", which means you can navigate from there with your keyboard or press the submit button to confirm the choice.

    However, if you select another object just after enabling the menu, the selection from last time will flash (highlight for a few frames) then the new object will be selected, so in this case prefer the other solution.

    Conclusion

    For my own project I will go for selecting null on menu close, and then I will have a look at how the EventSystem can be made more robust. Of course, everything depends on how much responsibility (to check everything is alright) the Unity team wants to give to the programmer.
     
    Last edited: Feb 16, 2015
    G76Dev, murp35, LinesinRows and 6 others like this.
  3. nesseggman

    nesseggman

    Joined:
    Dec 20, 2012
    Posts:
    22
    ^ That is an amazingly detailed response.

    I feel like using Select() is a lot easier to do from a script, though. In my current project, I have a menu in which I instantiate buttons dynamically. When the menu is brought up, I just use MyButton.Select()

    It works with any selectable, not just buttons:

    http://docs.unity3d.com/ScriptReference/UI.Selectable.Select.html

    ETA: I guess I should mention that the navigation will activate as long as there is a selected selectable. It's similar to what you're doing with the mouse click/drag thing -- one you have a button selected, the navigation kicks in. And what you're referring to as "highlighted" will be referred to as "selected" in the documentation if you need to look up more about it.
     
  4. huulong

    huulong

    Joined:
    Jul 1, 2013
    Posts:
    223
    That's right, Select() basically calls SetSelectedGameObject() but avoids using EventSystem.current.

    Developers should note that it is a method of Selectable whereas SetSelectedGameObject takes a GameObject as parameter. Depending on their scripts, using the GameObject or the Component may be shorter.

    I also prefer handling components because you can understand the role of each object just by reading the code. Internally, SetSelectedGameObject() calls generic functions that handle any kind of events, so it ends up using a plain GameObject, then getting its components and applying recasts... The price for flexibility.

    If you need to hack as suggested and select null, however, you need to use SetSelectedGameObject(null) since you can only apply the method from a Selectable.
     
    Last edited: Mar 21, 2015
  5. nesseggman

    nesseggman

    Joined:
    Dec 20, 2012
    Posts:
    22
    I started to run into problems today making some submenus and understand what you're talking about a lot better because of this experience. It was quite frustrating but your explanation and detail really helped a lot. Thanks so much for your posts, huulong.
     
  6. Teejay5

    Teejay5

    Joined:
    Feb 26, 2010
    Posts:
    106
    Thanks for the super detailed response!
    The problem is now fixed, I decided to merge the two canvases and activate/deactivate each gameobject seperately. Upon pausing, EventSystems.EventSystem.current.SetSelectedGameObject(); is called on the object I want selected.
    :)
     
  7. lordsnake

    lordsnake

    Joined:
    Dec 21, 2014
    Posts:
    4
    thanks i'm been trying to fix the problem for hours.
     
  8. Paratrooper82

    Paratrooper82

    Joined:
    Jan 17, 2018
    Posts:
    13
    Hey guys, I had a question on this. I have one canvas and one eventsystem in my scenes. But I have two sort of "submenus" which is my pause menu and my gameover menu. I can't use the evensystem and make a "first selected" because then only one of those two menus will work.

    I can navigate through my main menu just fine, but that is because I made my main menu a separate scene. The issue I am having is I can navigate through the main menu using a controller or arrows because I used the event system to make a first selected.

    I can't do that with my pause menu and gameover menu, because it seems there is only one event system for both as they are both in scene menus. So I am wondering how I can set a "first selected" button for both the pause menu and the gameover menu?

    Is there maybe a way to create a script that I can attach to both menus that creates a first selected?