# 7. Pitfalls and Performance Part of [[UI Toolkit and Diegetic UI]]. Requires Unity 6000.5+. Previous: [[6.Diegetic Terminal and Wrist Display]]. ## 7.1 Pitfalls you will hit | Symptom | Cause | Fix | |---|---|---| | `Q<T>()` returns `null` | Name typo, wrong type, or UXML not loaded yet (code ran before the reload callback) | Query only inside `RegisterUIReloadCallback`; use a `Require<T>` helper that throws with the missing name | | Handlers fire twice after toggling a component | `PanelRenderer` keeps its tree on disable/enable and the reload callback re-fires with the same version | Guard with `if (version == loadedVersion) return;` ([[2.UI Toolkit Project Setup]]). With `UIDocument`, tree rebuilds each enable: unregister in `OnDisable` | | Old references stop working after a UXML edit | Live reload rebuilt the tree (new version) | Re-query everything in the callback; never cache elements across versions | | UI does not appear in Game view | Panel Settings or Source Asset not assigned; root has zero size; theme missing | Debugger: check the panel exists and `world-bound` of root is non-empty. Give the root `flex-grow:1` or absolute fill | | Elements ignore your style | Specificity or sheet order; inline style set in UXML beats USS; wrong selector for built-in parts | Debugger -> *Matching Selectors*; use descendant selectors on `.unity-...` parts | | Clicks fall through to the world / are swallowed by the HUD | Full-screen root has default `picking-mode: Position` | `picking-mode="Ignore"` on non-interactive containers; keep it only on elements that must catch the pointer | | Layout looks different in builds/resolutions | Scale mode Constant Pixel Size, or px-based sizes with unconstrained text | Scale With Screen Size + reference resolution; use `%`/flex for containers | | Transition does nothing | You changed the class in the same frame the element was created/shown, so there was no "from" style; or the property is not in `transition-property` | Add the end-state class one scheduler tick later (see `Open()` in [[3.HUD and Inventory Example]]); list the property | | Transition on `width`/`left` janky | Animating layout properties | Animate `translate` / `scale` / `opacity` / `rotate` instead | | Binding silently does nothing | Missing `[CreateProperty]`, wrong property name, or the source never notifies | Console warnings (Panel Settings has *Binding Console Logs* levels); see [[4.Data Binding and Custom Controls]] 4.7 | | Inspector edits at runtime do not move the HUD | Edits bypass your setters, so no notification | `OnValidate()` re-notify, as in `PlayerStats` | | `ListView` shows nothing / wrong rows | `makeItem` returns `null`; `fixedItemHeight` missing; count changed without `RefreshItems()` | Return a real element; set the height; call `RefreshItems()` after changing the source list | | `ListView` rows show stale data after scrolling | `bindItem` does not reset every field (rows are recycled) | Always assign every visible field in `bindItem` (including clearing icons) | | No grid in `ListView` | `ListView` is a vertical list | Use a row-of-slots layout in `makeItem`, or the grid-like options in newer collection views; check the 6000.5 manual before building your own | | Text field eats gameplay keys / gameplay eats text | Two input systems both active | Gate gameplay input while a text field is focused; see [[5.Diegetic UI Approaches]] 5.6 | | Cursor visible/locked incorrectly after closing a window | State owned in many places | One component owns cursor mode; others raise events | | Code uses `UnityEditor` namespaces | Editor-only `ObjectField`, `PropertyField` etc. do not exist in builds | Use runtime controls only (`Button`, `Label`, `TextField`, `Slider`, `ListView`, `ScrollView`, `DropdownField`, ...) | | Custom control missing from UI Builder | Not `partial`, no `[UxmlElement]`, or compile errors | Fix compile, then reimport the UXML; check *Project > Custom Controls (C#)* in the Library | | World-space UI sorts wrong / draws through walls | Sorted by camera distance, no 2D sorting layers, no depth-test control | Reduce overlap, change layout; URP Render Objects or custom pass for depth behaviour; or use uGUI world canvas | | Render-texture UI is black | RenderTexture not assigned to **both** Panel Settings and the material; or panel disabled; or wrong color format | Check A1/A2/A5 in [[6.Diegetic Terminal and Wrist Display]] | | Render-texture UI colors off | Linear project but non-sRGB texture format (or the reverse) | `R8G8B8A8_SRGB` in Linear, `R8G8B8A8_UNorm` in Gamma | | Clicks land in the wrong place on the 3D screen | Y flips wrong; non-MeshCollider; ray hitting another collider; camera viewport differs | Re-read A6; log `hit.textureCoord` and compare to where you point | | UI looks blurry on the 3D screen | Low RT resolution or no mips/anisotropy; minification at distance | Larger RT, mips + aniso, larger type | ## 7.2 Performance rules **Layout and style** 1. **Animate `translate`, `scale`, `rotate`, `opacity`, background/text color** instead of `width/height/left/top`. Layout properties re-run the layout pass for affected subtrees; transform and opacity do not. 2. **Prefer `display: none` for panels that are closed for a long time** (removed from layout, rendering and picking), and `visibility: hidden` / `opacity: 0` for things that blink in and out and must keep their layout slot. 3. **Keep the tree shallow.** Every ancestor participates in layout and picking. Prefer one container with `flex-wrap` over nested wrappers. 4. **Batch class changes.** Toggling several classes per frame on many elements invalidates style repeatedly. Change one state class on a parent and let descendant selectors do the rest. 5. **Avoid per-frame `style.*` writes** in `Update` unless the value actually changed. Compare to the last value first. 6. **Text**: setting `label.text` to the same string is a no-op for layout in most cases, but formatting a new string every frame allocates. Format only when the source value changes (bindings with `OnSourceChanged` do this for free). **Data and events** 7. **Notify, do not poll.** Use `INotifyBindablePropertyChanged`/`IDataSourceViewHashProvider` so bindings only run when data changes. Avoid `BindingUpdateTrigger.EveryUpdate` outside prototypes. 8. **Cache queries.** Never call `Q`/`Query` per frame; store references once in the reload callback. 9. **`ListView` for anything beyond ~50 items.** Use `fixedItemHeight` (virtualization with fixed-size rows is the cheapest path); keep `makeItem` allocation-heavy work there and `bindItem` allocation-free (cache child refs in `userData`, as in [[3.HUD and Inventory Example]]). 10. **Prefer UQuery `Query<T>().Build()`/`ForEach`** to `ToList()` in hot paths (no list allocation). 11. **Lambdas allocate.** Closures capture and allocate per registration; for per-row/per-item callbacks use a static method and `evt.currentTarget`/`userData`. 12. **Unregister callbacks** you register on long-lived objects (`inventory.Changed -= ...`) in `OnDisable`. 13. `schedule.Execute(...).Every(ms)`: pick the slowest rate that looks right; pause items on hidden UI. **Rendering** 14. **Hints**: `VisualElement.usageHints` (for example `UsageHints.DynamicTransform` for elements whose transforms animate every frame, `UsageHints.GroupTransform` for a container that moves as a block) help the renderer decide what to batch and which vertex data to keep mutable. Set them on genuinely animated elements only; misusing hints costs memory. 15. **Atlases and textures**: use sprite atlases for icons so draw calls batch; avoid one big unique texture per element. Prefer `Sprite`/`Texture2D` icons in `background-image`. 16. **Transparency and overdraw** are the GPU cost: big translucent panels with blurs/filters (6.3+ USS filters) are expensive at 4K. Use filters sparingly on large areas. 17. **Check the Frame Debugger** (`Window > Analysis > Frame Debugger`) for UI draw call count and why batches break; check the Profiler for `UIElements` markers in the Update and render phases. Measure before optimizing. ## 7.3 Diegetic-specific performance - **Every distinct render texture panel is an extra render each frame** (as far as I can tell; I did not verify whether Unity skips repainting an unchanged panel, so test with the Profiler). Keep RT sizes as small as the legibility target allows (1024 x 640 is generous), and disable the `PanelRenderer` when the screen is far from the camera or off-screen (for example via renderer visibility callbacks `OnBecameVisible`/`OnBecameInvisible` on the screen mesh). - **Many world-space panels** (floating health bars): each is a `PanelRenderer` with its own visual tree. Hundreds is not what UI Toolkit world space was designed for; profile with your target numbers and consider pooling, distance-based disabling, or a non-UI solution (sprites/meshes with a shader) for crowds. - **Sharing**: panels that share a `PanelSettings` asset share one panel; a world-space asset's *Pixels Per Unit* applies to all of them. Create assets per distinct physical scale. - **Disabled `PanelRenderer` keeps its tree in memory** (6000.5). That is great for toggling (no rebuild), but it is not free memory-wise; destroy objects you will not reuse. - **Mipmaps** on render textures add a small GPU cost per update but are a big legibility win. - **MSAA** on the render texture costs memory and bandwidth; try it only for thin-stroke UIs. ## 7.4 Diagnosing diegetic UI 1. **Texture black**: select the `PanelSettings` asset: is *Target Texture* set? Select the RT in the Project window: does the preview show your UI? If yes, the material is wrong. If no, the `PanelRenderer` is disabled/unassigned or the UXML root has zero size. 2. **UI visible but clicks do nothing**: log inside `ScreenToPanel`. Return NaN means "no hit"; check `InUse`, collider identity, layers. Then check `Cursor.lockState` (locked cursor reports a fixed screen position). 3. **Clicks work but the caret/keys do not**: panel focus. Call `input.Focus()` after use mode begins (next tick) and ensure no other UI (uGUI `EventSystem` selection) is stealing focus. 4. **Keys move the player while typing**: you did not gate gameplay input; wire `onUseModeChanged`. 5. **World-space panel invisible**: check the Scene view gizmo (bounds), the back face (rotate 180 degrees), the camera culling mask / layer of the `PanelRenderer`'s GameObject, and that Panel Settings Render Mode is World Space. 6. **World-space panel too big/small**: pixels / Pixels Per Unit = meters. Fix PPU in the asset or the size in World-Space Dimensions. ## 7.5 When not to use UI Toolkit - You need **XR Interaction Toolkit's** established UI interaction path today (it targets uGUI), depth-tested/lit UI, or heavy per-Graphic shader effects: use uGUI world-space Canvas. - A thousand tiny moving objects with health bars: use instanced meshes/sprites, not UI of any kind. - You are shipping on a Unity version older than the features used here: this tutorial is 6000.5+ only. > [!note] Update (VR) > XR Interaction Toolkit 3.2+ (Unity 6.2+) now supports UI Toolkit world-space panels directly, which supersedes the uGUI-only caveat in the first bullet for XRI. See [[8.UI Toolkit for VR]]. ## 7.6 Docs and further reading - UI Toolkit overview: https://docs.unity3d.com/6000.5/Documentation/Manual/UIElements.html - `PanelRenderer` manual: https://docs.unity3d.com/6000.5/Documentation/Manual/ui-systems/panel-renderer-component.html - `PanelRenderer` API: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/UIElements.PanelRenderer.html - World Space UI: https://docs.unity3d.com/6000.3/Documentation/Manual/ui-systems/world-space-ui.html - Panel Settings reference: https://docs.unity.com/en-us/engine/7000.0/manual/uitoolkits/uielements/uie-support-for-runtime-ui/uie-render-runtime-ui/uie-runtime-panel-settings - `PanelSettings.SetScreenToPanelSpaceFunction`: https://docs.unity3d.com/ScriptReference/UIElements.PanelSettings.SetScreenToPanelSpaceFunction.html - `PanelSettings.targetTexture`: https://docs.unity3d.com/6000.0/Documentation/ScriptReference/UIElements.PanelSettings-targetTexture.html - Custom controls: https://docs.unity3d.com/6000.3/Documentation/Manual/UIE-create-custom-controls.html - Runtime binding: https://docs.unity3d.com/Manual/UIE-runtime-binding.html - UI Toolkit status posts (Unity forum): https://discussions.unity.com/t/ui-toolkit-development-status-and-next-milestones-november-2025/1698009 Next: [[8.UI Toolkit for VR]] Back to [[UI Toolkit and Diegetic UI]].