Skip to main content

Creating Dynamic UI

๐ŸŽฎ GDC: Park Clean-Up Game

Written by: Akram Taghavi-Burris | ยฉ Copyright 2025

Status: In Development

Although the UI Toolkit is designed primarily for C# developers, it can also be integrated with Visual Scripting, allowing for dynamic UI updates without extensive coding. In this tutorial section we will walk through the workflow of using the UI Toolkit with Visual Scripts as well as identify some limitations.

UI Elements with Visual Scriptingโ€‹

By default, Unity's Visual Scripting package does not load all possible Nodes. This reduces the size of the package especially since there are a great many Nodes that you may not use in your project. HOwever, in our case we will need to load the Nodes specifically for use with the UI Toolkit.

๐Ÿ›  Importing UI Nodesโ€‹

Tutorial: UI Nodes

๐Ÿ“UI ToolKit | ๐Ÿ•’15 minutes | ๐Ÿ“‚Required File: Park Clean-Up Game

In this tutorial we will import the required Nodes for using UI elements with Visual Scripting.

Step 1: Update the Type Optionsโ€‹

  1. In the Unity Editor select Edit > Project Settings > Visual Scripting
  2. From the Visual Scripting dialog, click on the Type Options to expand.
  3. At the bottom of the Type Options list, click the + button and add each of the following UI Toolkit elements:
    • U Query Extension
    • Visual Element
    • Label
    • Button
  4. Then press the Regenerate Notes button to import and save the type list.

Modular Designโ€‹

Now that we have imported the necessary UI Toolkit types, we can begin scripting the HUD (Heads-Up Display) using Visual Scripting. However, before we build the HUD, we need to think about Modular Design and how it should be implemented in our scripts.

Modularity is the practice of designing systems that can be reused in different contexts. For instance, in this tutorial, we will track the player's collected amount. While the current goal is to track trash bags in the Park Clean-Up Game, this same HUD system could later track cans for recycling, keys for unlocking doors, or any other collectible. By focusing on generalized logic, such as a "collected amount", we can design a flexible system that avoids unnecessary duplication of code.

This modular approach mirrors the abstraction seen in algebra, where unknown values are represented by symbols like X. As long as we pass the correct data, the same system can adapt to different gameplay objectives.

๐Ÿ›  Scripting the HUDโ€‹

Tutorial: Scripting the HUD

๐Ÿ“UI ToolKit | ๐Ÿ•’15 minutes | ๐Ÿ“‚Required File: Park Clean-Up Game

In this tutorial we create events for updating the HUD.

Step 1: Adding a Script Machineโ€‹

  1. Open the ParkScene in the Unity Editor
  2. Select the HUD GameObject in the Hierarchy window
  3. Add a Script Machine in the Inspector Window
    • Click Add Component and search for Script Machine.
    • In the Script Machine component, click New to create a new Script Graph.
    • Name the graph HUD and save it in your Scripts folder.

Step 2: Collection Variablesโ€‹

  1. Open the Script Graph

    • In the Inspector, locate the Script Machine component.
    • Click Edit Graph to open the visual scripting editor.
  2. Set Graph Variables

    • In the Blackboard of the Script Graph add the following variables:
      • collectedAmount - integer
      • requiredAmount - integer

HUD Variables

Event Argumentsโ€‹

As mentioned earlier our UI does not need to know if we are collecting trash or cans or whatever, but it does need to know thous values. Unity Visual Scripting allows us to pass this data as arguments through the custom events we define. Understanding how to work with arguments in events is crucial for creating flexible systems that can respond to different gameplay scenarios.

What is an Argument? An argument in programming refers to the value or data that is passed into a function, method, or event when it is called. In Unity Visual Scripting, arguments are simply the pieces of information (like numbers or strings) that get sent from one part of your game to another. These arguments allow different parts of your game (like the Trash Bin and HUD) to communicate without needing to know about each otherโ€™s specific implementation details.

In our case, we are passing two valuesโ€”the collected amount (the amount the player has collected) and the required amount (the amount the player needs to collect). The important thing to note here is that these values are passed by the Trash Bin to the HUD without the HUD needing to know that the values are related to trash. The system is flexible, so the same concept can be used for any other collectible items, such as keys or cans.

Step 3: Passing Argumentsโ€‹

  1. Update Collection Event

    • Create a new Custom Event and name it UpdateCollectedAmount.
      • Set the target to Global Event
      • Set the Arguments to 2
  2. Set Graph Variables

    • Add a Set Graph Variable node to assign the collectedAmount
    • Set the Arg 0 as the value for the collectedAmount
    • Flow the UpdateCollectedAmount into the Set Graph Vairable node
    • Add another a Set Graph Variable node to assign the requiredAmount
    • Set the Arg 1 as the value for the requiredAmount
    • Flow the collectedAmount Set Graph Variable node into the requiredAmount Set Graph Variable node

The image below displays how the event should appear in the script graph.

Event Arguments

Accessing UI Elementsโ€‹

In order to access UI elements in, whether in code or through visual script we need to first reference the UI Document in which the elements are on.

Once we have the UI Document we will need to get the root VisualElement of the UI Document. The root VisualElement serves as the parent for all other UI elements, like buttons, text fields, and images. Once we have access to the root element, we can then search for specific child elements by their name or type.

The process for accessing UI Elements follows this flow:

  1. Access the UI Document:

    • The UI Document is the asset that holds your entire UI structure. To interact with the UI, we must reference this document from the Visual Script.
  2. Get the Root VisualElement:

    • The root VisualElement is the container that holds all other UI elements. By using the Get Root VisualElement node, you can retrieve this parent element and start accessing the child elements inside it.
  3. Query the Element Name:

    • Once you have the root VisualElement, you can use the Query node or the Get VisualElement by Name node to access specific elements by their name. The name of an element is assigned when you set up the UI in the UI Builder.

In Visual Scripting, these nodes allow you to programmatically access UI elements and update them dynamically, such as changing text or adjusting visibility. By referencing UI elements through their names, you maintain flexibility and scalability in your UI logic.

This approach keeps your UI system organized and modular, as you can update or reference elements without needing to directly manipulate their position in the UI hierarchy, making it easy to adapt to changes or expand the interface.

Step 4: Accessing UI Elementsโ€‹

  1. Get the Root Visual Element:

    • Add the UI Document Get Root Visual Element node.
      • Set the Target to This (since the UI Document is on the HUD game object).
  2. Find the Visual Element:

    • Add the Visual Element Q (Name, Class Name) node.
      • Set the Name of the element to the name of the Text Label in the UI document you want to update. For example, if your text label is named Counter Text, set the name to Counter Text.
      • Connect the flow from the UI Document Get Root Visual Element node to the Target of the Visual Element Q node.
  3. Set Collected and Required Amounts:

    • Flow the last Set Graph Variable node (for the requiredAmount) into the Visual Element Q node.
  4. Concatenate the String for Display:

    • Add a String Concat node to combine the collected and required amounts into a message.
      • Pass in Get Variable nodes for both the collectedAmount and requiredAmount as inputs to the String Concat node.
      • Add a String Literal node to format the text (e.g., "Collected: " and " / Required: ").
  5. Set the Text of the Label:

    • Add a Label Set Text node to display the concatenated message.
      • Set the Target of the Label Set Text node to the result of the Visual Element Q node (the text label element).
      • Connect the output of the String Concat node into the Text input of the Label Set Text node.

The final UpdateCollectedAmount event should look like the image below.

Update Collected Amount

Triggering a UI Updateโ€‹

While we've created a custom event to update the UI, we have yet to set any triggers to call this event.

For the first mission, the Trash Bins hold the values for both the collected and required amounts of trash โ€” these are the values weโ€™ll display in the UI.

To ensure the UI shows the correct values:

  • The Trash Bin should call this method in the Start method to initialize the default values.
  • Additionally, the method should be called whenever the collected amount changes, which occurs during the AddToCounter event.

๐Ÿ›  Updating the UIโ€‹

Tutorial: UI Updatess

๐Ÿ“UI ToolKit | ๐Ÿ•’15 minutes | ๐Ÿ“‚Required File: Park Clean-Up Game

In this tutorial we will set up triggers to call the event to update the UI.

Step 1: Start Triggerโ€‹

  1. Open the Script Graph
    • Select the TrashBin Visual Script from the Assets > Scripts folder in the Inspector
    • Double-click on script to open it in the Visual Script Editor.
  2. Add an On Start Event
    • In the graph editor add an On Start event node
    • Flow the On Start node into a Custom Event Trigger
  3. Set the Custom Event Trigger
  • Set the name of the Event to UpdateUI
  • Set the target to This

Trash Bin On Start


Step 2: Add to Counter Triggerโ€‹

  1. Update the AddToCounter Event
    • Locate the AddToCounter event
    • At the end of the event after the Debug Log add a Custom Event Trigger
  2. Set the Custom Event Trigger
    • Set the name of the Event to UpdateUI
    • Set the target to This

Trash Bin AddToCounter


Applying Dry Principleโ€‹

In programming, the DRY principle, short for Don't Repeat Yourself, emphasizes the importance of avoiding duplication of logic in multiple places.

While we could simply call the UpdateCollectedAmount method in the Start method and pass the required arguments, we would need to repeat this call in the AddToCounter event as well.

Though passing the two arguments may not seem like much, there's a risk of accidentally passing the wrong value in one of the method calls, which could result in hours of troubleshooting as you search for where the incorrect value is coming from.

Additionally, if we ever need to change the name of the variables being passed, we'd have to update that change across every location where those variables are referenced.

By creating a single UpdateUI method that calls UpdateCollectedAmount and passes the arguments, we centralize this logic in one place. This reduces the potential for errors, as both the arguments and the method call are handled in a single location, simplifying future updates and minimizing the risk of mistakes.


Step 3: Create the UpdateUI Eventโ€‹

  1. Add Custom Event

    • Set the name of the Event to UpdateUI
    • Set the target to This
  2. Add Custom Trigger Event

    • From the Custom Event node flow into a Custom Event Trigger
    • Set the name of the Event to UpdateCollectedAmount
    • Set the Target to GlobalEvent
    • Set the amount of argments to 2
  3. Pass Arguments

    • Add a Get Graph Variable node for trashCollected
    • Add a Get Graph Variable node for requiredTrashAmount
    • Set the trashCollected node for the value of Arg 0
    • Set the requiredTrashAmount node for the value of Arg 1

Trash Bin UpdateUI

  1. Testing
    • Save the Script Graph and exit the Script editor
    • Return to the ParkScene
    • Press Play to test the scene
      • Interact and collect trash
      • As the trash is collected the values on the UI should update.