~3 min read

UI in a 3D Game — uGUI and UI Toolkit

Canvas, RectTransform, EventSystem — and two competing UI systems.

In Unity 6, two UI systems coexist:

  • uGUI (Unity UI) — what Unity has used for years. Built on GameObjects and Canvas.
  • UI Toolkit — the newer system based on UXML + USS (analogous to HTML + CSS). For the editor it’s already the standard, and for runtime it’s stable and recommended for new UI, but many projects are still on uGUI.

We’ll start with uGUI as the more widespread one, then cover UI Toolkit.

uGUI: Canvas and RectTransform

In uGUI, the UI lives inside a Canvas — a special GameObject. Child UI elements use a RectTransform (an extended Transform with anchors, pivot, and size).

Three Canvas modes:

Render ModeWhere it’s drawnWhen to choose it
Screen Space – OverlayOn top of everything, in a separate passHUD, main menu
Screen Space – CameraIn front of the camera, at a given distanceIf the UI should go through post-processing
World SpaceIn the 3D scene as a regular objectHealth bar above an enemy, a computer screen in a room

Anchors — the foundation of responsiveness

An anchor is a point on the parent element to which a side of the current element is “glued.” In essence, it’s an analog of CSS position: absolute; top/left/right/bottom.

If anchor min and max coincide (for example, both at (0.5, 0.5) — the center), the element has a fixed size. If they’re spread apart (min = (0, 0), max = (1, 1)) — the element stretches across the entire parent.

Web

Flexbox/Grid lay things out declaratively — you describe the rules and the browser arranges them. CSS classes let you reuse styles.

Unity

uGUI is closer to “absolute layouts with anchors.” For flex-like layout there are components: HorizontalLayoutGroup, VerticalLayoutGroup, GridLayoutGroup — but they imperatively recompute the layout every frame and can be slow with a large number of elements.

Basic uGUI components

  • Image — a picture (sprite or solid color).
  • RawImage — a texture without sprite settings.
  • TextMeshPro – Text (UI) — text. Use TMP, not Legacy Text — TMP provides SDF fonts with crisp scaling and proper formatting (rich text, outline, gradient).
  • Button — a button with highlights and onClick.
  • Toggle / Slider / InputField (TMP) — standard controls.
  • ScrollRect — scrollable content.
  • Mask / RectMask2D — clipping by shape.
public class HudController : MonoBehaviour
{
    [SerializeField] private TMPro.TextMeshProUGUI scoreText;
    [SerializeField] private UnityEngine.UI.Image healthBar;

    public void SetScore(int score) {
        scoreText.text = $"Score: {score}";
    }

    public void SetHealth(float normalized) {
        healthBar.fillAmount = Mathf.Clamp01(normalized);
    }
}

EventSystem

For buttons and any UI input in the scene to work, you need an EventSystem (one). It routes clicks, hovers, and touch events to UI elements. It’s created automatically when you add a Canvas through the menu “GameObject → UI → …”.

The main enemy of uGUI performance

Every change to the size or position of a UI element marks the Canvas as “dirty” and rebuilds its mesh. Animating a UI with a large number of elements on a single Canvas will eat your FPS. Split it into several Canvases (one for the static HUD, a separate one for animated damage numbers), or use UI Toolkit.

UI Toolkit: UXML + USS

UI Toolkit operates on different concepts. You describe a tree of elements in UXML (XML), style it in USS (close to CSS), and connect it to the scene through the UIDocument component.

<!-- HUD.uxml -->
<ui:UXML xmlns:ui="UnityEngine.UIElements">
  <ui:VisualElement class="hud">
    <ui:Label name="score" text="Score: 0" class="score" />
    <ui:VisualElement name="healthbar" class="bar" />
  </ui:VisualElement>
</ui:UXML>
/* HUD.uss */
.hud { position: absolute; top: 16px; left: 16px; }
.score { color: white; font-size: 24px; }
.bar { width: 200px; height: 12px; background-color: red; border-radius: 6px; }
public class HudUI : MonoBehaviour
{
    private Label _scoreLabel;
    private VisualElement _healthBar;

    private void OnEnable() {
        var root = GetComponent<UIDocument>().rootVisualElement;
        _scoreLabel = root.Q<Label>("score");
        _healthBar  = root.Q<VisualElement>("healthbar");
    }

    public void SetScore(int score) => _scoreLabel.text = $"Score: {score}";
    public void SetHealthNormalized(float n) {
        _healthBar.style.width = new Length(n * 100f, LengthUnit.Percent);
    }
}
Web

This is literally like browser UI: query selectors (root.Q<>("name")), USS styles resemble CSS, and UXML resembles HTML. Flexbox works.

Unity

A big plus of UI Toolkit is performance (retained mode, it doesn’t recreate the mesh on every change). The downside — World Space support only appeared in Unity 6.2 as experimental, and it’s not yet production-ready. Before that, in World Space it’s uGUI only.

What to choose in a new project

  • HUD and in-game UI — UI Toolkit for most cases, especially if there are many changing values and complex layouts.
  • Main menu — any approach; UI Toolkit is more convenient, uGUI if you need visual effects (shaders, masks).
  • 3D UI in the world (labels above enemies, screens on walls) — uGUI in World Space (stable); or UI Toolkit World Space from Unity 6.2 (experimental, being prepared for stabilization).

In the next chapter — Prefabs and ScriptableObjects, without which a project quickly turns into an unmaintainable mess.