World at War UI Toolkit
Recreation of the Call of Duty: World at War menu system in Unity UI Toolkit. A convention-based navigation system modelled after Epic Games' Lyra.
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 —.csand.uxml— with zero registration calls. - Convention-based ViewFactory.
ViewFactory.Create<T>()loadsResources/Views/{TypeName}.uxmlby convention. The class name is the file path. If the UXML doesn't exist, an immediateDebug.LogErrorfires before a null-ref can occur. - Typed Elements Records pattern. Every view's element query is a single call that returns a
sealed recordof strongly-typedVisualElementreferences. 16 records decouple UXML structure from C# logic. Element names live inConstants/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.
BaseViewdefines 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
MenuLayoutand always callsUIRouter.Instance.Back(), regardless of which view is active. Views don't own or know about the chrome. - USS custom property theming.
core.ussdefines a:rootblock of CSS custom properties for all colours, fonts, border radii, and motion values.screens.ussandmodals.ussinherit these tokens. Changing a single variable incore.usspropagates to every panel. - USS-class-driven audio.
UIAudioHandlerlistens forPointerEnterEventandClickEventat root level and checks the target foraudio--hoveroraudio--clickUSS classes. Audio assignment happens in UXML/USS. No C# event wiring per button. - C# 9 records on .NET Standard 2.1.
IsExternalInit.cspolyfill 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
OnGUIpanel that editscampaignStartedandmissionsCompletedon 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.