Skip to content

UI Development

Choosing a UI System

The modern, retained-mode UI system. Uses UXML for layout and USS for styling, similar to HTML/CSS. Best for:

  • Main menus, settings screens, HUD overlays
  • Editor tools and custom inspectors
  • Data-heavy interfaces (inventories, skill trees, dashboards)
  • Projects that need source-control-friendly UI definitions

Canvas/UGUI (Legacy, still supported)

The older immediate-style system using GameObjects and Canvas components. Still valid for:

  • World-space UI (floating health bars, interaction prompts)
  • Projects already using Canvas extensively
  • Quick prototypes where visual layout in the scene view is preferred

UI Toolkit

Document Structure

A UI Toolkit panel needs a UIDocument component referencing a UXML file:

public class MainMenuController : MonoBehaviour
{
    [SerializeField] private UIDocument _document;

    private Button _startButton;
    private Button _settingsButton;

    private void OnEnable()
    {
        var root = _document.rootVisualElement;
        _startButton = root.Q<Button>("start-button");
        _settingsButton = root.Q<Button>("settings-button");

        _startButton.clicked += OnStartClicked;
        _settingsButton.clicked += OnSettingsClicked;
    }

    private void OnDisable()
    {
        _startButton.clicked -= OnStartClicked;
        _settingsButton.clicked -= OnSettingsClicked;
    }

    private void OnStartClicked() => SceneManager.LoadScene("Game");
    private void OnSettingsClicked() => ShowSettingsPanel();
}

USS Styling

.main-menu {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
}

.menu-button {
    width: 300px;
    height: 60px;
    margin: 10px;
    font-size: 24px;
    -unity-font-style: bold;
    background-color: rgb(40, 40, 40);
    color: rgb(220, 220, 220);
    border-radius: 8px;
}

.menu-button:hover {
    background-color: rgb(60, 60, 60);
}

Runtime Data Binding (Unity 6)

Bind game variables directly to UI elements without boilerplate:

[UxmlElement]
public partial class HealthBar : VisualElement
{
    [UxmlAttribute]
    public float CurrentHealth { get; set; }

    [UxmlAttribute]
    public float MaxHealth { get; set; }
}

Custom Controls with [UxmlElement]

The deprecated UXMLFactory pattern is replaced by attribute-based registration:

[UxmlElement]
public partial class RadialProgress : VisualElement
{
    [UxmlAttribute]
    public float Progress { get; set; }

    public RadialProgress()
    {
        generateVisualContent += OnGenerateVisualContent;
    }

    private void OnGenerateVisualContent(MeshGenerationContext ctx)
    {
        // Custom drawing logic
    }
}

Canvas/UGUI

Canvas Render Modes

  • Screen Space - Overlay: Renders on top of everything. No camera required. Use for HUD.
  • Screen Space - Camera: Renders as if a fixed distance from a camera. Supports post-processing.
  • World Space: Exists in 3D space. Use for in-game screens, floating labels, VR interfaces.

Layout Components

  • HorizontalLayoutGroup: Arrange children in a row
  • VerticalLayoutGroup: Arrange children in a column
  • GridLayoutGroup: Arrange children in a grid
  • ContentSizeFitter: Auto-size to content
  • LayoutElement: Override layout sizing per element

Anchoring for Responsive UI

Set anchors to handle different screen resolutions: - Anchor to corners for elements that should stay at screen edges - Anchor to center for centered elements - Stretch anchors for elements that should scale with screen size

Performance

  • Minimize Canvas rebuilds: separate dynamic and static elements onto different Canvases
  • Use Canvas Groups to toggle visibility instead of enabling/disabling GameObjects
  • Mark static UI elements with "Raycast Target: false" if they do not need click detection
  • Use TextMeshPro for ALL text rendering; never use the legacy Text component

TextMeshPro

Always use TextMeshPro (TMP) for text:

using TMPro;

[SerializeField] private TMP_Text _scoreText;

private void UpdateScore(int score)
{
    _scoreText.SetText("Score: {0}", score);
}

Use SetText with format parameters instead of string concatenation to avoid garbage collection.