Skip to content

Instantly share code, notes, and snippets.

@hyakugei
Created April 19, 2019 21:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hyakugei/2054c949dde9c61f93e55fbf3a88a990 to your computer and use it in GitHub Desktop.
Save hyakugei/2054c949dde9c61f93e55fbf3a88a990 to your computer and use it in GitHub Desktop.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using deVoid.UIFramework;
public class SetSelectedOnEnable : MonoBehaviour
{
public GameObject toBeSelected;
public MonoBehaviour screenController;
IUIScreenController _screenController;
GameObject _previouslySelected;
protected GameObject previouslySelected
{
get
{
return _previouslySelected;
}
set
{
_previouslySelected = value;
}
}
protected static GameObject staticCurrentSelected;
private void Awake()
{
if (screenController) _screenController = screenController as IUIScreenController;
if (_screenController != null)
{
_screenController.InTransitionFinished += _screenController_InTransitionFinished;
}
}
private void OnEnable()
{
if (_screenController != null) return;
if( toBeSelected == null)
{
// reselect the last selected item.
// if the last selected item is now non-functional, get another sibling?
var selected = EventSystem.current.currentSelectedGameObject;
if(selected)
{
if (selected.activeInHierarchy == false) Debug.Log("-- current selected is not active in hierarchy...", selected);
var sel = selected.GetComponent<UnityEngine.UI.Selectable>();
if (sel != null && !sel.interactable) Debug.Log("-- selectable is not interactable...", sel);
}
EventSystem.current.SetSelectedGameObject(EventSystem.current.currentSelectedGameObject);
return;
}
SetSelected(toBeSelected);
}
private void Update()
{
if (EventSystem.current.currentSelectedGameObject) staticCurrentSelected = EventSystem.current.currentSelectedGameObject;
if (EventSystem.current.currentSelectedGameObject == null && staticCurrentSelected != null)
{
Debug.Log("-- resetting currentSelected to " + staticCurrentSelected, staticCurrentSelected);
EventSystem.current.SetSelectedGameObject(staticCurrentSelected);
}
}
private void OnDisable()
{
if (previouslySelected && EventSystem.current != null && previouslySelected.activeInHierarchy)
{
Debug.Log("-- OnDisable() setting: " + previouslySelected, previouslySelected);
EventSystem.current.SetSelectedGameObject(previouslySelected);
}
}
void SetSelected(GameObject go)
{
previouslySelected = EventSystem.current.currentSelectedGameObject;
EventSystem.current.SetSelectedGameObject(go);
Debug.Log("-- SetSelected() previous: " + previouslySelected + " current: " + go, this);
}
void _screenController_InTransitionFinished(IUIScreenController obj)
{
if (toBeSelected) SetSelected(toBeSelected);
else
{
var btn = GetComponentInChildren<Button>();
if (btn)
{
SetSelected(btn.gameObject);
}
else
{
Debug.LogError("Unable to find child object to set selected on...", this);
}
}
}
}
@hyakugei
Copy link
Author

Added to the following prefabs in the example scene:

  • CameraProjectionWindow -- both to be selected and screen controller slots empty
  • ConfirmationPopup -- to be selected has the ConfirmButton
  • NavigationPanel -- screen controller set to NavigationPanel
  • PopupExampleWindow -- to be selected set to Button
  • StartGameWindow -- to be selected set to StartButton

Now this is using InControl and its In Control Input Module to get the controller input into the unity event system. And, as you mentioned, you can still navigate to "disabled" ui items (when in a "modal" window). But it properly selects buttons in the popups, and you don't loose something to navigate from and to (due to my brute force resetting of the currently selected item in Update()).

Again, as you mentioned, dealing with only navigating to "valid" selectables is a problem to be solved...

@hyakugei
Copy link
Author

For local multiplayer would be a requirement to have specific windows/panels which only respond to a specific controller's input... I'm not sure how to do that with the InControl Input module, if that is even possible, without some kind of more manual input routing.

@yankooliveira
Copy link

This is actually quite more involved than my original hack for this matter: mine was simply something on the lines of

public class AutoSelectInteractable : MonoBehaviour {
    private void OnEnable() {
        EventSystem.current.SetSelectedGameObject(gameObject);
    }
}

So it would simply always select the same object upon enabling the containing window (the component would be added to the object itself, not to the window controller). That said, you have an interesting UX case that is not covered by that at all, which is keeping the last element selected (as it makes sense: if you're navigating with a controller from screen A to screen B, you probably want to have the button you clicked to go to screen B selected when you return).

You also have one interesting solution of hooking up to the animation to make sure you can't input while transitioning - which made me double check the UIFrame code: it has

private void OnRequestScreenBlock() {
        if (graphicRaycaster != null) {
            graphicRaycaster.enabled = false;
        }
    }

And I wonder if you could actually disable the event system or the input module instead of just the graphics raycaster - that way in theory, even if you pressed a button, the even wouldn't be called. If that doesn't work or generates other problems, there also seems to be a toggle called EventSystem.sendNavigationEvents.
If one of those options covers all the cases, you don't have to worry about hooking to a screen's animation at all, as it would be covered by the system itself.

So back to your solution specifically: If I understood correctly, you're storing the previous one when you're selecting a new one, and then restoring it OnDisable, and I imagine you have one of these per screen, which would mean it should work in most cases - I do feel there might be some edge cases to that (if, eg, you're not dealing with a straight history), but then again, you're restoring back to some selectable if you don't have an option. So all in all, it's very much a "if it works, it works" :D

If you're willing to poke around in the framework code itself (and is what I'll probably test first when I come to this), is instead of relying on an external component to store your data, relying on the WindowHistoryEntry: you could add a field in there like
public GameObject LastSelectedElement;. You could then manipulate that in the WindowUILayer: upon closing, you'd write the contents of EventSystem.current.currentSelectedGameObject, and upon opening you'd set it if that property was set. Mind you this is all very much from the top of my head and not tested at all, so I might be missing something hahah

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment