Skip to content

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:

  1. Leaf / shared: Shared/CurrencyView, Inventory/InventoryItemView, Inventory/InventoryStatView, Inventory/InventoryPassiveSkillView, Nav/NavBarView, Shared/TopPanelView, Lobby/StageIndicator, Common/UIButton
  2. 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 or Sprite/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 with Object.Instantiate(_typedTemplate), which returns the typed component directly — no GetComponent. (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 Sprite fields.

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

  1. Add serialized fields (names chosen to match a clear convention).
  2. Run the one-off wiring pass on every prefab that hosts the component.
  3. Replace the runtime lookups with the serialized fields; delete Bind/F/Sub.
  4. 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 LoadPrefabContentsSaveAsPrefabAssetUnloadPrefabContents (never OpenPrefab, 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/MapView not in this pass unless they share a converted sub-view (then only the sub-view is touched).