Hard-linked view components — design¶
Goal¶
Every runtime UI view references its child components and sprites through hard
[SerializeField] inspector links, assigned in the prefab. Eliminate all runtime
discovery: no F(...) / FindDeep / GetComponentInChildren / GetComponent
lookups, no Resources.Load / AssetDatabase sprite loading. The point is that
opening a view prefab in the Inspector shows exactly which objects/sprites it uses
and how — nothing is resolved by name or type at runtime.
Scope (13 scripts, Assets/Scripts/UI/)¶
Done shared-first, one script at a time, each verified before the next:
- Leaf / shared:
Shared/CurrencyView,Inventory/InventoryItemView,Inventory/InventoryStatView,Inventory/InventoryPassiveSkillView,Nav/NavBarView,Shared/TopPanelView,Lobby/StageIndicator,Common/UIButton - Composite views:
Inventory/InventoryItemCardView,Inventory/InventoryEquipmentView,Inventory/InventoryView,Lobby/LobbyView,InGame/InGameView
Rules¶
- Replace each by-name / by-type lookup with a
[SerializeField] private T _field;(typed component orSprite/Image), assigned in the prefab. - Remove
Bind()/FindDeep/F(...)/Sub(...)machinery once fields are wired. - Fixed repeats (8 equip slots, 4 stat cells) → a serialized array of the typed sub-view, one element per authored child.
- Dynamic repeats (stash grid cells, passive rows, currency chips — count not
known at author time) → keep ONE serialized typed template/prefab ref, typed as
the sub-view component (e.g.
[SerializeField] InventoryPassiveSkillView _passiveTemplate;). Spawn withObject.Instantiate(_typedTemplate), which returns the typed component directly — noGetComponent. (A DI-owning controller may instead use_container.InstantiatePrefabForComponent<T>(), same effect.) The template prefab's own internals are hard-linked, so the repeating unit is fully wired; only the count is runtime. - Sprites currently pulled from the project become serialized
Spritefields.
Wiring mechanism (approved: one-off script, no helper left)¶
For each script: add the [SerializeField] fields, then a throwaway editor
script opens the prefab via PrefabUtility.LoadPrefabContents, resolves each
child by its current (soon-to-be-deleted) name using the same names F(...) used,
sets new SerializedObject(component).FindProperty("_field").objectReferenceValue,
ApplyModifiedPropertiesWithoutUndo(), and SaveAsPrefabAsset. Then the by-name
lookup code is deleted from the view. No auto-wire method remains in shipped code.
Order of operations per script¶
- Add serialized fields (names chosen to match a clear convention).
- Run the one-off wiring pass on every prefab that hosts the component.
- Replace the runtime lookups with the serialized fields; delete
Bind/F/Sub. - Verify (below). Only then move to the next script.
Verification (per script)¶
- Null-ref assert: an editor script loads each affected prefab and asserts every serialized object reference on the component is non-null (catches an unwired field).
- Grep gate: no
F(/FindDeep/GetComponentInChildren/Sub(remain in the converted script. - Compile clean.
- Play spot-check of the affected screen once the batch is done.
Safety / rollback¶
- All prefab writes go through
LoadPrefabContents→SaveAsPrefabAsset→UnloadPrefabContents(neverOpenPrefab, which auto-saves). - These prefabs were recently in a broken/inconsistent state and just repaired to
match
WinScreenView; do NOT touch the root Canvas/RectTransform driven state. - Git is the backup; commit after each view converts cleanly so a bad step is a one-view revert, not a 13-file revert.
Out of scope¶
- No behavior changes, no layout changes, no renaming of child objects (names stay; they're just no longer used for lookup).
WinScreenView/LoseScreenView/SettingsView/MapViewnot in this pass unless they share a converted sub-view (then only the sub-view is touched).