Create a ListView
Code and workflows for creating a dynamic UI ListView in UI Builder

What you'll develop on this page

Dynamic ListView that displays the name of GameObjects in a scene
Use UI Builder to create a dynamic UI ListView that displays GameObjects in a scene and responds to which item is clicked.

What our ListView will do

We are going to build a ListView that displays the number of GameObjects with a "LocalGame" tag and the names of the corresponding GameObjects. When we click on the item in the list view we will be taken to the JoinGameScreen.
Just to be upfront, the approach we take to build this ListView is overkill for the number of GameObjects we will be counting. ListViews are good when all you want to do is bind data to visible items currently in the view and there are hundreds of items. As we scroll through the list, data will bind and appear and then unbind as we scroll past it. Again, this is overkill for just a few items, but we at Moetsi personally could not find any other resources published online explaining how to do this from scratch, so that just leaves us to do it!!!
We will create a new uxml file named "ListItem" that we will add and remove in the ListView. Next, we will make a new script named "LocalGamesFinder" that will power this new ListView. The reason it is called LocalGamesFinder is because we will be using it to find LAN games in the next Multiplayer section.
There are 3 spells you must cast to conjure ListView:
    itemsSource (declares the data source of the items)
    makeItem (creates the list item visual element from our uxml)
    bindItem (attach the data from our itemsSource to our item)
When you update the data in itemsSource you can call Refresh() to update the list.
We do this differently in the next Multiplayer section, where we'll be listening for game broadcasts, and as new ones are found, we update our list.
In this page we will look for GameObjects in our scene and refresh our itemsSource 1x per second. This is not an optimal approach because it refreshes even when no items have changed, but it is fine to simply demonstrate the purposes of ListView.

Building our ListView

Creating ListItem uxml

Since we've already explained how to create and style uxmls using UI Builder on the previous page ("Styling a View"), we're going to just jump straight to the code here.
    Create a new uxml file named ListItem (right-click in the UI folder, select Create, then UI Toolkit, and select UI Document)
    and paste in the code snippet below:
1
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
2
<Style src="TitleScreenUI.uss" />
3
<ui:VisualElement name="row" focusable="false" class="row">
4
<ui:VisualElement name="game-name-data" class="game-name-data">
5
<ui:Label text="Mary&apos;s Game" name="game-name" class="list-item-game-name" />
6
<ui:Label text="Game Name" name="game-name-label" class="list-item-game-name-label" />
7
</ui:VisualElement>
8
<ui:Button text="Join" display-tooltip-when-elided="True" name="join-local-game" class="blue-button list-item-button" />
9
</ui:VisualElement>
10
</ui:UXML>
Copied!
    Add the following classes at the bottom of TitleScreenUI USS (don't paste this snippet in over all your code!! Just add it to the bottom!)
1
.row {
2
flex-direction: row;
3
justify-content: space-between;
4
align-items: center;
5
height: 74px;
6
}
7
8
.game-name-data {
9
margin-left: 17px;
10
}
11
12
.list-item-game-name {
13
margin-left: 0;
14
font-size: 24px;
15
margin-right: 0;
16
margin-top: 0;
17
margin-bottom: 0;
18
color: rgb(150, 191, 208);
19
padding-left: 0;
20
padding-right: 0;
21
padding-top: 0;
22
padding-bottom: 0;
23
}
24
25
.list-item-game-name-label {
26
padding-left: 0;
27
padding-right: 0;
28
padding-top: 0;
29
padding-bottom: 0;
30
font-size: 10px;
31
}
32
33
.list-item-button {
34
width: 141px;
35
height: 46px;
36
margin-left: 0;
37
margin-right: 12px;
38
margin-top: 0;
39
margin-bottom: 0;
40
border-left-color: rgba(0, 0, 0, 0);
41
border-right-color: rgba(0, 0, 0, 0);
42
border-top-color: rgba(0, 0, 0, 0);
43
border-bottom-color: rgba(0, 0, 0, 0);
44
}
Copied!
Creating the ListItem uxml file and then adding more classes to TitleScreenUI
    We will be taking the names of found GameObjects with "LocalGame" tags and inserting them in the label #game-name from the uxml
    When we click on #join-local-game we will be taken to the local games view

Creating LocalGamesFinder

    In NavigationScene, create an empty GameObject in the Hierarchy named "LocalGamesDiscovery"
    Add component to LocalGamesDiscovery that is a new script named "LocalGamesFinder"
      First make the file by right-clicking in the Scripts and Prefabs folder > Create > C# Script and name the file "LocalGamesFinder"
      Then Navigate to LocalGamesDiscovery in Hierarchy and click Add Component in Inspector to add LocalGamesFinder
Creating LocalGamesDiscovery and LocalGamesFinder
    Paste the code snippet below into LocalGamesFinder.cs:
1
using System;
2
using System.Collections;
3
using System.Collections.Generic;
4
using UnityEngine;
5
using UnityEngine.UIElements;
6
using UnityEditor;
7
8
public class LocalGamesFinder : MonoBehaviour
9
{
10
//We will be pulling in our SourceAsset from TitleScreenUI GameObject so we can reference Visual Elements
11
public UIDocument m_TitleUIDocument;
12
13
//When we grab the rootVisualElement of our UIDocument we will be able to query the TitleScreenManager Visual Element
14
private VisualElement m_titleScreenManagerVE;
15
16
//We will query for our TitleScreenManager cVE by its name "TitleScreenManager"
17
private TitleScreenManager m_titleScreenManagerClass;
18
19
//Within TitleScreenManager (which is everything) we will query for our list-view by name
20
//We don't have to query for the TitleScreen THEN list-view because it is one big tree of elements
21
//We can call any child from the parent, very convenient! But you must be mindful about being dilligent about
22
//creating unique names or else you can get back several elements (which at times is the point of sharing a name)
23
private ListView m_ListView;
24
25
//Although this variable name doesn't make sense for this use case it will in the Multiplayer section
26
//Here we will store our discovered GameObjects
27
//This will be used as our itemSource
28
private GameObject[] discoveredServerInfoObjects;
29
30
//This is our ListItem uxml that we will drag to the public field
31
//We need a reference to the uxml so we can build it in makeItem
32
public VisualTreeAsset m_localGameListItemAsset;
33
34
//These variables are used in Update() to pace how often we check for GameObjects
35
public float perSecond = 1.0f;
36
private float nextTime = 0;
37
38
void OnEnable()
39
{
40
//Here we grab the SourceAsset rootVisualElement
41
//This is a MAJOR KEY, really couldn't find this key step in information online
42
//If you want to reference your active UI in a script make a public UIDocument variable and
43
//then call rootVisualElement on it, from there you can query the Visual Element tree by names
44
//or element types
45
m_titleScreenManagerVE = m_TitleUIDocument.rootVisualElement;
46
//Here we grab the TitleScreenManager by querying by name
47
m_titleScreenManagerClass = m_titleScreenManagerVE.Q<TitleScreenManager>("TitleScreenManager");
48
//From within TitleScreenManager we query local-games-list by name
49
m_ListView = m_titleScreenManagerVE.Q<ListView>("local-games-list");
50
51
}
52
53
// Start is called before the first frame update
54
void Start()
55
{
56
//We start by looking for any GameObjects with a LocalGame tag
57
discoveredServerInfoObjects = GameObject.FindGameObjectsWithTag("LocalGame");
58
59
60
// The three spells you must cast to conjure a list view
61
m_ListView.makeItem = MakeItem;
62
m_ListView.bindItem = BindItem;
63
m_ListView.itemsSource = discoveredServerInfoObjects;
64
65
}
66
67
private VisualElement MakeItem()
68
{
69
//Here we take the uxml and make a VisualElement
70
VisualElement listItem = m_localGameListItemAsset.CloneTree();
71
return listItem;
72
73
}
74
75
private void BindItem(VisualElement e, int index)
76
{
77
//We add the game name to the label of the list item
78
e.Q<Label>("game-name").text = discoveredServerInfoObjects[index].name;
79
80
//Here we create a call back for clicking on the list item and provide data to a function
81
e.Q<Button>("join-local-game").RegisterCallback<ClickEvent>(ev => ClickedJoinGame(discoveredServerInfoObjects[index]));
82
83
}
84
85
void ClickedJoinGame(GameObject localGame)
86
{
87
//We query our JoinGameScreen cVE and call a new function LoadJoinScreenForSelectedServer and pass our GameObject
88
//This is an example of clicking a list item and passing through data to a new function with that click
89
//You will see in our JoinGameScreen cVE that we use this data to fill labels in the view
90
m_titleScreenManagerClass.Q<JoinGameScreen>("JoinGameScreen").LoadJoinScreenForSelectedServer(localGame);
91
92
//We then call EnableJoinScreen on our TitleScreenManager cVE (which displays JoinGameScreen)
93
m_titleScreenManagerClass.EnableJoinScreen();
94
95
}
96
97
// Update is called once per frame
98
void Update()
99
{
100
if (Time.time >= nextTime)
101
{
102
//We check for GameObjects with a localGame tag
103
discoveredServerInfoObjects = GameObject.FindGameObjectsWithTag("LocalGame");
104
105
//We again set our itemsSource to our array (if the array changes it must be reset)
106
m_ListView.itemsSource = discoveredServerInfoObjects;
107
//We then must refresh the listView on this new data source
108
//(don't worry it doesn't make the list jump, ListView is cool like that)
109
m_ListView.Refresh();
110
111
//We increment
112
nextTime += (1/perSecond);
113
}
114
115
}
116
117
}
Copied!
Updating LocalGamesFinder
    Let's also update our JoinGameScreen custom Visual Element (cVE) with the function that our ListItem button (join-local-game) calls, "LoadJoinScreenForSelectedServer"
    To update, paste the code snippet below into JoinGameScreen.cs (cVE):
1
using System;
2
using System.Text;
3
using System.Net;
4
using System.Net.Sockets;
5
using System.Net.NetworkInformation;
6
using System.Collections;
7
using System.Threading.Tasks;
8
using System.Threading;
9
using System.Collections.Generic;
10
using UnityEngine;
11
using UnityEngine.UIElements;
12
using Unity.Entities;
13
using Unity.NetCode;
14
using UnityEngine.SceneManagement;
15
16
public class JoinGameScreen : VisualElement
17
{
18
Label m_GameName;
19
Label m_GameIp;
20
TextField m_PlayerName;
21
String m_HostName = "";
22
IPAddress m_MyIp;
23
24
public new class UxmlFactory : UxmlFactory<JoinGameScreen, UxmlTraits> { }
25
26
public JoinGameScreen()
27
{
28
this.RegisterCallback<GeometryChangedEvent>(OnGeometryChange);
29
}
30
31
void OnGeometryChange(GeometryChangedEvent evt)
32
{
33
//
34
// PROVIDE ACCESS TO THE FORM ELEMENTS THROUGH VARIABLES
35
//
36
m_GameName = this.Q<Label>("game-name");
37
m_GameIp = this.Q<Label>("game-ip");
38
m_PlayerName = this.Q<TextField>("player-name");
39
40
// CLICKING CALLBACKS
41
this.Q("launch-join-game")?.RegisterCallback<ClickEvent>(ev => ClickedJoinGame());
42
43
44
45
this.UnregisterCallback<GeometryChangedEvent>(OnGeometryChange);
46
}
47
48
void ClickedJoinGame()
49
{
50
Debug.Log("clicked join game");
51
}
52
53
public void LoadJoinScreenForSelectedServer(GameObject localGame)
54
{
55
m_GameName = this.Q<Label>("game-name");
56
m_GameIp = this.Q<Label>("game-ip");
57
m_GameName.text = localGame.name;
58
m_GameIp.text = localGame.name;
59
}
60
61
}
Copied!

🔑MAJOR KEY ALERT🔑

The next few steps we take with LocalGamesFinder will show you a good approach for making scripts that interact with active UI. The general steps to follow are:
    Make a public UI Document variable in your script
    Drag your UI SourceAsset into that field in the Inspector from the scene's Hierarchy
    Call .rootVisualElement on that variable
    You can now query your entire Visual Element tree
    Select LocalGamesDiscovery so LocalGamesFinder is visible in the Inspector
      Drag the TitleScreenUI GameObject from the Hierarchy onto the "Title UI Document" field under Local Games Finder in Inspector
      Drag ListItem from the Assets/UI folder in the Project folder to the "Local Game List Item Asset" field
Setting the public variables on LocalGamesFinder
    In LoadJoinScreenForSelectedServer
      We query for "game-name" and "game-ip" labels
      We then set the text value of those labels to the name of our GameObjects
    Delete the Debug.Log in "EnableJoinScreen()" in TitleScreenManager cVE
Updating JoinGameScreen cVE and
    Now let's create an empty GameObject called LocalGame
    Add a "LocalGame" tag to the LocalGame GameObject
      To do this, you need to click the dropdown next to "Tag" in the Inspector when LocalGame is selected and choose the last option "Add Tag..." and click the + under Tags and type "LocalGame" to make a new tag
    Copy and paste to make 10 copies
    Let's hit play
      Scroll up and down the list to see all our "LocalGames"
      Copy and paste and make more GameObjects and see the ListView include those items
      Click on Join button on one of the ListItems and check out the JoinGameScreen
      Return back and click on another ListItem to see new data load
Creating LocalGame tagged GameObjects and seeing our ListView work
    Place the LocalGamesFinder script in Assets/Multiplayer Setup
      No gif here, we believe in you 💪
We now have our working ListView that dynamically updates and can pass data when an item is clicked
    We created ListItem uxml and added its classes to TitleScreenUI USS
    We created LocalGamesDiscovery
    We created LocalGamesFinder
    We updated JoinGameScreen cVE
    We updated TitleScreenManager cVE (barely)
    We created a new tag and GameObjects to replicate finding new local games
Github branch link:
git clone https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/ git checkout 'Creating-a-List'
Last modified 2mo ago