# 2. UI Toolkit Project Setup
Part of [[UI Toolkit and Diegetic UI]]. Requires Unity 6000.5+. Previous: [[1.UI Toolkit Concepts]]. Next: [[3.HUD and Inventory Example]].
## 2.1 Packages and project settings
1. Create a project from the **Universal 3D** template (URP). URP matters later: 6.3+ custom UI shaders via Shader Graph are URP-only, and the render-texture diegetic technique works in any pipeline.
2. **Input**: new 3D templates use the **Input System** package and set *Active Input Handling* to "Input System Package (New)". The scripts in this tutorial use `UnityEngine.InputSystem` (`Keyboard.current`, `Mouse.current`). If you are on the legacy Input Manager, swap those calls for `Input.GetKeyDown` and `Input.mousePosition`.
3. No extra package is needed for UI Toolkit runtime: it is part of the engine (`UnityEngine.UIElementsModule`). The UI Builder and UXML/USS importers ship with the editor.
## 2.2 Folder layout
Keep UXML, USS and scripts that belong together in one folder per feature. UXML `<Style src="...">` paths are relative to the UXML file, so moving a folder as a unit does not break references.
```
Assets/
UI/
Settings/
GamePanelSettings.asset <- screen-space panel
WristPanelSettings.asset <- world-space panel (own asset: Pixels Per Unit is per asset)
TerminalPanelSettings.asset <- render-texture panel
Shared/
Theme.uss
Hud/
Hud.uxml
Hud.uss
HudController.cs
Inventory/
Inventory.uxml
InventoryRow.uxml
Inventory.uss
InventoryScreenController.cs
Terminal/
Terminal.uxml Terminal.uss TerminalController.cs ...
Game/
PlayerStats.cs ItemDefinition.cs Inventory.cs
```
## 2.3 Create a Panel Settings asset
1. `Assets > Create > UI Toolkit > Panel Settings Asset`. Name it `GamePanelSettings`.
2. Unity creates a default runtime theme alongside it and assigns it to **Theme Style Sheet**. If buttons render with no styling at all, the theme is missing; reassign one.
3. Configure for a HUD:
| Property | Value | Why |
|---|---|---|
| Render Mode | Screen Space Overlay | Normal screen HUD |
| Scale Mode | Scale With Screen Size | Author once at a reference size |
| Reference Resolution | 1920 x 1080 | Your design size |
| Screen Match Mode | Match Width Or Height, Match = 0.5 | Compromise for ultrawide / portrait |
| Sort Order | 0 | Raise for overlays; lower = drawn earlier |
| Target Texture | None | Only set for the render-texture technique |
Why not Constant Pixel Size? It is fine for editor-like tools. For games, you want the same layout at 1080p and 4K, so scale with screen.
> [!note] One Panel Settings = one panel
> Every component pointing at the same `PanelSettings` asset renders into the **same panel** (one visual tree space, one focus context). Different render modes/targets need different assets. This is why the wrist display and terminal later get their own assets.
## 2.4 Add UI to the scene with `PanelRenderer` (6000.5+)
1. `GameObject > UI Toolkit > Panel Renderer` (or add `Component > UI Toolkit > Panel Renderer`).
2. Assign **Panel Settings** and **Source Asset** (your UXML).
3. `PanelRenderer` derives from `Renderer`, so it has `sortingOrder` (an `int`), bounds, and culling like any renderer. Siblings render by ascending Sort Order; children render on top of parents. Give siblings unique Sort Orders when layering matters.
4. In **world space**, the component additionally exposes *World-Space Dimensions* (size mode, size, pivot, pivot reference size). See [[6.Diegetic Terminal and Wrist Display]].
### Getting the root: the reload callback pattern
`PanelRenderer` has no public `rootVisualElement`. You register a callback instead. It runs immediately if the UI is already loaded, then again whenever it reloads (asset edit/live reload, changed `PanelSettings` / `VisualTreeAsset`).
```csharp
using UnityEngine;
using UnityEngine.UIElements;
public class ReloadPatternExample : MonoBehaviour
{
PanelRenderer panelRenderer;
int loadedVersion = -1;
void OnEnable()
{
panelRenderer = GetComponent<PanelRenderer>();
panelRenderer.RegisterUIReloadCallback(OnUIReload); // versioned overload
}
void OnDisable() => panelRenderer.UnregisterUIReloadCallback(OnUIReload);
void OnUIReload(PanelRenderer renderer, VisualElement root, int version)
{
if (version == loadedVersion) return; // same tree as last time: skip re-wiring
loadedVersion = version;
var play = root.Q<Button>("play");
play.clicked += () => Debug.Log("Play");
}
}
```
Why the version check: the docs state that disabling and re-enabling a `PanelRenderer` keeps the visual tree in memory (unlike `UIDocument`, which destroys and rebuilds it), so the callback fires again with the **same** version. If you re-wired anyway you would add a second `clicked` handler to the same button. When the tree is genuinely rebuilt, the version changes and your old references are stale, so you re-query everything.
> [!info] Using `UIDocument` instead (any Unity 6)
> `UIDocument` still works and is not removed; new features target `PanelRenderer` first. The swap is mechanical:
> - Component: `UIDocument` -> `PanelRenderer` (reassign Panel Settings + Source Asset).
> - Code: `GetComponent<UIDocument>().rootVisualElement` in `OnEnable` -> reload callback.
> - Behaviour: `UIDocument` rebuilds its tree on every enable; `PanelRenderer` keeps it.
> - `sortingOrder`: `float` on `UIDocument`, `int` on `PanelRenderer`.
> Both implement `IPanelComponent`, can share one `PanelSettings`, and can coexist during migration.
## 2.5 Input and the EventSystem
Screen-space panels receive input one of two ways:
- **With an active `EventSystem`** (uGUI present, Input System UI Input Module): the EventSystem is the input source and UI Toolkit panels receive redirected events.
- **No `EventSystem`**: UI Toolkit's built-in input handles it.
For **world-space** panels, add a **Panel Input Configuration** component (`GameObject > UI Toolkit > Panel Input Configuration`); see [[6.Diegetic Terminal and Wrist Display]]. Its *Panel Input Redirection* default (**Auto-switch**) uses the EventSystem when one is active and UI Toolkit's own input otherwise. Mixed uGUI + UI Toolkit scenes should keep one EventSystem.
## 2.6 Shared theme file
Create `Assets/UI/Shared/Theme.uss`. Variables defined on `.theme` inherit to every descendant of an element with that class. Add `class="theme"` to the top element of every UXML.
```css
.theme {
--color-bg: rgba(12, 16, 24, 0.82);
--color-bg-solid: rgb(18, 24, 34);
--color-border: rgba(120, 190, 255, 0.35);
--color-text: rgb(230, 236, 245);
--color-muted: rgb(140, 154, 175);
--color-accent: rgb(64, 200, 255);
--color-health: rgb(224, 82, 82);
--color-gold: rgb(240, 196, 70);
--radius: 8px;
--gap: 8px;
color: var(--color-text);
font-size: 16px;
}
.panel {
background-color: var(--color-bg);
border-width: 1px;
border-color: var(--color-border);
border-radius: var(--radius);
padding: 12px;
}
.caption {
font-size: 11px;
color: var(--color-muted);
-unity-font-style: bold;
letter-spacing: 2px;
}
```
## 2.7 Fonts and text
- Runtime UI Toolkit text uses the panel's text settings. You can reference a Font Asset or TMP-style font through `-unity-font-definition: url("project://database/Assets/Fonts/MyFont SDF.asset");`.
- Dynamic font assets work for most UI; SDF font assets give crisp scaling for world-space UI. Unity's forum status posts describe a migration to the Advanced Text Generator (ATG); check the 6000.5 Text documentation for current defaults before committing a font pipeline.
## 2.8 Smoke test
1. Create `Assets/UI/Hud/Hud.uxml` with one `Label` and a USS file; assign to a `PanelRenderer` with `GamePanelSettings`.
2. Press Play. If nothing appears: Debugger (`Window > UI Toolkit > Debugger`), set the Game view as target, and check (a) the panel exists, (b) your root has a size (`flex-grow:1` or absolute fill), (c) the theme is assigned.
Next: [[3.HUD and Inventory Example]].