uw-ui-toolkit-binder-ed0ba0 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited uw-ui-toolkit-binder-ed0ba0 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Generate MVVM UI code using Unity 6's Runtime Data Binding.
docs/ProjectConfig.yaml for:ui_system — must be "UIToolkit" or "Mixed". If "UGUI", this skill does not apply.architecture_pattern — "so-first" or "di-first" (affects how ViewModels get their data).mcp.unity_mcp — if true, call refresh_unity after creating files.docs/NAMING_CONVENTIONS.md — UXML files use PascalCase (e.g., PlayerHUD.uxml), USS files match (e.g., PlayerHUD.uss).docs/CODING_STANDARDS.md — class structure order, serialization rules, async patterns apply to ViewModels.docs/GFD_Template.md for guidelines and use uw-game-feel-integrator for tween integration.| Use Case | Recommended | Why |
|---|---|---|
| Menus, settings, inventories, data-heavy UI | UI Toolkit | Batched rendering, no GameObject overhead, CSS-like styling |
| Animated in-game HUD, world-space UI | uGUI | Easier world-space Canvas, mature animation workflow |
| Complex project with both | Hybrid | UI Toolkit for menus, uGUI for in-game HUD |
If ProjectConfig.yaml -> ui_system is "UGUI", stop here — use uGUI patterns instead.
Model (Data/SO) --> ViewModel ([CreateProperty] MonoBehaviour) --> View (UXML + USS)[CreateProperty] on properties. This is the binding source. It bridges game data to UI elements without the View knowing about game logic.DataBinding in a controller script.For a feature called PlayerHUD inside a feature module:
Features/PlayerHUD/
├── Scripts/
│ ├── {RootNamespace}.PlayerHUD.asmdef
│ ├── PlayerHUDViewModel.cs
│ └── PlayerHUDController.cs
├── UI/
│ ├── PlayerHUD.uxml
│ └── PlayerHUD.uss
└── Tests/
└── {RootNamespace}.PlayerHUD.Tests.asmdefUse uw-unity-feature-scaffold to generate the module structure first, then add the UI-specific files.
The ViewModel exposes bindable properties using [CreateProperty] from Unity.Properties. Properties must have public getters for the binding system to read them.
using Unity.Properties;
using UnityEngine;
namespace {RootNamespace}.PlayerHUD
{
public class PlayerHUDViewModel : MonoBehaviour
{
[SerializeField] private int _maxHealth = 100;
private int _currentHealth;
[CreateProperty]
public int Health
{
get => _currentHealth;
set
{
if (_currentHealth == value) return;
_currentHealth = value;
}
}
[CreateProperty]
public float HealthPercent => _maxHealth > 0 ? (float)Health / _maxHealth : 0f;
[CreateProperty]
public string HealthText => $"{Health} / {_maxHealth}";
[CreateProperty]
public int CoinCount { get; private set; }
/// <summary>
/// Called by game systems to update health display.
/// </summary>
public void SetHealth(int current, int max)
{
_maxHealth = max;
Health = current;
}
public void AddCoins(int amount)
{
CoinCount += amount;
}
}
}When a [CreateProperty] value changes, the binding system detects it automatically on the next frame. No manual "notify" call is needed — Unity polls [CreateProperty] values each frame.
UXML defines the layout structure. Think of it like HTML for Unity UI.
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
<ui:VisualElement name="hud-root" class="hud-container">
<!-- Health bar -->
<ui:VisualElement name="health-section" class="stat-section">
<ui:Label name="health-label" text="100 / 100" class="stat-text" />
<ui:VisualElement name="health-bar-bg" class="bar-background">
<ui:VisualElement name="health-bar-fill" class="bar-fill health-fill" />
</ui:VisualElement>
</ui:VisualElement>
<!-- Coin counter -->
<ui:VisualElement name="coin-section" class="stat-section">
<ui:Label name="coin-label" text="0" class="stat-text" />
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>Naming: Use kebab-case for UXML element name attributes (e.g., health-label, coin-section). This follows USS selector conventions and avoids conflicts with C# naming.
USS is Unity's CSS-like styling language. Define visual appearance here, not in C#.
.hud-container {
position: absolute;
top: 16px;
left: 16px;
padding: 8px;
flex-direction: column;
}
.stat-section {
flex-direction: row;
align-items: center;
margin-bottom: 4px;
}
.stat-text {
font-size: 18px;
color: white;
-unity-font-style: bold;
min-width: 80px;
}
.bar-background {
width: 200px;
height: 16px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 4px;
overflow: hidden;
}
.bar-fill {
height: 100%;
border-radius: 4px;
transition-property: width;
transition-duration: 0.3s;
}
.health-fill {
background-color: rgb(76, 175, 80);
width: 100%;
}Transitions: USS supports transition-property, transition-duration, transition-timing-function, and transition-delay for smooth animated changes. Use these for bar fills, opacity fades, and position shifts instead of C# tweens where possible.
The controller loads the UXML, applies the USS, wires up data bindings, and handles UI events like button clicks.
using UnityEngine;
using UnityEngine.UIElements;
namespace {RootNamespace}.PlayerHUD
{
[RequireComponent(typeof(UIDocument))]
public class PlayerHUDController : MonoBehaviour
{
[SerializeField] private PlayerHUDViewModel _viewModel;
private UIDocument _document;
private Label _healthLabel;
private VisualElement _healthBarFill;
private Label _coinLabel;
private void Awake()
{
_document = GetComponent<UIDocument>();
}
private void OnEnable()
{
var root = _document.rootVisualElement;
// Cache element references (equivalent of GetComponent caching)
_healthLabel = root.Q<Label>("health-label");
_healthBarFill = root.Q<VisualElement>("health-bar-fill");
_coinLabel = root.Q<Label>("coin-label");
// Data bindings
_healthLabel.SetBinding("text", new DataBinding
{
dataSource = _viewModel,
dataSourcePath = new PropertyPath(nameof(PlayerHUDViewModel.HealthText))
});
_coinLabel.SetBinding("text", new DataBinding
{
dataSource = _viewModel,
dataSourcePath = new PropertyPath(nameof(PlayerHUDViewModel.CoinCount))
});
}
private void Update()
{
// Update bar width from ViewModel — binding handles labels,
// but bar width needs manual style update since it's a style property
_healthBarFill.style.width = Length.Percent(_viewModel.HealthPercent * 100f);
}
}
}Why cache in OnEnable, not Awake? The UIDocument.rootVisualElement may not be available in Awake. OnEnable is the earliest reliable point to query the visual tree.
For interactive elements like buttons, register callbacks in OnEnable and unregister in OnDisable to prevent memory leaks.
private Button _pauseButton;
private Button _settingsButton;
private void OnEnable()
{
var root = _document.rootVisualElement;
_pauseButton = root.Q<Button>("pause-button");
_settingsButton = root.Q<Button>("settings-button");
_pauseButton.clicked += OnPauseClicked;
_settingsButton.clicked += OnSettingsClicked;
}
private void OnDisable()
{
_pauseButton.clicked -= OnPauseClicked;
_settingsButton.clicked -= OnSettingsClicked;
}
private void OnPauseClicked()
{
// Raise event or call game system
}
private void OnSettingsClicked()
{
// Navigate to settings screen
}For more complex interactions (hover, drag, custom input), use RegisterCallback<T>:
// Pointer events
element.RegisterCallback<PointerEnterEvent>(OnHover);
element.RegisterCallback<PointerLeaveEvent>(OnHoverExit);
// Always unregister in OnDisable
element.UnregisterCallback<PointerEnterEvent>(OnHover);For inventories, leaderboards, or any list populated at runtime, create items from a template UXML and add them to a parent container.
[SerializeField] private VisualTreeAsset _itemTemplate;
private void PopulateInventory(List<ItemData> items)
{
var container = _document.rootVisualElement.Q<VisualElement>("inventory-list");
container.Clear();
foreach (var item in items)
{
var element = _itemTemplate.Instantiate();
element.Q<Label>("item-name").text = item.DisplayName;
element.Q<Label>("item-count").text = item.Count.ToString();
container.Add(element);
}
}For large lists (100+ items), use ListView with virtualization — it only renders visible items:
var listView = root.Q<ListView>("inventory-list");
listView.itemsSource = _inventoryItems;
listView.makeItem = () => _itemTemplate.Instantiate();
listView.bindItem = (element, index) =>
{
var item = _inventoryItems[index];
element.Q<Label>("item-name").text = item.DisplayName;
};When binding numeric data that needs formatting (percentages, currency, time), handle it in the ViewModel as a computed [CreateProperty] string:
[CreateProperty]
public string DamageText => $"{Damage:F0}";
[CreateProperty]
public string TimerText
{
get
{
int minutes = Mathf.FloorToInt(_remainingTime / 60f);
int seconds = Mathf.FloorToInt(_remainingTime % 60f);
return $"{minutes:D2}:{seconds:D2}";
}
}
[CreateProperty]
public string PriceText => $"{Price:N0} G";Keep formatting logic in the ViewModel, not the View layer. The View should only display strings.
root.Q<T>() in OnEnable and store references. Never query inside Update.ListView.RefreshItems() to update, not Clear() + re-add.style.* assignment can trigger layout recalculation. Batch changes or use USS class toggling (AddToClassList/RemoveFromClassList) instead.element.EnableInClassList("low-health", healthPercent < 0.25f) over setting individual style properties in C#.uw-game-feel-integrator for screen shake, flash effects, or tween-based UI animations that go beyond USS transitions.uw-unity-editor-tools if you need custom Inspector UI or Editor Windows built with UI Toolkit.uw-unity-test-runner — test ViewModel logic (property calculations, state changes) in EditMode tests without needing UI rendering.[CreateProperty] requires Unity.Properties namespace — always include the using.DataBinding and related types are in UnityEngine.UIElements..uxml and .uss files alongside ViewModel C# scripts.NAMING_CONVENTIONS.md): PlayerHUD.uxml, PlayerHUD.uss.root.Q<T>() calls in OnEnable — never query the visual tree in Update.OnEnable, unregister in OnDisable.[SerializeField] private for Inspector-exposed fields on ViewModels and Controllers — never public fields.ProjectConfig.yaml -> mcp.unity_mcp is true, call refresh_unity after creating files.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.