I still have a big feature to do and that is UI navigation using all the input devices — keyboard, mouse and controller. I want these done in a way that I can easily modularize, yet keep concrete pipeline that my scripts can implement. Basically, I don’t want to wire every button tab and custom element I create with every potential input. I just want them to be automatically navigatable — clickable, selectable with arrows, controller. And while Unity partially provides such navigation, I need it in layers — not navigating tab pages while I am in the page. And I also need the system to pass any input not already processed by navigation to my actual controls/panel/element. Basically, Unity UI cannot do this without me writing more spaghetti than I already have for all of this.

First of all, I need a script that I can place on every button, tab or navigatable element:

In short, this script receives events from player’s input – up, down, left, right, submit, cancel. I then process it myself, based on what the navigator wants. If it doesn’t do anything, then my own elements are given the input.

I immediately added a sprite swapper since buttons and tabs already had it, just not via this navigation framework. I’ll use it as before, selecting sprites when the element is highlighted or activated. In fact, I decided I need both sprites and I swapped the ones I have around — orange for player’s navigation and lighter blue for whatever selected is there.

The idea is that I can navigate UI within “groups”, like this:

Green arrows transition between the selected group. Orange array is entering/exiting the group. So I can browse tabs, then enter into the tab and browse items, then exit tab.

First thing, I am adding logic selection for when any navigation direction is selected, such as left/right arrows:

I am also adding overflow logic for things like “next” and “previous” in hierarchy, so they can swap around the list.

And this makes the tabs nicely navigate left/right:

Before I totally lose where all my navigators are, I decided to add little icons in hierarchy showing which object have them:

Now I need to actually wire a callback to the panel to tell it to switch tabs:

I previously did it with my QuickTab logic, but that’s getting axed with the new navigation system. I can now keep track of selected elements automatically and receive events when needed.

I add this script to prefabs, so then I override the needed value in actual panels, such as main menu panels wanting navigation:

And this also works nicely.

Now, more complicated stuff — groups and more intricate layouts. First, I need all navigators to know what “layer” they belong to. This way, the navigation can always start on top and then go into lower layers.

As I am making more options, I also extended the icons to actually show what is happening with arrows, including layers:

Firstly, I need a way to react to player’s submit input and go into the next layer. I call it “descend”:

If I am browsing my player info panel’s tabs, this would send the navigation into the current tab.

Since I am also using actuation for other needs, I am adding a callback:

That way I can wire buttons, such as pause or main menu panel buttons, to react to player’s input:

In the same vein, player’s cancel input would “ascend” to the previous level:

Now I can add navigation scripts to all my navigatable elements, like the player loadout panel (which I spent way too long hard-coding) navigation rules:

And it works nicely, respecting the layers:

I can move between slots, between items, and actuate any item to equip. All the highlighting and selecting happens automatically. The only thing I really do is listen to callbacks and highlight the equipped item.

Of course, none of this was quite as easy. There were a lot of issues and debugging. For example, the items may be in the lowest layer, but there are items in each lowest layer. So I have to keep track of which layer has which element selected, otherwise I end up with weirdness like this:

I have to deselect any elements that I navigate away from in every layer, but I have to do this only for layer before. I cannot automatically find the elements either, because they may or may not be in the same hierarchy. In short, there’s a lot to consider. And I’m sure I could have done much of better.

So far, all of this was done with button navigation. That’s easy on the whole layer logic as I explicitly go up and down levels. However, I am also making every navigatable element respond to a direct mouse click. This mean the navigation has to handle any element suddenly being selected, no matter where in hierarchy and layer it is.

Just to illustrate some other bugs that happen when the logic isn’t complete (and why it takes so long to do). Here’s clicking a button directly in a panel — leaves the tab visually selected:

Here’s clicking another tab while “being” in the previous tab — doesn’t deselect clear sibling highlight:

Here’s clicking away from tab and then back in, but the old in-tab selection is still highlighted:

Et cetera.

Also, I don’t actually want clicking element to necessarily actuate them. For example, it’s great for buttons and tabs. But stuff like equipment slots or keybinding entries should get equipped and poll for input just when clicked once. Second click when selected: sure. So I need a flag for this:

At this point, most of my existing panels pretty much work with the new system. There are a few places, like the goals panel, where the navigation is extra complicated. I will likely need to cover this in a separate post when I update the actual UI for it. For now, I think I have spent quite a lot of time on this and it’s certainly a “task done”. I will be getting back to UI soonish. But I might need a break from UI for a bit.

Related Posts

MicroRogue DevDiary #84 – UI navigation system
Tagged on:     

Leave a Reply

Your email address will not be published. Required fields are marked *