Hello! Welcome to the next lesson in our journey to build a moddable life simulation game.
In our previous lesson, we significantly improved the robustness of our modding framework by implementing a conflict detection system. Your Rust backend can now identify when two mods try to register content with the same ID, preventing silent overwrites and providing clear error messages to mod authors. This was a crucial step in creating a stable and developer-friendly modding ecosystem.
With the core modding system in a good place, we'll now shift our focus back to the player's experience. A life simulation game is all about managing a character's state, but right now, the player has no visibility into their character's well-being. Today, we'll fix that. Your learning outcome is to create a main HUD that displays the character's core needs, mood, and money.
This lesson will be a great opportunity to dive into Unity's UI systems and bridge the gap between the data churning in your Rust engine and the visual feedback the player sees on screen.
1. Choosing Your UI Tool: UGUI vs. UI Toolkit
Unity offers two primary systems for building user interfaces:
- Unity UI (UGUI): The traditional, GameObject-based system. If you've worked with Unity before, you are likely familiar with creating Canvases, Panels, and Images in the hierarchy.
- UI Toolkit: The newer, modern system inspired by web technologies (HTML, CSS). It separates UI structure (UXML), styling (USS), and logic (C#), promoting a more scalable and maintainable workflow.
For this project, I recommend using UI Toolkit. Its separation of concerns aligns very well with our project's architecture, and it's the direction Unity is heading for future development. The skills you learn here are highly transferable to web development and other modern UI frameworks.
To get a high-level overview of what UI Toolkit is and why it's structured the way it is, please read the following introduction from the Unity documentation.
Introduction to UI Toolkit - Unity Manual
This document introduces the core concepts of UI Toolkit, explaining its web-inspired design with UXML for structure and USS for styling.
Read the sections 'Web-inspired design', 'Retained mode architecture', 'Data binding', and 'Layout engine'. This will give you the foundational vocabulary for the rest of the lesson.
2. Designing the HUD with UI Builder
The first step is to visually create the HUD. We will use Unity's UI Builder, a visual authoring tool for creating UXML (structure) and USS (style) files. We will create simple bars for the four needs (hunger, energy, social, fun) and mood, plus a text label for money.
The tutorial below is for a health bar, but the principles are identical for creating our need and mood bars. We will follow its process closely.
Make a Health Bar with UI Toolkit
This excellent Unity Learn tutorial walks through creating a health bar with UI Toolkit. We will adapt its steps to create our needs bars and money display.
Follow the steps in sections 4 ('Create the UI File and the Health Bar Background') and 5 ('Add a Label to display Health'). Instead of one health bar, your task is to create a layout for your HUD. A common layout is to place these elements in a corner of the screen. Create: A main container VisualElement to hold all HUD elements. Five bar-like structures for hunger, energy, social, fun, and mood. Give them unique names like hunger-bar, energy-bar, etc. You can style them with different colors. A Label for displaying money, naming it money-label. At the end of this step, you should have a static UI visible in your game view.

A key concept in UI design is ensuring it scales correctly on different screen resolutions. The principles of anchoring are crucial here. The following video uses the older UGUI system, but its explanation of anchors is fundamentally important for any UI work in Unity.
How To Build A Scalable HUD: Introduction To Unity UI And Anchors
This video provides a fantastic explanation of UI anchors and how they ensure your UI adapts to different screen sizes. The concepts are directly applicable to UI Toolkit.
Watch the segment on anchors (from 04:12 to 06:58). Pay attention to how setting the anchor presets (e.g., top-left, bottom-right) makes the UI elements stick to the corners of the screen, which is exactly what we want for our HUD.
Apply these anchoring principles in the UI Builder to position your HUD container in a corner of the screen.
3. Exposing Character Stats from Rust
Now that we have a static UI, we need to feed it live data from the Rust backend. This involves two parts: creating an endpoint in Rust to provide the data, and calling it from Unity.
First, let's define a simple struct in Rust to hold all the data our HUD needs. This struct will be serialized to JSON and sent to Unity.
In your Rust project, create a new file src/models.rs (if it doesn't exist) and add:
// src/models.rs
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct CharacterHudStats {
pub hunger: f32,
pub energy: f32,
pub social: f32,
pub fun: f32,
pub mood: f32,
pub money: i32,
}
(Don't forget to add pub mod models; to your lib.rs or main.rs)
Next, let's modify our command handler in src/lib.rs (or wherever you process commands) to handle a new command, get_hud_stats. This command will populate and return the CharacterHudStats struct.
// In your command processing function (e.g., in lib.rs)
// Make sure you have the necessary imports
use crate::models::CharacterHudStats;
// ... and your game state struct
// Inside your match statement for commands:
"get_hud_stats" => {
// Assuming you have a `game_state` variable holding the world and characters.
// This is a simplified example. You'll need to fetch the active character.
let active_character = &game_state.characters[0]; // Or however you track the active character
let stats = CharacterHudStats {
hunger: active_character.needs.hunger,
energy: active_character.needs.energy,
social: active_character.needs.social,
fun: active_character.needs.fun,
mood: active_character.mood,
money: active_character.wallet.balance,
};
let response = serde_json::to_string(&stats).unwrap();
return Ok(response); // Return the JSON string
}
This new command gives Unity a way to ask for all the data it needs for the HUD in a single request.
4. Connecting the HUD in Unity
With the Rust backend ready, let's write the C# script in Unity to fetch the data and update the UI.
- Create a new C# script named
HudController.cs. - Attach this script to a GameObject in your scene (e.g., the
GameUIobject that holds theUIDocumentcomponent).
This script will periodically send the get_hud_stats command to Rust and use the response to update the width of the need bars and the text of the money label.
We will once again refer to the "Make a Health Bar" tutorial, this time for the C# logic.
Make a Health Bar with UI Toolkit
The C# portion of this tutorial shows how to get a reference to a UI element and update its properties from a script. This is the final piece of our puzzle.
Read sections 5 ('Add a Label to display Health') and 6 ('Add a filling bar'). Focus on: How to get a reference to a VisualElement and Label using rootVisualElement.Q<T>(). How to change a Label's text with myLabel.text = .... How to change a VisualElement's width for the 'fill' effect: myBar.style.width = Length.Percent(...).
Now, apply these concepts in your HudController.cs. Here is a template to get you started.
HudController.cs
using UnityEngine;
using UnityEngine.UIElements;
// This struct must match the Rust CharacterHudStats struct
[System.Serializable]
public class CharacterHudStats
{
public float hunger;
public float energy;
public float social;
public float fun;
public float mood;
public int money;
}
public class HudController : MonoBehaviour
{
// Drag your UIDocument component here in the Inspector
public UIDocument uiDocument;
private VisualElement hungerBar;
private VisualElement energyBar;
private VisualElement socialBar;
private VisualElement funBar;
private VisualElement moodBar;
private Label moneyLabel;
// Timer to avoid spamming the backend every frame
private float updateTimer = 0.0f;
private const float UPDATE_INTERVAL = 0.5f; // Update twice per second
void Start()
{
var root = uiDocument.rootVisualElement;
// Query for the elements you created in UI Builder
// NOTE: You'll need to create "fill" elements inside your bars
// as children, just like the health bar tutorial.
hungerBar = root.Q<VisualElement>("hunger-bar-fill");
energyBar = root.Q<VisualElement>("energy-bar-fill");
socialBar = root.Q<VisualElement>("social-bar-fill");
funBar = root.Q<VisualElement>("fun-bar-fill");
moodBar = root.Q<VisualElement>("mood-bar-fill");
moneyLabel = root.Q<Label>("money-label");
}
void Update()
{
updateTimer += Time.deltaTime;
if (updateTimer >= UPDATE_INTERVAL)
{
updateTimer = 0.0f;
RequestHudStats();
}
}
void RequestHudStats()
{
// This assumes you have a Rust FFI wrapper like `RustApi.SendCommand`
// that takes a command and a callback for the response.
RustApi.SendCommand("get_hud_stats", (responseJson) => {
if (!string.IsNullOrEmpty(responseJson))
{
CharacterHudStats stats = JsonUtility.FromJson<CharacterHudStats>(responseJson);
UpdateUI(stats);
}
});
}
void UpdateUI(CharacterHudStats stats)
{
// Assuming needs are 0-100, we convert to a percentage
hungerBar.style.width = Length.Percent(stats.hunger);
energyBar.style.width = Length.Percent(stats.energy);
socialBar.style.width = Length.Percent(stats.social);
funBar.style.width = Length.Percent(stats.fun);
moodBar.style.width = Length.Percent(stats.mood);
// Update money display
moneyLabel.text = $"${stats.money}";
}
}
5. Practical Exercise
Your task is to implement the full pipeline:
- Design the static HUD in the UI Builder, making sure to create nested "fill" elements for your bars so you can change their width.
- Add the
CharacterHudStatsstruct and theget_hud_statscommand handler to your Rust code. - Implement the
HudController.csscript in Unity. - Run the game. As time passes and your character's needs decay (as implemented in Module 5), you should see the bars on the HUD shrink accordingly. The money display should show the character's current balance.

Conclusion
Well done! You have now created a vital piece of the game's user interface. The player can finally see their character's core stats at a glance, making the simulation feel much more interactive.
Key Takeaways from this Lesson:
- You learned the fundamentals of UI Toolkit, including creating UI structure with UXML, styling with USS, and adding behavior with C#.
- You reinforced the data flow between Rust and Unity by creating a new command to request data and using the JSON response to drive UI updates.
- You implemented a common game UI pattern: a dynamic HUD that visualizes character statistics like needs and resources.
In our next lesson, we will continue to build on these UI skills. We will build a settings menu with an autonomy toggle and volume controls, further polishing the user experience and adding more player control.