Skip to content

Hard-linked View Components Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Every runtime UI view references its child components and sprites through hard [SerializeField] inspector links assigned in the prefab — no F()/FindDeep/GetComponentInChildren/GetComponent/UIButton.Attach at runtime, no sprite loading from the project.

Architecture: Most views already declare the [SerializeField] fields but keep a by-name Bind() fallback; the work is (a) bake the links into prefabs, (b) delete the fallback + FindDeep. Two deeper strands: author Button+UIButton components into the prefabs so nothing calls UIButton.Attach at runtime, and serialize NavBarView's captured sprites/sizes.

Tech Stack: Unity UGUI, TMPro, Zenject (controllers only), C#. No unit-test framework — "tests" are editor verification scripts (null-ref assert) + a grep gate + compile + a play spot-check.

Global Constraints

  • All prefab writes go through PrefabUtility.LoadPrefabContents → mutate → SaveAsPrefabAssetUnloadPrefabContents. Never OpenPrefab (it auto-saves).
  • Do not touch the root Canvas / RectTransform driven state (anchors (0,0)/(0,0), pivot (0,0), scale (0,0,0)) — it was just repaired to match WinScreenView.
  • Child object names stay (they're just no longer used for lookup).
  • No behaviour/layout changes. Pure wiring refactor.
  • Real Python: C:\Users\rksy\AppData\Local\Programs\Python\Python312\python.exe; set PYTHONUTF8=1 for scripts that print.
  • Commit after each view converts cleanly (per-view revertability).
  • Field names already present in a script are kept as-is (they're the wiring keys).

Task 0: Shared editor tooling (wiring + verification)

Files: - Create: unity/raiders/Assets/Scripts/Editor/UIWiring/UIWireOneOff.cs (throwaway; deleted in the final task) - Create: unity/raiders/Assets/Scripts/Editor/UIWiring/UIWireVerify.cs (throwaway; deleted in the final task)

Interfaces: - Produces: UIWire.Set(GameObject prefabRoot, Component target, string field, Object value) helper that sets a SerializedProperty object-ref by field name; UIWireVerify.AssertAllRefs(prefabPath, componentType) that logs any null serialized object-ref.

  • [ ] Step 1: Write the wiring helper

UIWireOneOff.cs — a RunCommand-invocable static utility. Core helper:

// Set a serialized object-reference field on `target` (a component in a LoadPrefabContents tree).
static void Set(Component target, string field, Object value)
{
    var so = new SerializedObject(target);
    var p = so.FindProperty(field);
    if (p == null) { Debug.LogError($"no field '{field}' on {target.GetType().Name}"); return; }
    p.objectReferenceValue = value;
    so.ApplyModifiedPropertiesWithoutUndo();
}
// Array variant: sizes the array then assigns element refs.
static void SetArray(Component target, string field, Object[] values) { /* FindProperty; arraySize; GetArrayElementAtIndex(i).objectReferenceValue */ }
// FindDeep by name (reuse the existing depth-first search) to resolve children during the one-off pass.

Each per-view wiring pass: var root = PrefabUtility.LoadPrefabContents(path); var v = root.GetComponentInChildren<T>(true); Set(v, "field", FindDeep(root.transform,"child")?.GetComponent<TChild>()); ... PrefabUtility.SaveAsPrefabAsset(root, path); PrefabUtility.UnloadPrefabContents(root);

  • [ ] Step 2: Write the verifier

UIWireVerify.cs: AssertAllRefs(string prefabPath, System.Type compType) — LoadPrefabContents, get the component(s), iterate SerializedObject visible properties; for any SerializedPropertyType.ObjectReference (and array elements) whose objectReferenceValue == null, log "[UNWIRED] {prefab} {comp}.{path}". Returns count of nulls.

  • [ ] Step 3: Verify it compiles — run any Unity_RunCommand (fails if the editor assembly doesn't compile). Expected: compiles OK.

  • [ ] Step 4: Commit

git add unity/raiders/Assets/Scripts/Editor/UIWiring/
git commit -m "chore(ui): add one-off UI wiring + verify editor tooling"

Group A — verify-only (already hard-linked)

Task 1: Verify InGameView / TopPanelView / CurrencyView wiring

These three have no Bind/FindDeep/F() — they are already the target state. Only confirm every prefab link is assigned.

Files (prefabs): InGameView/InGameView.prefab, Shared/TopPanelView.prefab, Shared/CurrencyView.prefab (+ any prefab embedding TopPanel/Currency: LobbyView, InventoryView, InventoryEquipmentView).

  • [ ] Step 1: Run UIWireVerify.AssertAllRefs for InGameView, TopPanelView, CurrencyView on their prefabs. Expected: 0 unwired (a [UNWIRED] line means an authored link is missing — wire it via UIWireOneOff.Set using the field's // comment name, e.g. timerValue ← txt_value).
  • [ ] Step 2: Grep gate — InGameView.cs, TopPanelView.cs, CurrencyView.cs contain no FindDeep/GetComponentInChildren/F(. Expected: none.
  • [ ] Step 3: Commit (only if a wiring fix was needed): git commit -am "fix(ui): wire missing InGame/TopPanel/Currency links".

Group B — simple fallback removal (fields exist; delete Bind fallback + FindDeep)

For each: (1) run the one-off wiring pass to bake links from the field // comment names; (2) verify 0 unwired; (3) delete the if (x == null) x = FindDeep(...) fallback lines, the Bind() method (inline any non-lookup setup into Awake), the _bound flag, and the FindDeep/Img/Txt/Sub helpers; (4) grep gate; (5) compile; (6) commit.

Task 2: InventoryPassiveSkillView

Fields → children: description ← txt_description, icon ← img_icon. Sprites: none. Prefab: InventoryView/InventoryPassiveSkill.prefab. - [ ] Wire → verify 0 unwired → delete Bind/FindDeep, keep _normal = description.color capture in Awake (guard null) → grep gate → compile → commit.

Task 3: InventoryStatView

Fields → children: value ← txt_value, arrow ← img_arrow. Prefab: InventoryView/InventoryStat.prefab. - [ ] Wire → verify → delete Bind/FindDeep; move _normal = value.color into Awake → grep gate → compile → commit.

Task 4: StageIndicator

Fields → children: numberLabel ← txt_value. Prefabs: the three pip templates inside LobbyView.prefab (panel_done/panel_active/panel_notdone). Wire on each template instance. - [ ] Wire (three templates) → verify → delete the LobbyView.FindDeep fallback in Awake/SetNumber → grep gate → compile → commit.

Task 5: InventoryItemView

Fields → children: icon ← img_icon, frame ← img_frame, levelLabel ← txt_lvl, arrowUp ← img_arrow_up (GameObject), betterOutline ← img_framebetteritem (GameObject), back ← img_back. Sprites (already serialized, verify assigned): commonFrame/rareFrame/epicFrame/legendaryFrame. Prefab: InventoryView/InventoryItem.prefab. - [ ] Wire → verify (incl. the 4 frame sprites) → delete Bind/Img/FindDeep fallback → MakeClickable: see Task 9 (UIButton authoring) — until then keep UIButton.Attach but note it. Grep gate (allow UIButton for now) → compile → commit.


Group C — composite views (fallback removal + dynamic templates)

Task 6: InventoryItemCardView

Heaviest FindDeep user (14 sites). Fields to ADD (currently all via F()/Sub()): _outline ← img_cardoutline; badge: _badgeRoot ← panel_label, _badgeText ← txt_lable, _badgeArrow ← img_arrow; header: _headerItem ← (InventoryItemView under layout_header), _nameText ← txt_itemname, _powerText ← panel_power/txt_value, _powerArrow ← panel_power/img_arrow; stats array _stats[4] ← InventoryStat_Attack/Health/Crit/Speed; passives: _passivesRoot ← layout_passives, _passiveTemplate ← (typed InventoryPassiveSkillView, first child of layout_passives); actions: _actionsRoot ← layout_actions, _scrapCost/_levelCost ← (InventoryStatView[0..1] under layout_actions), buttons _scrap/_equip/_unequip/_lvlup ← btn_scrap/btn_equip/btn_unequip/btn_lvlup. - [ ] Convert each private field to [SerializeField]; type the passive template as InventoryPassiveSkillView and spawn via Instantiate(_passiveTemplate) (returns typed — drop GetComponent). Buttons: reference authored UIButton (Task 9). Wire prefab InventoryView/InventoryItemCard.prefab → verify → delete Bind/F/Sub/Stat/Btn → grep gate → compile → commit.

Task 7: InventoryView

Fields → children: topPanel ← (TopPanelView), navBar ← (NavBarView), cellPrefab (already serialized — verify), arrays _slots[8] ← layout_items_l[0..3] + layout_items_r[0..3] (as [SerializeField] InventoryItemView[] slots), _stats[4] ← InventoryStat_Attack/Health/Crit/Speed, _grid ← grid_items (as [SerializeField] RectTransform grid). Stash: Instantiate(cellPrefab) already returns typed. - [ ] Add serialized slots/stats/grid; wire prefab InventoryView/InventoryView.prefab (slots in Loadout.SlotOrder order) → verify → delete Bind/F/Stat/SlotCell lookup + GetComponentInChildren → grep gate → compile → commit.

Task 8: LobbyView & InventoryEquipmentView

LobbyView fields → children (mostly exist): topPanel/navBar (by-type → serialized), descendButton ← btn_raid, prevButton ← btn_left, nextButton ← btn_right, locationName ← txt_locationname, stageValue ← layout_stagenumber/txt_value, haulValue ← panel_haul/panel_value/txt_value, raidsValue ← panel_raids/txt_value, doneTemplate/activeTemplate/notdoneTemplate ← panel_done/active/notdone, add [SerializeField] Transform stageRow ← panel_stagenumbers. Stage pips: Instantiate(template) + typed StageIndicator ref via GetComponent is dropped by typing the template as StageIndicator OR keep go.GetComponent<StageIndicator>() on the clone (acceptable — clone of an authored prefab; but prefer typing templates as StageIndicator). InventoryEquipmentView fields: equippedCard ← InventoryItemCard_Equipped, selectedCard ← InventoryItemCard_Selected, topPanel (by-type→serialized), add closeButton ← btn_close (authored UIButton, Task 9). - [ ] Wire both prefabs → verify → delete Bind/FindDeep/Btn/Txt/F/GetComponentInChildren → grep gate → compile → commit.


Group D — eliminate runtime UIButton.Attach (author components in prefabs)

Task 9: Author Button+UIButton into clickable prefab nodes

UIButton.Attach adds Button+UIButton at runtime on: nav slots (btn_raid/btn_inventory), item cells (img_back), lobby buttons (btn_raid/btn_left/btn_right), lobby stage pips, card buttons (btn_scrap/btn_equip/btn_unequip/btn_lvlup), equipment btn_close, inventory slot/stash cells. - [ ] For each authored clickable node, in its prefab add a Button (transition None, targetGraphic = the node's Image) + a UIButton component; then reference the UIButton from the owning view's serialized field. Do this via LoadPrefabContents (add components with AddComponent, set targetGraphic, save). - [ ] Replace every UIButton.Attach(go, graphic) call-site with the serialized UIButton ref (fixed clickables) — the wiring binds Clicked. For dynamically-instantiated clickables (stash cells, stage pips): author the Button+UIButton on the template prefab so each clone already has it; get the typed UIButton from the clone via the template being typed (or a serialized field on the cell view exposing its UIButton). Keep UIButton.Attach only if a clone genuinely can't pre-author it (document why). - [ ] Verify → grep gate (UIButton.Attach gone from converted views) → compile → play spot-check each screen → commit.

Task 10: NavBarView (capture → serialized)

Serialize the two slots' pieces and the reference styles: add [SerializeField] for each Slot's rect/bg/label/icon (or a serialized Slot[2]), and activeSprite/inactiveSprite/activeSize/inactiveSize. - [ ] Add serialized fields for the captured sprites/sizes + the two slots' rect/bg/label/icon; wire prefab NavBarView.prefab → verify → delete Bind/Capture/FindDeep → grep gate → compile → play spot-check nav → commit.


Task 11: Final gate + remove tooling

  • [ ] Repo-wide grep: no FindDeep, GetComponentInChildren, UIButton.Attach, or F(/Sub( lookup helpers remain in Assets/Scripts/UI/. Fix any stragglers.
  • [ ] Run UIWireVerify.AssertAllRefs across all touched prefabs → 0 unwired.
  • [ ] Play-test lobby → inventory → equipment popup → in-raid HUD → nav switching. Expected: identical behaviour.
  • [ ] Delete Assets/Scripts/Editor/UIWiring/ (throwaway tooling) + its .meta.
  • [ ] Commit: git commit -am "refactor(ui): hard-link all view components; remove runtime lookups + UIButton.Attach".

Self-review notes

  • InGameView/TopPanelView/CurrencyView already compliant → Task 1 is verify-only.
  • Dynamic repeats (stash cells, stage pips, passive rows) keep a typed template ref + Instantiate(template) (returns typed) — no GetComponent; count stays runtime (unavoidable).
  • The heaviest strands are Task 9 (authoring Button+UIButton into prefabs) and Task 10 (NavBarView) — schedule extra care/verification there.
  • Every prefab edit uses LoadPrefabContents/SaveAsPrefabAsset; the root driven Canvas state is never touched.