06 — Wiring It Up With C#

Open Scripts/HUDController.cs and Scripts/HealthScoreSystem.cs side by side. Two scripts, two jobs:

This separation (a lightweight MVC) is what Unity's own samples like Dragon Crashers use, and it's the habit worth grading for.

Getting into the UI

VisualElement root = GetComponent<UIDocument>().rootVisualElement;

Everything starts at the UIDocument's root element. Do this in OnEnable — the visual tree is rebuilt when a UIDocument is disabled/enabled, so OnEnable/OnDisable is the correct lifecycle pair (not Start).

Finding elements: UQuery

Button damageButton = root.Q<Button>("damage-button");   // by name
Label anyLabel     = root.Q<Label>();                    // first Label
var allButtons     = root.Query<Button>().ToList();      // all of them

Q is short for Query. Cache the results in fields once; never call Q every frame.

Reacting to the UI

// Buttons: the clicked event
damageButton.clicked += () => stats.TakeDamage(10);

// Every value-holding control (Slider, Toggle, TextField, Dropdown...):
healthSlider.RegisterValueChangedCallback(evt =>
    stats.SetHealth(Mathf.RoundToInt(evt.newValue)));

// Lower-level pointer events power the hover-description feature:
button.RegisterCallback<PointerEnterEvent>(_ => ShowDescription(...));

One pattern — RegisterValueChangedCallback — covers sliders, toggles, dropdowns, and text fields alike. Learn it once, use it everywhere.

Updating the UI

scoreLabel.text = $"Score: {newScore}";        // text
healthBar.value = current;                     // progress bar fill
healthBar.title = $"{current} / {max}";        // text on the bar
healthSlider.SetValueWithoutNotify(current);   // sync a control WITHOUT re-firing its callback
lockedButton.SetEnabled(false);                // triggers :disabled styling

SetValueWithoutNotify matters: when the model changes health, we move the debug slider to match — but if that fired the slider's own callback, we'd loop forever.

Driving USS from code

healthBar.AddToClassList("health-low");        // bar turns red via USS
scoreLabel.AddToClassList("score-pop");        // transition animates the pop
scoreLabel.schedule.Execute(() =>
    scoreLabel.RemoveFromClassList("score-pop")).StartingIn(150);

C# decides when; USS decides what it looks like. schedule.Execute is UI Toolkit's built-in timer — handy for delays without coroutines.

Always unsubscribe

OnDisable mirrors every += with a -=. Skipping this causes ghost callbacks and memory leaks — the kit shows the full pattern.

Exercises

  1. Add a Reset button (UXML + USS + a clicked handler that sets health to max and score to 0).
  2. Make the description panel show the player's name when the TextField changes (already wired — read how, then extend it to validate empty names).
  3. Harder: give HealthScoreSystem a Died event when health hits 0, and show a "You Died" overlay by toggling a USS class that changes display: none → flex.

Next: 07 - Reusing and Exporting