Problem

UI Toolkit resources and examples at scale are scarce. The built-in system has no navigation, no dependency injection, no view locator, and no code generation for compile-time safe bindings. Without source generators like CommunityToolkit.Mvvm, implementing full MVVM requires manual wiring that cancels out the pattern's benefits. The closest design pattern comparison is HTML, CSS and Vanilla JS from the 2000s.

The challenge: build a production-scale UI system handling 15+ screens and modals with navigation, routing, state management, and theming — while keeping inspector and ScriptableObject references at zero.

Key Technical Decisions

  • UIRouter singleton for convention-based navigation. NavigateTo<T>() uses the C# type as the destination identifier. No enums, no switch statements, no inspector mappings, no string lookups. A new screen equals two files — .cs and .uxml — with zero registration calls.
  • Convention-based ViewFactory. ViewFactory.Create<T>() loads Resources/Views/{TypeName}.uxml by convention. The class name is the file path. If the UXML doesn't exist, an immediate Debug.LogError fires before a null-ref can occur.
  • Typed Elements Records pattern. Every view's element query is a single call that returns a sealed record of strongly-typed VisualElement references. 16 records decouple UXML structure from C# logic. Element names live in Constants/Elements.cs, not as magic strings. C# 9 positional records give immutability, value equality, and zero boilerplate per record.
  • Dual UILayer stacks for screens and modals. Two independent Stack<BaseView> instances — one for screens, one for modals. Each manages its own lifecycle. Push deactivates the current view and stores history. Pop restores it. Modals stack over any screen without affecting screen state.
  • Complete view lifecycle contract. BaseView defines four phases: constructor (clones UXML once), Activate() (one tree walk + event bind), Deactivate() (unbind events), Dispose() (full cleanup). The split between expensive element querying and cheap event binding prevents stale handlers and ensures clean teardown.
  • Persistent Chrome Shell. The header bar, footer bar, and modal overlay live outside both stacks and survive every screen push/pop. The back button is bound once by MenuLayout and always calls UIRouter.Instance.Back(), regardless of which view is active. Views don't own or know about the chrome.
  • USS custom property theming. core.uss defines a :root block of CSS custom properties for all colours, fonts, border radii, and motion values. screens.uss and modals.uss inherit these tokens. Changing a single variable in core.uss propagates to every panel.
  • USS-class-driven audio. UIAudioHandler listens for PointerEnterEvent and ClickEvent at root level and checks the target for audio--hover or audio--click USS classes. Audio assignment happens in UXML/USS. No C# event wiring per button.
  • C# 9 records on .NET Standard 2.1. IsExternalInit.cs polyfill enables sealed positional records in Unity's .NET Standard 2.1 runtime. The entire Elements Records pattern depends on this. Without it, every record would require explicit constructor and property boilerplate.
  • Runtime debug tool. F3-toggled OnGUI panel that edits campaignStarted and missionsCompleted on live save data. Proves the modal system, save-data round-trip, and mission unlock logic work without needing a game loop.

Next Steps

  • Expand the architecture to a full HUD demo with gameplay integration and extended data pipelines.
  • Publish architecture deep-dive documentation with class diagrams, navigation sequences, and data flow.