- Home /
360 Degree UI Controller Automatic Navigation
So I've been working on integrating controllers into my game, and thus have needed my UI to be navigable by controllers. To do this, I've been using Unity's UI Selectable class and its automatic navigation (with some exceptions where I use explicit). This however seems increasingly insufficient, especially for UI that gets dynamically filled in, such as my awards screen as shown here:
. .
I was wondering if anyone's found solutions to simply get the UI element that the controller axis is pointing at (which would enable more than 4 directional UI selection). It seems like there should be a really obvious answer to this but I haven't been able to find one anywhere, and it's bumming me out x__x
Answer by Zilby · Oct 03, 2018 at 03:15 AM
Ended up writing a script for it overriding Selectable by modifying a default Selectable script. It uses some of my own input manager RInputManager to determine if the UI is currently using controller input, but aside from that it can be used basically anywhere. (Can also have it override Button to use it with those):
.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
/// <summary>
/// OmniDirectionalUI allows for UI to receive directional input from a controller in any direction.
/// Written by Alex Zilbersher.
/// </summary>
public abstract class OmniDirectionalUI : Selectable
{
private static bool justMoved = false;
private const float MOVE_DELAY = 0.2F;
protected override void Start()
{
base.Start();
Navigation n = new Navigation();
n.mode = Navigation.Mode.Explicit;
navigation = n;
interactable = true;
}
public virtual void Update()
{
if (RInputManager.ControllerInput && currentSelectionState == SelectionState.Highlighted)
{
Vector3 dir = new Vector3(RInputManager.Horizontal(RInputManager.P1), RInputManager.Vertical(RInputManager.P1), 0.0f);
if (dir.magnitude > 0.1f && !justMoved)
{
Selectable s = FindSelectable360(dir);
if (s != null)
{
EventSystem.current.SetSelectedGameObject(s.gameObject);
StartCoroutine(Moved());
}
}
}
}
protected IEnumerator Moved()
{
justMoved = true;
yield return new WaitForSecondsRealtime(MOVE_DELAY);
justMoved = false;
}
// Find the next selectable object in the specified world-space direction.
public Selectable FindSelectable360(Vector3 dir)
{
dir = dir.normalized;
Vector3 pos = (Vector3)(transform as RectTransform).rect.center;
pos = transform.TransformPoint(pos);
float minDist = Mathf.Infinity;
Selectable bestPick = null;
for (int i = 0; i < allSelectables.Count; ++i)
{
Selectable sel = allSelectables[i];
if (sel == this || sel == null)
continue;
if (!sel.IsInteractable() || sel.navigation.mode == Navigation.Mode.None)
continue;
var selRect = sel.transform as RectTransform;
Vector3 selCenter = selRect != null ? (Vector3)selRect.rect.center : Vector3.zero;
selCenter = sel.transform.TransformPoint(selCenter);
Vector3 myVector = (selCenter - pos);
float angle = Vector2.Distance(dir, myVector.normalized);
if (angle > 1)
continue;
float dist = Vector2.Distance(pos, selCenter);
float score = angle + (dist / 5);
if (score < minDist)
{
minDist = score;
bestPick = sel;
}
}
return bestPick;
}
}