Missing UI — Implementation Plan¶
Screens designed (prefabs exist) but not yet wired: FullMapView, WinScreenView, LoseScreenView, SettingsView. Read
docs/ui/UNITY_UI_WORKFLOW.mdfirst — all the rules there (hard-linked components,LoadPrefabContentsediting,UIMount.FillParent, driven root state, adaptive spine, MVC) apply to everything below. Web proto =game.js.
Status snapshot — ✅ ALL IMPLEMENTED (2026-07-03)¶
| Screen | View script | Controller | Notes |
|---|---|---|---|
| FullMapView | Map/FullMapView (+ LegendItemView) |
Map/MapController (extended) |
framed surface mount + on-spawn legend + crisp circle markers (both maps) |
| WinScreenView | Result/WinScreenView |
Result/ResultController (shared) |
biome-unlock block; sequential loot pop-in |
| LoseScreenView | Result/LoseScreenView |
Result/ResultController (shared) |
"Slain by X"; hide-empty panels |
| SettingsView | Settings/SettingsView |
Settings/SettingsController |
VolumeSliderView×2 + UIToggle; lobby btn_settings entry |
Leaf components built: Common/UIToggle, Settings/VolumeSliderView, Map/LegendItemView (+ the
Common/GameSettings audio/haptics seam). All prefabs wired 0 nulls; every screen compiles clean.
Data added for the result screens: RaidState.Kills (player-attributed) + RaidState.DeathCause.
Remaining validation: a play-test pass (open each screen in-game; the UIWiring tooling can still
re-verify). The sections below are the as-built reference.
A. Leaf components to build first¶
A1. VolumeSliderView (the flagged "fillable progress bar") — Settings Music/Sound¶
Prefab sub-tree (panel_music/panel_sound → progbar):
progbar
img_back (track background — full width)
img_fill (the filled portion; NOT a Filled-type Image — resize its width)
img_knob (draggable handle)
MonoBehaviour on progbar:
- Serialized refs: RectTransform track (img_back), RectTransform fill (img_fill),
RectTransform knob (img_knob).
- public float Value {get; } in 0..1; event Action<float> Changed;
- SetValue(float v): clamp 0..1; set fill width = track.width * v (anchor fill left,
drive sizeDelta.x or a left/right anchor), and knob.anchoredPosition.x = track.width * v
(minus half-knob). Do NOT use Image.type = Filled — the art is a separate fill sprite.
- Drag input: implement IDragHandler/IPointerDownHandler (or a small drag on the track);
map pointer-x within the track rect → value → SetValue → raise Changed.
- No Slider component, no runtime Find — pure serialized refs.
Reused twice (Music, Sound).
A2. SettingsToggleView — Settings Vibration¶
Prefab sub-tree (panel_vibration → btn_toggle):
btn_toggle
img_bar_body_3 (track)
img_toggle_on (on-state art / knob-right)
img_toggle_off (off-state art / knob-left)
GameObject onArt (img_toggle_on), GameObject offArt (img_toggle_off);
authored UIButton on btn_toggle (see workflow §5 button authoring).
- public bool On {get;}; event Action<bool> Toggled;
- SetOn(bool): onArt.SetActive(on), offArt.SetActive(!on).
- On UIButton.Clicked: flip On, apply, raise Toggled.
A3. LegendItemView — FullMapView legend row (MapLegendItem.prefab)¶
- One legend entry = a colour swatch
Image+ a labelTMP_Text. - Serialized refs:
Image swatch,TMP_Text label;Set(Color c, string text). - Used as a typed template cloned per legend entry (see the web
renderLegendlist).
A4. Reused (already exist)¶
CurrencyView— win/lose currency chips (blueprints + gold). Prefabs already containCurrencyView_Blueprints/CurrencyView_Currency— just wire +Set(icon,value).InventoryItem— win/lose loot grid cells (obtained / lost). Clone intogrid_itemsandRenderItem(icon,rarity,level,…)per item (likeInventoryView.RenderStash).
B. FullMapView (minimap tap → full tactical map)¶
Web proto: tapping the minimap calls openBigMap() (game.js:4693) → drawBigMap() +
renderLegend(); handleMapTap handles taps; closeBigMap() on tap/back. A one-time
"🗺 tap to open" hint sits on the minimap until save.mapEverOpened.
Unity today: Map/MapView.cs already renders BOTH the minimap and a full-screen tactical
map (a RawImage of the MapTextureBaker texture + circle markers + player dot; it builds its
surface procedurally). Map/MapController.cs owns the minimap (mounted into InGameView's
MinimapSlot). FullMapView.prefab is the authored Figma frame + legend; MapLegendItem.prefab
is the legend row.
To implement:
1. Decide integration (recommend): FullMapView.prefab provides the frame art + a
map_surface RectTransform slot + a legend container. At open, the controller mounts a
MapView map-surface (or reuses the existing tactical MapView) inside map_surface, feeds it
the same baked texture + markers the minimap uses, and populates legend by cloning
LegendItemView for each marker category (You / Rival / Threat / Guardian / Vault / Rare /
Epic / Chest / Safe Exit / Descent Portal — from the imported IngameMap legend + web
renderLegend).
2. FullMapView.cs (pure view): serialized map_surface, legend container + LegendItemView
template, an authored close UIButton (tap-to-close, like the settings scrim). Events:
CloseClicked; a MapTapped(Vector2 viewportPos) if tactical taps do anything.
3. Wire into MapController: expose the minimap as tappable (author a UIButton on the minimap
root or its frame) → OpenFullMap(); show FullMapView (mount under UIRoot via a
controller + UIMount.FillParent), freeze/keep the raid running per web (mode==="playing"),
set save.mapEverOpened. Close returns to the HUD.
4. Keep the "tap to open" minimap hint until first open (web drawMapHint/mapEverOpened).
Data source: MapTextureBaker (baked map texture), the live marker list the minimap already
builds (MapController marker feed), player world position.
C. WinScreenView / LoseScreenView (raid result)¶
Web proto: endRun(win, msg) → result overlay: resultTitle = "✦ Extracted! ✦" (win) /
"✗ You Fell" (lose); a loot list (chips) or "Extracted empty-handed."; a flavour line
("You descend deeper, haul in hand." on a progress extract). Death holds on a beat first.
Unity today: Tester/MetaHud.cs owns the placeholder Result card. Flow:
RaidController.EndRaid banks the run → MetaHud.OnRaidEnded(bool extracted, bool descended) →
AdvanceLadder → ShowResult. Death waits player.deathDelay (DeathBeat) then shows; extract
is instant. Replace the placeholder card with the two Figma views.
Prefab structure (both):
popup (adaptive VLG+CSF spine)
panel_header → txt_header ("Extracted!" / "Extraction Failed!")
panel_stats → content: txt_description ("You descend deeper" / "You have been slain")
+ panel_lvl_stat (time / kills / floor — the run summary)
panel_biome (WIN ONLY, toggleable) → "New Biome Unlocked" + txt_biomename
panel_loot → txt_header ("Obtained Items:" / "Lost Loot:")
[Lose also: txt_description "Your gear is safe, …"]
+ grid_items (InventoryItem clones) + row_currency (CurrencyView ×2)
btn_continue → "Continue"
To implement:
1. WinScreenView.cs + LoseScreenView.cs (or ONE ResultScreenView with a bool win — they
share ~90%; a shared view with the biome block toggled off for lose is DRY-est). Pure view:
serialized header/stats/biome/loot(grid template + currency)/continue + a Render(ResultVM).
2. Adaptive spine on popup (biome block toggles off for lose / when no new biome; loot grid +
currency populate dynamically) — same pattern as DebriefingWin.
3. ResultVM: title/body strings, run stats (time, kills, floor reached), biome-unlock name
(or none), obtained/lost loot list ({icon,rarity,level}), currency deltas (blueprints, gold).
Fill it in the controller from the raid-end state (RaidState, ExtractionService/RaidController
result, save deltas). DebriefingWin.prefab is the win-design reference already imported.
4. Wire into MetaHud (or a new ResultController): on OnRaidEnded, instantiate the right view
under UIRoot (UIMount.FillParent), Render(vm), btn_continue.Clicked → return to lobby
(ReturnToLobby) / next. Reuse the DeathBeat delay for lose.
5. Reuse CurrencyView (wire CurrencyView_Blueprints/_Currency) + InventoryItem (grid).
D. SettingsView (Unity/Figma-only)¶
No web counterpart. New meta screen. Opened from… (decide: a gear button on the lobby
TopPanel/NavBar — none authored yet; may need a settings button). Closed via BackgroundTapToClose.
Prefab structure:
Content
BackgroundTapToClose (authored UIButton) → txt_close "Tap To Close"
popup
panel_header → "Settings"
panel_sound → panel_settings → panel_music / panel_sound / panel_vibration
panel_music → panel_header(icon+"Music") + progbar → VolumeSliderView (A1)
panel_sound → panel_header(icon+"Sound") + progbar → VolumeSliderView (A1)
panel_vibration → panel_header(icon+"Vibration") + btn_toggle → SettingsToggleView (A2)
panel_buttons → grid_buttons: btn_tos / btn_pp / btn_support / btn_delete (author UIButtons)
panel_info → panel_version (txt "Version" + txt_version_number)
layout_user_id (txt "User ID" + txt_id + btn_copy)
To implement:
1. Build leaf components A1 (VolumeSliderView) + A2 (SettingsToggleView).
2. SettingsView.cs (pure view): serialized musicSlider/soundSlider (VolumeSliderView),
vibrationToggle (SettingsToggleView), 4 button UIButtons, version/id TMP_Text, copy
UIButton, close UIButton. Render(SettingsVM) + events (MusicChanged, SoundChanged,
VibrationToggled, TosClicked, PpClicked, SupportClicked, DeleteClicked, CopyIdClicked,
CloseClicked).
3. SettingsController: reads/writes new save fields — musicVol/soundVol (0..1),
vibration (bool) — on SaveData; pushes them into an audio/haptics seam (mirror the
existing UiFeedback no-op seam in Common/UIButton.cs: AudioSettings.Impl etc. — real
service later). Button actions: TOS/PP/Support → open URL (Application.OpenURL) or no-op
stub; Delete → confirm + clear save; Copy → GUIUtility.systemCopyBuffer = userId. Version =
Application.version; User ID = a save/device id.
4. Add a settings entry point (a gear UIButton somewhere on the lobby) — flag this: the
authored lobby has no settings button yet; needs Figma art or a placeholder.
E. Suggested order¶
- Leaf components A1
VolumeSliderView, A2SettingsToggleView, A3LegendItemView(small, testable in isolation). - SettingsView (self-contained; exercises A1/A2 + save fields + the audio seam).
- WinScreenView / LoseScreenView (shared
ResultScreenView; replacesMetaHud.ShowResult; reuses CurrencyView + InventoryItem + the adaptive spine). - FullMapView (most integration — reconcile the procedural
MapViewsurface with the authored frame + legend, and add the minimap-tap entry point).
Each screen: author any missing Button+UIButtons, wire every ref with the UIWire tooling,
verify UIWireVerify.AssertAllRefs == 0, edit prefabs via LoadPrefabContents only, and
play-test. Follow the per-view checklist in the workflow doc.
F. Open questions to resolve with the designer/owner¶
(Resolved — see §G.)
G. Owner decisions (2026-07-03) — build to these¶
Layering: Settings mounts on top of the Lobby; Results + FullMap mount on top of the in-raid HUD. (Nested under UIRoot with a sort order above the host screen.)
Settings:
- Slider (VolumeSliderView): interactive drag. When value == 0 → enable the icon's
img_disabled mark (the disabled overlay on the row's img_music/img_sound/img_vibration)
and tint the img_knob red; otherwise hide the mark + normal knob colour. Wire Music AND
Sound identically; both are exposed + functional as values, but there is no audio system
to drive yet (store the value; no-op seam).
- Toggle → make it a SHARED widget Common/UIToggle (sibling of UIButton, reusable later).
Both on/off visuals are authored (img_toggle_on/img_toggle_off) — swap them. Vibration uses it.
- 4 buttons (TOS/PP/Support/Delete): authored UIButtons with NO logic for now — leave inert.
- User ID: leave the value empty for now (wire later). Still serialize the ref.
- Version: read Application.version (current build version).
- Entry point: a settings button at the TOP-RIGHT of the Lobby (add to LobbyView; needs
an authored UIButton — may need placeholder art / a Figma gear icon).
Win/Lose (separate views + prefabs; shared CONTROLLER only):
- Display all data carried; disable/hide empty panels: no obtained/lost items → hide the
item grid; no currencies → disable the currency bars.
- Text — web-faithful: win → "You descend deeper…" (progress extract) / extract-banked wording;
lose → "slain by X" (the killer/cause). Match game.js endRun messages.
- Animate loot appearance: reveal collected/lost items one-by-one (fast sequential pop-in),
skippable on tap (tap → instantly show all).
- Do NOT combine the two views/prefabs; a shared controller (ResultController) drives both.
FullMap: - Legend behaves like the web: when an item/marker is present on the stage, show its legend row — computed on spawn (not realtime). - The map is framed inside the frame sprite (same as the minimap). - Circle sprite for map markers — and replace the minimap markers with the same circle (the current default markers are hard to read/distinguish because they're gradient).
Build order: leaf components (UIToggle shared, VolumeSliderView, LegendItemView) →
Settings → Win/Lose (shared controller) → FullMap (+ circle markers on both maps).