Create a ScreenManager

Code and workflows for navigate between different views using UI Builder and UI Toolkit

What you'll develop on this page

Navigate between different views using UI Builder by creating Navigation Scene with a ScreenManager.

Github branch link: https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/tree/Creating-a-ScreenManager

Background on the UI Approach

Components of our UI

Often in games there is a "title" screen with options or settings the user sets up before going into the game. In this section we will implement the ability to switch between different UI views like that in this page.

We will create a new Scene called "NavigationScene." We choose the name "NavigationScene" because the users of Moetsi XR experiences usually need UI to "navigate" to the environment they want to join.

NavigationScene will have a GameObject that contains a "UI Document" Component which has a "Source Asset" field. We will put in a ScreenManager ("TitleSceneManager") that will control the display of different views in the Scene. You should think of the TitleSceneManager as the "switchboard" that decides which views are shown.

We will have 4 "views" within our TitleScreenManager:

  1. Title Screen (the first view you see)

  2. Host Game Screen (to host a local game)

  3. Join Game Screen (to join a local game)

  4. Manual Connect Screen (to manually connect to a game)

"ScreenManager" is not an official Unity term. It is a term we use to indicate a design pattern whereby a "managing" View handles the switching between different views.

How we'll build our UI

You will notice a pattern for how we build UI. We start with an UXML. Usually we put a custom Visual Element (cVE) as the first child of the UXML.

The most basic building block in UI Toolkit is a visual element. The visual elements are ordered into a hierarchy tree with parent-child relationships. The diagram below displays a simplified example of the hierarchy tree, and the rendered result in UI Toolkit.

Visual elements

The VisualElement class is the base for all nodes in the visual tree. The VisualElement base class contains common properties for all controls, such as styles, layout data, and event handlers. Visual elements can have children and descendant visual elements. For example, in the diagram above, the first Box visual element has three child visual elements: Label, Checkbox, and Slider.

You can customize the appearance of visual elements through stylesheets. You can also use event callbacks to modify the behavior of a visual element.

VisualElement derives into subclasses that define additional behavior and functionality, such as controls. UI Toolkit includes a variety of built-in controls with specialized behavior. For example, the following items are available as built-in controls:

  • Buttons

  • Toggles

  • Text input fields

You can also combine visual elements together and modify their behavior to create custom controls. For a list of built-in controls, see the Control reference page.

From UI Toolkit's Visual Tree Documentation

Within that custom Visual Element (cVE) we put all the elements that come to mind when we think of UI: text, buttons, labels, input fields, images (these are all also "Visual Elements" in UI Toolkit terminology) and give each of the elements a "Name" so that we can reference them within our custom Visual Element (cVE).

Standard Visual Elements are Visual Elements provided by Unity with standard functionality (label, textfield, buttons, input fields, images). cVEs will be created by us and they do things like add callbacks to children Visual Elements or set data to Visual Elements.

We will follow the flow of UXML > cVE > Standard Visual elements throughout this gitbook.

  • Admittedly, this might be a bit confusing without any context so let's just get to it!

Setting up our Components

  • First let's begin by adding the necessary packages to our manifest

  • Open the manifest.json file in your Project folder and add these three lines to the bottom:

"com.unity.ui": "1.0.0-preview.18",
"com.unity.ui.builder": "1.0.0-preview.18",
"com.unity.vectorgraphics": "2.0.0-preview.19",

You might get warnings and errors after adding these packages.

This is because the packages are still a bit wonky. Fear not, these warnings and errors will not cause any problems; please ignore them. Each time you open the project the errors will appear, but they won't cause any trouble.

This does NOT mean from now on you can ignore all errors in the gitbook. Make sure you do not have any errors before you add these packages and note the type of errors that appear after you install the packages. These are UI Toolkit/UI Builder errors.

  • Create a new folder in "Assets" called "UI"

  • Within our "Scenes" folder, right-click, select "Create", then select Scene and name the newly created scene "NavigationScene"

  • Double click on "NavigationScene" to open it

  • Right click in the Hierarchy and create an empty GameObject called TitleScreenUI

  • Add an "Input System Event System (UI Toolkit)" component

Class EventSystem

Use this class to handle input, and send events to a UI Toolkit runtime panel.

From UI Toolkit's Event System documentation

  • Also add a "UI Document" component

Class UIDocument

Defines a Component that connects VisualElements to GameObjects. This makes it possible to render UI defined in UXML documents in the Game view.

From UI Toolkit's UIDocument documentation

  • Notice the field in the UI Document component called "Source Asset"

    • As mentioned in the Overview page of this section of the gitbook, this is going to be our "parent" for all our visual elements/uxmls

  • Open the UI folder, right-click inside, hover over "Create", navigate down to UI Toolkit, and select "Panel Setting Asset" name it "PanelSettings"

Class PanelSettings

Defines a Panel Settings asset that instantiates a panel at runtime. The panel makes it possible for Unity to display UXML-file based UI in the Game view.

From UI Toolkit's PanelSettings documentation

  • We created a "Panel Settings" with default Unity values

    • No need to update most of these as the default values work fine

    • Digging into the weeds of each field in Panel Settings is a bit overkill for this gitbook, but if you're curious we recommend that you check out the documentation linked above

  • Select PanelSettings and in the inspector change Scale Model to "Constant Pixel Size"

  • Drag "PanelSettings" into the "Panel Settings" field in the UI Document component found in Inspector when TitleScreenUI is selected in Hierarchy

  • Right-click in our UI folder, hover over Create, navigate down to UI Toolkit, and select "UI Document" and name the file TitleScreenManager

    • This will be our parent uxml of our NavigationScene UI

  • Drag it to the Source Asset field of the UI Document component found in Inspector when TitleScreenUI is selected in Hierarchy

  • Now create your next 4 UI Documents (right-click in UI folder, Create, hover over UI Toolkit, and select UI Document, naming each individual UI Document as follows):

    • TitleScreen

    • HostGameScreen

    • JoinGameScreen

    • ManualConnectScreen

  • If you expand each of the uxmls (UI Documents) you will notice that they each have something called inlineStyle

    • We will have a shared USS that will allow us to share stylings across views

    • But a view might have some stylings that are specific to that one particular view and to no other views; these stylings are called "inline" styles

      • (AKA styles that are part of the document but do not come from a separate USS sheet)

  • For now, we're not going to paste in any code snippets into these uxmls we just made right now

  • Now let's create our 4 custom Visual Elements (cVEs)

    • Again, they will have the exact same names as the uxml files. Please try not to get confused!

  • To make each of the 4 cVEs, just right-click in the UI folder > Create > C# Script and name each file according to the four bullet points below, pasting the code snippet underneath into each cs files:

  • TitleScreenManager

using UnityEngine;
using UnityEngine.UIElements;

public class TitleScreenManager : VisualElement
{
    
    public new class UxmlFactory : UxmlFactory<TitleScreenManager, UxmlTraits> { }

    public TitleScreenManager()
    {
        this.RegisterCallback<GeometryChangedEvent>(OnGeometryChange);
    }

    void OnGeometryChange(GeometryChangedEvent evt)
    {
        

        this.UnregisterCallback<GeometryChangedEvent>(OnGeometryChange);
    }
}
  • HostGameScreen

using UnityEngine;
using UnityEngine.UIElements;

public class HostGameScreen : VisualElement
{
    
    public new class UxmlFactory : UxmlFactory<HostGameScreen, UxmlTraits> { }

    public HostGameScreen()
    {
        this.RegisterCallback<GeometryChangedEvent>(OnGeometryChange);
    }

    void OnGeometryChange(GeometryChangedEvent evt)
    {
        

        this.UnregisterCallback<GeometryChangedEvent>(OnGeometryChange);
    }
}
  • JoinGameScreen

using UnityEngine;
using UnityEngine.UIElements;

public class JoinGameScreen : VisualElement
{
    
    public new class UxmlFactory : UxmlFactory<JoinGameScreen, UxmlTraits> { }

    public JoinGameScreen()
    {
        this.RegisterCallback<GeometryChangedEvent>(OnGeometryChange);
    }

    void OnGeometryChange(GeometryChangedEvent evt)
    {
        

        this.UnregisterCallback<GeometryChangedEvent>(OnGeometryChange);
    }
}
  • ManualConnectScreen

using UnityEngine;
using UnityEngine.UIElements;

public class ManualConnectScreen : VisualElement
{
    
    public new class UxmlFactory : UxmlFactory<ManualConnectScreen, UxmlTraits> { }

    public ManualConnectScreen()
    {
        this.RegisterCallback<GeometryChangedEvent>(OnGeometryChange);
    }

    void OnGeometryChange(GeometryChangedEvent evt)
    {
        

        this.UnregisterCallback<GeometryChangedEvent>(OnGeometryChange);
    }
}
  • These are the custom Visual Elements (cVEs) we will nest in our UXMLs

  • In the code for each, you will notice that we work within 3 boilerplate "sections" (for lack of a better word) to build our UI. They are as follows:

    • UxmlFactory

      • (this will allow us to read from the UXML we are nested in so we can add functionality)

      • We will not alter this

    • this.RegisterCallback(OnGeometryChange)

      • We will not alter this

    • OnGeometryChange(GeometryChangedEvent evt)

      • This runs each time the layout is updated

      • Here we will assign any visual elements we want to interact with and register any callbacks

      • Think of it like Create() or OnCreate()

We have named our custom Visual Elements (cVEs) after the uxmls they will be nested in.

FYI: you are able to change the name of your cVEs and nest them anywhere; the name is not a dependency for working with uxml.

However, if you do change the name you must change it in the 3 places you see the name used in the files above:

  • Where the public class is defined (at the top)

  • Where you define the uxml factory

  • The constructor that contains registering the callback OnGeometryChange

Again, if you change the name of a cVE, the uxmls will not automatically pick up this change. Instead, you will need to manually update the names in the uxml files themselves.

We at Moetsi like to match names as a rule of thumb (although it may seem confusing) to provide clarity on exactly what each cVE should do. We don't find it too confusing because the uxmls and cVEs are different file types so the little iconography next to the file names symbolize the difference between the two.

These are a lot of opinionated decisions we made for the sake of the gitbook. Anyone with frontend webapp experience knows how many different ways you can skin a cat when it comes to a front end. We are trying to show you all the techniques you can use with latest packages. Actual structure and separation of concerns in projects can vary a lot based on architecture approach.

Class VisualElement.UxmlFactory

Instantiates a VisualElement using the data read from a UXML file.

From UI Toolkit's UxmlFactory documentation

GeometryChangedEvent

Event sent after layout calculations, when the position or the dimension of an element changes. This event cannot be cancelled, it does not trickle down, and it does not bubble up.

From UIElement's GeometryChangedEvent documentation

  • Now that we have created our uxmls and cVEs let's open up UI Builder

    • Navigate to "Window', select "UI Toolkit", then choose "UI Builder"

  • We are now ready to to structure our components

    • You might not have "TitleScreenManager" already available in the Hierarchy when you open UI Builder, don't worry we open it up as our next step below

We created our UI components in this section and we are prepared to structure them in the next section

  • We created NavigationScene

  • We added a TitleScreenUI GameObject

  • We added Event System (UI Toolkit) and UI Document

  • We created uxmls

    • TitleScreenManager

    • TitleScreen

    • HostGameScreen

    • JoinGameScreen

    • ManualConnectScreen

  • We created custom Visual Elements (cVEs)

    • TitleScreenManager

    • HostGameScreen

    • JoinGameScreen

    • ManualConnectScreen

  • We navigated to UI Builder

Working with UI Builder

We are going to use the UI Builder to put our different components together and to also add additional standard Visual Elements. In the next section we will also do styling in the UI Builder.

You will notice that there is no way to create new Visual Elements through the UI Builder. This is why we started this section by creating those items first. It is generally a good idea to plan out your views and structure before working with UI Builder to make construction a bit easier.

The main UI Builder window

  1. StyleSheets: Manage StyleSheets and USS Selectors to share styles across UI Documents (UXML) and elements.

  2. Hierarchy: Select, reorder, reparent, cut, copy, paste, delete elements in the UI hierarchy of your document.

  3. Library: Create new elements or instance other UI Documents (UXML).

  4. Viewport: See what your UI Document (UXML) looks like and edit elements visually directly on the Canvas.

  5. Code Previews: See what the UI Builder is creating as text for both the UI Document (UXML) and the StyleSheets (USS).

  6. Inspector: Use it to change the attributes and style properties of the selected element or USS selector.

From UI Builder's Overview documentation

It's a good idea to watch Unity's UI Builder video to get an overview of the different parts of UI Builder. This gitbook will touch on most aspects of UI Builder, but to get a more exhaustive understanding we recommend watching Unity's run-through of the entire system.

Watch Unity's talk on UI Builder here.

  • While UI Builder is open, navigate to the "Library" section and take a look at what exists on the "Standard" tab

    • Standard Visual Elements are broken out into two sections:

      • Containers

      • Controls

    • We will use these Standard Visual Element to build our UI in UI Builder

  • Now click on the Project tab

    • This tab is broken out into

      • UI Documents (UXML)

      • Custom Controls (C#) (Our custom Visual Elements, cVEs)

  • Expand the UI folder in the Assets folder and notice the uxmls we created

  • Scroll to TitleScreenManager, hover over, and click on the icon that appears on the right

  • We have now loaded our TitleScreenManager uxml into our Hierarchy

    • Be careful not to double-click any items in project as that actually loads into the Hierarchy (unless you intend to load the item you double clicked into your current Hierarchy)

  • Also notice that you cannot "open" our cVEs like you can our uxmls

    • There is no icon that appears when you hover over a cVE

    • In order to update the contents of our cVEs, we must go into the actual file

  • Drag TitleScreenManager.cs from Custom Controls into the Hierarchy

  • Click on TitleScreenManager.uxml in the Hierarchy and hit save

    • Generally, always click on the uxml and save after making any change

      • UI Builder is still a bit wonky, so it's is better to be safe than sorry and a necessary step to saving is selecting the uxml in the Hierarchy before hitting save

UI BUILDER CURRENTLY HAS A "DOUBLE SAVE BUG"

Currently if you "save" after a change in UI Builder, it will save fine the first time.

But if you hit save AGAIN, immediately without making changes (and sometimes when making a change regarding children/hierarchy), it will save, BUT UI BUILDER WILL VISUALLY LOSE DATA.

If you then hit save a third time, it will save the lost data state (aka erasing everything).

So...

Only "save" once per change And if you hit "save" and UI Builder loses data DO NOT HIT SAVE AGAIN (or it will save the lost data state) Instead change to another UXML in the Project hierarchy and then switch back (UI Builder will correctly show the data again)

We have reached out to Unity to fix this issue. You can see it described here: https://forum.unity.com/threads/saving-twice-in-a-row-in-ui-builder-causes-errors-and-lost-data.1296954/

Below in the GIF we demonstrate this bug (data being lost in UI Builder) and then show that by switching to another UXML and back you can return your data state.

  • We now have our custom Visual Element (cVE) nested in our uxml

    • Woo! Our ScreenManager is all set up!

    • FYI: Having a "ScreenManager all set up" is when there is a parent UXML + a child cVE, which will control all other children uxmls/Visual Elements

  • Click on our TitleScreenManager cVE in Hierarchy and notice how the large blue highlighted area shrinks down to a line when we click from the TitleScreenManager uxml to the cVE

    • You may need to expand the window to see this

    • This is because our cVE is currently styled to consume only as much "room" on the screen as its contents take up

      • And because our cVE is empty, the styled area shrinks

It is important to note that the size of the TitleScreenManager uxml in UI Builder does not have a connection to the size the game will be rendered at.

The uxml will take as much space as exists (similar to view height and view width in Web Development).

By resizing this uxml canvas, we can test how the UI will react to different screen sizes/resolutions.

Within the Viewport pane, you can find the Canvas, a floating re-sizable edit-time container that contains a live version of the UI Document (UXML) being edited. If you can't see it, try clicking on the Fit Canvas button in the Viewport toolbar to bring it into view. Any settings related to the Canvas, like its size, are not saved as part of the UI Document (UXML) but will be remembered (using an internal separate settings file) for the next time you open the same UI Document (UXML).

From UI Builder's Setting up the edit-time Canvas documentation

  • Maximize the UI Builder to make it easier to navigate

  • Click on TitleScreenManager uxml and resize it to see that it the blue highlighted area always takes up the size of the canvas

    • Type in values in Canvas Size in the Inspector to see the canvas resize

      Drag the sides of the canvas and notice how the "Canvas Size" values in the Inspector change

  • Hit "Fit Canvas" in the top and it will automatically set to be contained within the Viewport

  • We aren't going to dive much deeper into the functionality of the UI Builder because it's so thoroughly explained in Unity's UI Builder talk

  • We will move on and focus on how to make certain functionalities work

  • Click on our TitleScreenManager cVE and in the Inspector, type TitleScreenManager in the "Name" field

  • In "StyleSheets" (top left) hit the plus sign and select "Create new USS"

  • Create a new USS named "TitleScreenUI" in the "UI" folder (you might need to expand the Finder window in order to navigate to the UI folder)

  • If you don't save often, sometimes UI Builder can get confused

  • Now select the TitleScreenManager cVE and in the Inspector navigate to "Flex"

    • Update "Grow" to 1

  • Notice how the highlighted area now encompasses the entire canvas

    • We have "styled" our TitleScreenManager cVE to "grow" to the entire canvas

  • Now under "StyleSheet" in the Inspector type ".screen" into the Style Class List input field and then click "Extract Inlined Styles to New Class"

    • Nice! We just made a new "class" of style called ".screen"

    • We can now apply this style to other Visual Elements and it will update the Visual Element's styling

    • We will use this class for all our "screens"

  • Now expand TitleScreenUI.uss in StyleSheets

  • Here is where we see that the UI Builder has automatically put our extracted class in our USS

    • Thanks, UI Builder!

  • Now let's drag our 4 other uxmls into our TitleScreenManager cVE

    • They should be nested within the cVE

We now have our initial setup of our TitleScreenManager

  • We nested our TitleScreenManager cVE within the uxml

  • We created TitleScreenUI USS

  • We created a .screen class

  • We nested TitleScreen, HostGameScreen, JoinGameScreen, ManualConnectScreen uxmls in our TitleScreenManager cVE

Adding Visual Elements and switching views

We are going to add Visual Elements to our nested uxmls (TitleScreen, HostGameScreen, JoinGameScreen, ManualConnectScreen). While TitleScreenManager.uxml is the focus in our Hierarchy, it is not possible to add Visual Elements to the nested uxmls. We will quickly show you what we mean.

  • In UI Builder, navigate to the Library, select the Standard tab, and drag a VisualElement into our nested TitleScreen uxml

  • Save TitleScreenManager.uxml

  • Now go to Library, then to the Project tab, and open the TitleScreen uxml

  • No VisualElement!

  • That is because we did not insert the VisualElement into the actual TitleScreen uxml file

    • It is part of the TitleScreenManager uxml (because it was the focus in our Hierarchy)

  • Delete the VisualElement and save

  • TitleScreenManager.uxml is the file that's actually keeping track of this new VisualElement

    • This approach of not actually nesting the VisualElement in the child uxml can work, but in this gitbook we want to have self-contained files for separation of concerns

  • So let's take a brief look at the different ways we can edit these nested uxmls

  • Right-click on TitleScreen in TitleScreenManager's Hierarchy and notice the options on the bottom:

    • Open in UI Builder

    • Open Instance in Isolation

    • Open Instance in Context

    • Show In Project

  • We will be editing our uxmls through the "Open in UI Builder" option

    • This allows you to focus on the exact screen you are working on

    • This will open the uxml in the same way that navigating to Library > Project > UI Documents > uxml selection would

    • You can use the other options to edit while in the context of the greater Hierarchy but we have found it easier to focus on one view at a time when doing anything other than just minor touchups

  • Click on "Open in UI Builder"

  • The uxml will take up as much space as its parent

    • The parent of TitleScreenManager uxml is the actual display so it will take up as much space as the display

    • The parent of TitleScreen is our cVE (TitleScreenManager) which we have made take up the entire display as well (through the .screen class)

  • We are going to add a VisualElement that takes up the entire screen to this uxml so that we can make full use of the display when adding our content

    • This might seem a little confusing at first but we want to take up the whole area with a parent element

      • We just made a full screen container, why do we need another one?!

    • Think of it as turning on the lights to a room

    • We want the light everywhere so we can place our objects relative to each other knowing the boundaries of the room

    • If we were to place our items in the room in the dark, we will not be able to control where they are relative to boundaries

    • (Eh, that wasn't our best analogy. If you have a better one please reach out in the Moetsi Discord!)

  • Drag a standard VisualElement to the Hierarchy

  • Change the VisualElement's name to "screen"

    • Instead of changing the name when the item is selected in Inspector, you can also do this from the Hierarchy by renaming the element "#screen"

      • The "#" is a convention from CSS (web development)

  • In StyleSheets (top left of UI Builder), click the "+" and "Add Existing USS" and select "TitleScreenUI.uss"

  • When the #screen VisualElement is selected in Hierarchy, go to Inspector and under StyleSheet type in ".screen" in the Style Class List field. Click "Add Style Class to List"

    • This will add ".screen" from TitleScreenUI.uss to "#screen"

  • We can see that screen now has the same styling as our .screen class and grows to take up space

  • We want to be able to differentiate screens so let's change the background

  • Select "#screen" and in the Inspector scroll down to "Background" and change the color to a blueish color (or whatever color you like)

  • Next, let's add 3 buttons as children of screen

  • Find the Button in the Standard tab under Controls, and drag three of them under #screen in Hierarchy as children

    • Name = host-local-button

      • Text: Host game

    • Name = join-local-button

      • Text: Join game

    • Name = manual-connect-button

      • Text: Manually connect

  • Now we will change the backgrounds and add a button to each of the other views as well

    • We will navigate to one of the 3 other views from TitleScreen using TitleScreen's 3 buttons

    • The other views will have a button back to TitleScreen

  • For the other 3 screens we will not add a standard VisualElement as we did for TitleScreen, we will add our cVEs

    • We did not need to add a cVE for TitleScreen to add callbacks to the buttons because we will use the cVE TitleScreenManager

      • If this is still a bit confusing, please bare with us and once we start adding callbacks it should make more sense

      • Hit save!

  • Open up HostGameScreen by navigating to the Project tab in Library and clicking the open icon when hovering over it:

    • Hit + under StyleSheets and click "Add Existing USS," select TitleScreenUI USS

    • Drag HostGameScreen from "Custom Controls" in Library into the Hierarchy

    • Add the .screen class to the HostGameScreen cVE (found in the StyleSheet section, type ".screen" and click Add Style Class to List)

    • Name the cVE "HostGameScreen" (by typing in HostGameScreen into the "Name" field in Inspector)

  • Next

    • Make the background greenish

    • Add a Label (drag one from Controls under Standard tab in Library)

      • Don't name the Label

      • Text = "Host Screen"

    • Add a button

      • Name = "back-button"

      • text = "Back"

  • We have our basic (albeit not very pretty) HostGameScreen

    • You might be wondering why didn't we name the label?

      • Or not, but we are going to tell you anyway!

    • In order to reference VisualElements in our custom Visual Elements (cVEs) they must have names to identify them (like an id)

    • If we don't plan on interacting with an element, then there is no real functional need to name them

      • Other than to make the Hierarchy a bit more clear to know what is going on

        • Which we will eventually do in the next section, "Styling a View," but it's too much to do right now

  • Next up open JoinGameScreen

    • Add TitleScreenUI USS

    • Drag JoinGameScreen from "Custom Controls" in Library into the Hierarchy

    • Add the .screen class to the JoinGameScreen cVE

    • Name the cVE "JoinGameScreen"

  • Next

    • Make the background orange-ish

    • Add a Label

      • Text = "Join Screen"

    • Add a button

      • Name = "back-button"

      • text = "Back"

  • One more screen to go, ManualConnectScreen

  • Open ManualConnectScreen

    • Add TitleScreenUI USS

    • Drag ManualConnectScreen from "Custom Controls" in Library into the Hierarchy

    • Add the .screen class to the ManualConnectScreen cVE

    • Name the cVE "ManualConnectScreen"

  • Next

    • Make the background purplish

    • Add a Label

      • Text = "Manual Connect Screen"

    • Add a button

      • Name = "back-button"

      • text = "Back"

  • Now let's navigate to TitleScreenManager

  • What is going on why is it when we click on each of our nested uxmls the highlighted area is so small?

    • We have not added the .screen class to all the uxmls themselves

      • Just the Visual Elements within them

      • Let's fix that

  • Add the .screen class to each of the nested uxmls

  • Okay so they are not tiny any more, they grow to take the space but now there are 4 equally spaced screens

    • We want to only display one screen at a time

  • Click on HostGameScreen in the Hierarchy and in the Inspector navigate to the Display section and change the Display field from "flex" to "none" (you need to hover over the little icons to know which one is "flex" and which one is "none")

  • Repeat for JoinGameScreen and ManualConnectScreen

  • We have our single TitleScreen visible with 3 buttons that will take us to the other views

  • One more thing to checkout in UI Builder:

    • At the bottom you can pull up "UXML Preview" and "USS Preview" and see that what we created in TitleScreenManager.uxml is being actually saved as code

  • This illustrates that UI Builder is almost like an "interpreter" of the uxml and USS we are creating and all the changes we make are saved and reflected in these files

  • In the next step we will add callbacks to the buttons we created to change the display values of different screens

Now now have our NavigationScene UI set up

  • We added TitleScreenUI USS to each uxml

  • We added a .screen class, background, name, buttons and labels to each view

  • We added the .screen class to each uxml

  • We updated the Display property in the inspector to "none" of

    • HostGameScreen

    • JoinGameScreen

    • ManualConnectScreen

Adding callbacks to our UI

Now that we have our screens and buttons set up, we want to add logic to be able to toggle between different views.

The TitleScreenManager cVE will handle adding callbacks to the TitleScreen uxml buttons and the back buttons that return to the uxml. This way we have a single cVE that we know is in charge of displaying views (separation of concerns).

  • Close out of UI Builder and open up TitleScreenManager.cs (the cVE)

  • We are going to need to declare 4 variables for our 4 views at the top

    • TitleScreen

    • HostGameScreen

    • JoinGameScreen

    • ManualConnectScreen

  • Then we are going to have to "query" our nested children by name, and assign them to those variables

  • Once we have our views, we will then query within those views to find our buttons and assign callbacks

  • Finally, we will create functions to hide and display views when different buttons are pressed

  • Paste the code snippet below into TitleScreenManager.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class TitleScreenManager : VisualElement
{
    VisualElement m_TitleScreen;
    VisualElement m_HostScreen;
    VisualElement m_JoinScreen;
    VisualElement m_ManualConnectScreen;
    
    public new class UxmlFactory : UxmlFactory<TitleScreenManager, UxmlTraits> { }

    public TitleScreenManager()
    {
        this.RegisterCallback<GeometryChangedEvent>(OnGeometryChange);
    }

    void OnGeometryChange(GeometryChangedEvent evt)
    {
        m_TitleScreen = this.Q("TitleScreen");
        m_HostScreen = this.Q("HostGameScreen");
        m_JoinScreen = this.Q("JoinGameScreen");
        m_ManualConnectScreen = this.Q("ManualConnectScreen");

        m_TitleScreen?.Q("host-local-button")?.RegisterCallback<ClickEvent>(ev => EnableHostScreen());
        m_TitleScreen?.Q("join-local-button")?.RegisterCallback<ClickEvent>(ev => EnableJoinScreen());
        m_TitleScreen?.Q("manual-connect-button")?.RegisterCallback<ClickEvent>(ev => EnableManualScreen());

        m_HostScreen?.Q("back-button")?.RegisterCallback<ClickEvent>(ev => EnableTitleScreen());
        m_JoinScreen?.Q("back-button")?.RegisterCallback<ClickEvent>(ev => EnableTitleScreen());
        m_ManualConnectScreen?.Q("back-button")?.RegisterCallback<ClickEvent>(ev => EnableTitleScreen());

        this.UnregisterCallback<GeometryChangedEvent>(OnGeometryChange);
    }

    public void EnableHostScreen()
    {
        m_TitleScreen.style.display = DisplayStyle.None;
        m_HostScreen.style.display = DisplayStyle.Flex;
        m_JoinScreen.style.display = DisplayStyle.None;
        m_ManualConnectScreen.style.display = DisplayStyle.None;

    }

    public void EnableJoinScreen()
    {
        m_TitleScreen.style.display = DisplayStyle.None;
        m_HostScreen.style.display = DisplayStyle.None;
        m_JoinScreen.style.display = DisplayStyle.Flex;
        m_ManualConnectScreen.style.display = DisplayStyle.None;
    }

    public void EnableManualScreen()
    {
        m_TitleScreen.style.display = DisplayStyle.None;
        m_HostScreen.style.display = DisplayStyle.None;
        m_JoinScreen.style.display = DisplayStyle.None;
        m_ManualConnectScreen.style.display = DisplayStyle.Flex;
    }

    public void EnableTitleScreen()
    {
        m_TitleScreen.style.display = DisplayStyle.Flex;
        m_HostScreen.style.display = DisplayStyle.None;
        m_JoinScreen.style.display = DisplayStyle.None;
        m_ManualConnectScreen.style.display = DisplayStyle.None;
    }

}
  • Now hit play and check out the navigation

    • There may be console warnings Internal: deleting an allocation that is older than its permitted lifetime of 4 frames (age = 5)if the frame rate is very fast

We now have our TitleScreenManager functioning

  • We updated our TitleScreenManager custom Visual Element to provide callbacks and functions when but it is harmestsclicked

Github branch link:

git clone https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/ git checkout 'Creating-a-ScreenManager'

Last updated