Creating UI Tabs in Unity: Part 3

Posted On: 2020-08-24

By Mark

Today's post is part 3 in a series about how to add tabs to an in-game UI in Unity. In part 2 I described a simple implementation, along with some inadequacies it brings. In this part, I'll cover some of the polish work that goes into making it work seamlessly with a controller.

Controller Support

As mentioned at the end of part 2, Unity has built-in keyboard/controller support (the navigation system), but the results of using it are often of mixed quality. Some layouts will work as expected by default, while others will require manual adjustments before they will make sense. Fortunately, Unity provides quite a bit of flexibility for configuring navigation, so (with a bit of work) it should be possible to get it to behave well for any UI layout.

In order to effectively do so, however, it is important to understand the various tools at your disposal to configure the navigation system. Covering that foundational information could easily be a blog post unto itself, but I want to stay focused on the tab control, so I'll instead point you to an excellent blog post by Dylan Wolf. That post approachably explains* how the navigation system works, how (and when) to use the "explicit" navigation mode, and provides a clear example of how a non-conventional UI (a vertical list of controls that are operated using left/right inputs) can be coaxed to behave correctly by setting the navigation settings through code.

Customizing Navigation

There are three main kinds of navigation when using a tab control: navigation between tabs, navigation inside the content area, and navigation between the content area and the tabs themselves. Each of these can be tackled individually, but the third (going between tab and content) will require that the other two be completed first.

Most tabs are laid out with the tab strip running in a line along one axis, and the content itself aligned on the other axis*. Thus, the navigation of the buttons should be laid out accordingly: one axis (left/right or up/down) handles the navigation between tabs, and the other axis always points to the tab content. While it is possible to set this up manually in the inspector, the simplicity and consistency of the tabs make it just as easy to do so with code. Thus, for a horizontal tab strip**, one could set it up in the Start method:

for (var i = 0; i < TabCollection.Length; i++)
    var navigationSetting = TabCollection[i].TabButton.navigation;
    navigationSetting.mode = Navigation.Mode.Explicit;
    if (i > 0)
        navigationSetting.selectOnLeft = TabCollection[i - 1].TabButton;
    if (i + 1 < TabCollection.Length)
        navigationSetting.selectOnRight = TabCollection[i + 1].TabButton;
    TabCollection[i].TabButton.navigation = navigationSetting;

With regard to arranging the tab content area itself - that is largely an exercise for the reader. The layout of the content will be different for every reader (perhaps even every tab as well), so I cannot provide any specific guidance on configuring that. There are, however, a few general tips to keep in mind:

From Tab Strip To Content (And Back)

The final part of the navigation, moving between tab strip and tab content area, can be configured a few different ways, each with a slightly different user experience. For the purposes of this post, I am assuming the following is the desired experience*:

  1. Moving from tab strip to tab content should pick one specific element (the "first" item)
  2. Moving from tab content to tab strip should pick the currently selected tab
  3. More than one piece of tab content can navigate to the tab strip, and they all do so using the same direction (ie. hitting "up" on any item in the top row will move to the tab strip.)

For the first requirement, the tab content needs the ability to express which item is the "first" one. This means a small update to the way tab pair is implemented: instead of the content area being a CanvasGroup, it will be a new object type (TabPane) which has both the canvas group and the "FirstItem" that will be selected when navigating away from the tab strip.

 public class TabPane : MonoBehaviour
    public Selectable FirstItemInPane;
    public CanvasGroup ContentDisplay;
public class TabPair
    public Button TabButton;
    public TabPane TabContent;

This will require some minor code adjustments (updating code using TabContent to TabContent.ContentDisplay), but once those are done it is possible to configure moving from the tab strip to that first item in the tab content. To do so, just update PickTab to also set the navigation:

public void PickTab(int index)
   //existing code omitted for brevity
    for (var i = 0; i < TabCollection.Length; i++)
        var navigationSettings = TabCollection[i].TabButton.navigation;
        navigationSettings.selectOnDown = TabCollection[i].TabContent.FirstItemInPane;
        TabCollection[i].TabButton.navigation = navigationSettings;

The second requirement vastly simplifies going back from tab content to tab strip. For any given content area, returning to the tab strip always returns to the exact same tab; it therefore is possible to simply set that navigation in the inspector (using Explicit Mode). Thus, the only thing required here is to explicitly point the navigation for any relevant controls so that the correct input (ie. up) goes to the tab strip button for that tab content*.

With that in place, you should have a fully functioning, controller-supported* tab control. Although the control requires some work to set up the correct navigation on the tab content area, the amount that is actually related to the tabs (adding/removing connections from content to tab strip) is generally going to be less than what any (non-grid) screen requires to set up sane controller navigation.

With the functionality complete, it's time to look at some of the visual polish, such as animations. Next week*, I'll cover how I use SLayout to achieve some simple animations, and (briefly) explore some of the UX options available to designers using the tab control.