t1k:unity:tof:ui-create-prefab
| Field | Value |
|---|---|
| Module | tof |
| Version | 2.2.2 |
| Effort | low |
| Tools | — |
Keywords: anchor pivot, Background, BasePuzzlePopup, BasePuzzleScreen, Content, content children, field-driven, FlexibleLayoutGroupVer2, Foreground, HyperCasualRootUI, layout image, popup prefab, prefab variant, puzzleui, responsive ui, SafeArea, screen prefab, theonefeature, theonefeature ui, Timeline_Grp, tof, TOFUIs, ui prefab, unity mcp
How to invoke
Section titled “How to invoke”/t1k:unity:tof:ui-create-prefab[screen-name] [layout-image-path] (both optional)Create TheOneFeature UI Prefab (Screen / Popup)
Section titled “Create TheOneFeature UI Prefab (Screen / Popup)”Prefab-only authoring guide for TheOneFeature-scoped UI screens / popups. Scope is the .prefab asset: variant of base templates, hierarchy (Background / Content / Foreground / Timeline_Grp), SafeArea scope, anchor/pivot, layout groups, field-driven Content children, Addressables registration. Script side (View / Presenter / [ScreenInfo]) is OUT OF SCOPE — covered by other tof skills.
Mirror counterpart:
t1k:unity:ui:create-prefab(at.claude/skills/t1k-unity-ui-create-prefab/SKILL.md) — generic Unity UI prefabs outside TOF paths. When editing either skill, mirror the change.
Scope — When to Use This Skill vs. the Generic Skill
Section titled “Scope — When to Use This Skill vs. the Generic Skill”| Target prefab path | Use skill |
|---|---|
Packages/TheOneFeature* (anything under any TheOneFeature.* package) | this skill |
Assets/Prefabs/UIs/ (project-level TOF screen registry path) | this skill |
Anything else — Packages/com.gdk.<x>/, Packages/<consumer>/, Assets/Prefabs/<Feature>/UI/, project-side feature packages that do not use the TOF runtime | t1k:unity:ui:create-prefab |
If the target path does NOT match a TOF row above, STOP and switch to the generic skill — generic screens variant from a different base (UIBaseScreen.prefab / UIBasePopup.prefab in com.gdk.core), register in a different Addressables group, and parent to RootUI.prefab instead of HyperCasualRootUI.prefab.
Authoring Tool — Unity MCP (with documented limits)
Section titled “Authoring Tool — Unity MCP (with documented limits)”Default to the Unity MCP bridge (mcp__UnityMCP__manage_prefabs, mcp__UnityMCP__manage_gameobject, mcp__UnityMCP__manage_components, mcp__UnityMCP__manage_asset). See §10 MCP Tool Capability Matrix below for which actions actually work today — two operations require direct YAML edits as a fallback: (a) creating a prefab variant (MCP create_from_gameobject has no variant flag), and (b) adding an asset to an Addressables group (manage_asset has no addressables_add action). When you do fall back to YAML, follow the exact patterns in §1 (variant) and §7 (Addressables) — they pattern off existing working files in the project (HomeScreenView Variant.prefab, UIs.asset), not free-form.
See t1k:unity:base:mcp-skill for the MCP install/connect procedure and rules/unitymcp-the1studio-fork-only.md for the canonical install.
Decision Tree
Section titled “Decision Tree”| Intent | Path |
|---|---|
| ”New full screen” | Create variant of BasePuzzleScreen.prefab → §1 Create as Variant |
| ”New popup / modal” | Create variant of BasePuzzlePopup.prefab → §1 Create as Variant |
| ”Spawn children for each [SerializeField] on the View” | §9 Materialize Content Children |
| ”User provided a layout image” | §9.4 Layout image input |
| ”Add full-bleed background image” | Place under Background → §2 Layer scope |
| ”Add main UI / popup body” | Place under Content → §2 Layer scope |
| ”Add HUD overlay / toast” | Place under Foreground → §2 Layer scope |
| ”Hook open/close animation” | Bind to Timeline_Grp → §3 Timeline_Grp |
| ”Set anchors / pivot on a child” | §5 Anchor / pivot rules |
| ”Grid that reflows by aspect ratio” | §6 Layout groups |
| ”Register prefab in Addressables” | §7 Addressables registration |
| ”Which MCP actions actually work?” | §10 MCP tool capability matrix |
1. Create as Variant
Section titled “1. Create as Variant”Pick exactly one base — never start from an empty Canvas, never duplicate the base (variant so future base fixes propagate).
| Need | Base prefab | GUID source |
|---|---|---|
| Screen (full-page view) | Packages/TheOneFeature.PuzzleUI/Common/Prefabs/Base/BasePuzzleScreen.prefab | read <base>.meta for guid: |
| Popup / modal | Packages/TheOneFeature.PuzzleUI/Common/Prefabs/Base/BasePuzzlePopup.prefab | read <base>.meta for guid: |
Track A — MCP-preferred path
Section titled “Track A — MCP-preferred path”- Instantiate the base in a temp scene:
mcp__UnityMCP__manage_gameobject(action="create", name="<ScreenName>", prefab_path="<base-path>") - Attach the View MonoBehaviour:
mcp__UnityMCP__manage_gameobject(action="modify", target="<ScreenName>", search_method="by_name", components_to_add=["<FullyQualified.ViewType>"]) - Check capability for variant save: does
mcp__UnityMCP__manage_prefabs.create_from_gameobjectaccept avariantparameter? As of 2026-05-28: NO — the action’s schema has onlyunlink_if_instance, which produces a STANDALONE prefab, not a variant. Until upstreamThe1Studio/unity-mcpadds the flag, skip step 4 and go to Track B. - (Future, when MCP adds variant support)
mcp__UnityMCP__manage_prefabs(action="create_from_gameobject", target="<ScreenName>", prefab_path="<target>", variant=true).
Track B — Minimal-YAML variant (current reality)
Section titled “Track B — Minimal-YAML variant (current reality)”Track B is the mandatory fallback today. It writes a variant .prefab file directly that patterns the existing Assets/Prefabs/UIs/HomeScreenView Variant.prefab (a working 37-line minimal variant). It is NOT free-form YAML — it is a fixed template with three substitutions.
Inputs to gather before writing:
| Variable | How to derive |
|---|---|
<base-guid> | head -3 <base>.prefab.meta → guid: line |
<base-root-go-fileID> | See §1.5 lookup algorithm |
<screen-name> | The View class name (e.g. GameplayScreenView) |
<view-script-guid> | head -3 <ViewName>.cs.meta → guid: line |
<random-int64-A>, <random-int64-B> | Two distinct positive int64 anchor IDs (e.g. 5844738139906543051, 5844738139906543052) |
Template (write to <target-path>.prefab):
%YAML 1.1%TAG !u! tag:unity3d.com,2011:--- !u!1001 &<random-int64-A>PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: - target: {fileID: <base-root-go-fileID>, guid: <base-guid>, type: 3} propertyPath: m_Name value: <screen-name> objectReference: {fileID: 0} # 20 RectTransform overrides identical to HomeScreenView Variant.prefab # (pivot.x/y, anchorMin.x/y, anchorMax.x/y, sizeDelta.x/y, # localPosition.x/y/z, localRotation.w/x/y/z, anchoredPosition.x/y, # localEulerAnglesHint.x/y/z) — all set to defaults (pivot 0.5,0.5; # anchorMin 0,0; anchorMax 1,1; everything else 0 or identity) m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: <base-guid>, type: 3}After writing the file, attach the View script as a SEPARATE step (Track B + then re-instantiate + add component via Track A.2 + use mcp__UnityMCP__manage_prefabs.modify_contents to write back to the variant). Reason: embedding the m_AddedComponents block by hand requires synthetic fileIDs and a property block that is fragile; let MCP own it once the variant file exists.
Wiring follows in §9.3 Track A (direct YAML edit) — MCP cannot wire in-prefab object references today. Plan for that step when budgeting variant authoring time.
Refresh: call mcp__UnityMCP__refresh_unity(scope="assets", mode="force") so Unity imports the new asset and assigns it a GUID for §7 Addressables registration.
Validation: after refresh, the new .prefab must:
- start with
--- !u!1001 &<id>(PrefabInstance, NOT GameObject) - end with
m_SourcePrefab: ... guid: <base-guid>matching the chosen base - import without console errors
If the file fails import, copy HomeScreenView Variant.prefab verbatim, swap only the m_Name value, and re-test — that proves the GUID values are correct.
1.5 Resolving the base root-GO fileID
Section titled “1.5 Resolving the base root-GO fileID”The root-GO fileID is the GameObject fileID that the variant must reference for m_Name and root-RectTransform overrides. Two cases:
Case A — base is a standalone prefab (e.g. a non-variant base):
- The root GO fileID is the
&<id>anchor on the first!u!1 GameObjectblock in the prefab file. - Quick lookup:
grep -n -m1 '!u!1 &' <base>.prefab→ take the number after&.
Case B — base is itself a variant (e.g. BasePuzzleScreen is a variant of BaseUIScreen):
-
The root GO is NOT declared in the base file — it lives in the grandparent. But the base file references it through its own
m_Modificationsblock. -
Lookup: find the GO fileID that the base uses as a
target:for its root-level overrides (them_Nameoverride at the very end of the base’s modifications list is the canonical pointer).Terminal window grep -B1 -A3 'propertyPath: m_Name' <base>.prefab | tail -5# Reads as: target: {fileID: <ROOT-GO-FILEID>, guid: <grandparent-guid>, type: 3} -
The
<ROOT-GO-FILEID>and grandparentguidare what you write into the variant — NOT the base’s own GUID. Inheritance chains in Unity variants always reference the originating GameObject’s fileID, even thoughm_SourcePrefabpoints at the immediate parent.
If a base prefab does not contain an m_Name propertyPath modification (no root rename), find any target: line in its m_Modifications block — every override targets the same root GO fileID for root-level properties.
2. Layer Scope — Background / Content / Foreground
Section titled “2. Layer Scope — Background / Content / Foreground”The base prefab ships three sibling layer containers under the root. Place new GameObjects under the layer matching their visual role. SafeArea only wraps Content — the other two are full-bleed.
| Layer | SafeArea? | Render order | What goes here |
|---|---|---|---|
Background | NO | Bottom | Full-screen images, gradients, video backdrops, particle backgrounds. |
Content | YES | Middle | Main UI: popup body, panels, buttons, lists, HUD elements that must stay inside the safe area. |
Foreground | NO | Top | Overlays that float over Content AND extend to screen edges: toasts, FX, tutorial pointers. |
Rules:
- Never place full-bleed art under
Content— SafeArea will inset it and reveal letterboxing on notched devices. - Never place tap-targets / readable UI under
BackgroundorForeground— they can fall under the notch or the gesture bar and become unhittable. - Do NOT add a second SafeArea anywhere. The one on
Contentis the only SafeArea in the prefab. - Do NOT add a CanvasScaler or AspectRatioFitter to the variant root — CanvasScaler lives ONLY on
HyperCasualRootUI.
3. Timeline_Grp — Open / Close Animations
Section titled “3. Timeline_Grp — Open / Close Animations”The base ships Timeline_Grp with two PlayableDirectors (Tl_Appears, Tl_Disappears). Customize per-screen by overriding the bound Timeline assets on the variant — never edit the base’s Timeline assets directly. If no custom anim needed, leave it untouched. Never delete Timeline_Grp — the open/close transition silently no-ops without it.
Author per-screen Timeline assets under Packages/TheOneFeature.PuzzleUI/<Feature>/Timelines/<ScreenName>_Appear.playable and <ScreenName>_Disappear.playable.
4. Reuse Prefabs — DRY
Section titled “4. Reuse Prefabs — DRY”If the SAME widget appears twice or more anywhere in the project, it MUST be a prefab and instantiated as a prefab reference — never duplicated GameObjects.
Existing reusable widgets (compose with these first):
| Need | Prefab |
|---|---|
| Currency / coin counter | CurrencyView.prefab |
| Title bar | Title.prefab |
| Bottom tab bar | Feature/bottom_nav_bar_location.prefab |
| Reusable button | Feature/btn_feature.prefab |
| Coin visuals | Coins/prefab_coin*.prefab |
| Timer / clock | Timer.prefab, Clock.prefab |
| Animated number | TextCounting.prefab, NumberHolder.prefab |
| Lives indicator | LivesView.prefab |
| Error / retry state | ErrorRetryView.prefab |
| Loader spinner | Loader.prefab |
| Notification badge | ic_noti.prefab |
| Background gradient | BgGradient.prefab, BgTop.prefab |
If a needed widget is not in the catalog AND you’ll use it 2+ times, create a new prefab in Common/Prefabs/<Category>/ BEFORE adding the second instance.
5. Anchor / Pivot Rules
Section titled “5. Anchor / Pivot Rules”Core principle: the pivot lives on the same edge as the anchor. Stretch ONLY the axis that must flex with device size; fixed-axis elements use coincident min=max anchors.
| Element role | Anchor min → max | Pivot |
|---|---|---|
Layer container (Background / Content / Foreground) | (0,0) → (1,1) | (0.5, 0.5) |
| Header / title bar | (0,1) → (1,1) | (0.5, 1) |
| Footer / bottom nav | (0,0) → (1,0) | (0.5, 0) |
| Top-right HUD (coins, settings) | (1,1) → (1,1) | (1, 1) |
| Top-left HUD (back, menu) | (0,1) → (0,1) | (0, 1) |
| Bottom-right corner button | (1,0) → (1,0) | (1, 0) |
| Bottom-left corner button | (0,0) → (0,0) | (0, 0) |
| Centered popup body | (0.5,0.5) → (0.5,0.5) | (0.5, 0.5) |
| Horizontal stretched bar at y=Y | (0,Y) → (1,Y) | (0.5, Y) |
| Vertical stretched bar at x=X | (X,0) → (X,1) | (X, 0.5) |
6. Layout Groups
Section titled “6. Layout Groups”| Pattern | Component |
|---|---|
| Simple 1-axis horizontal list | Unity HorizontalLayoutGroup |
| Simple 1-axis vertical list | Unity VerticalLayoutGroup |
| Grid that must reflow rows×cols by aspect ratio | FlexibleLayoutGroupVer2 (Packages/TheOneFeature/UI/Utilities/UILayout/) preset BaseOnContent |
Never use Unity GridLayoutGroup for responsive grids — it fixes cell count, breaking on different aspect ratios.
7. Addressables Registration
Section titled “7. Addressables Registration”Required so ScreenManager.OpenScreen<T>() can resolve the prefab via the [ScreenInfo] key.
7.1 Detect the correct group asset (project-specific)
Section titled “7.1 Detect the correct group asset (project-specific)”The group name varies per project. Do NOT hardcode TOFUIs.asset — detect:
# Pick an existing screen variant in Assets/Prefabs/UIs/ that already loads at runtime,# read its .meta GUID, then grep the AddressableAssetsData groups:existing_guid=$(head -3 'Assets/Prefabs/UIs/HomeScreenView Variant.prefab.meta' | grep guid | awk '{print $2}')grep -l "$existing_guid" Assets/AddressableAssetsData/AssetGroups/*.asset# The matching file is the group asset for screens/popups in this project.In TheOneFeatureProject the result is UIs.asset. In other projects it may be TOFUIs.asset or <Project>UIs.asset.
7.2 Add entry via direct YAML edit
Section titled “7.2 Add entry via direct YAML edit”mcp__UnityMCP__manage_asset does NOT expose an addressables_add action today (§10). Edit the group asset .asset file directly:
-
Read the new prefab’s GUID from its
.meta. -
Open the group asset YAML. Entries are sorted alphabetically by GUID under
m_SerializeEntries. -
Find the insertion point by grepping for the first 1-2 hex chars of the new GUID. Insert in alphabetical order:
- m_GUID: <new-prefab-guid>m_Address: <ScreenName>m_ReadOnly: 0m_SerializedLabels: []FlaggedDuringContentUpdateRestriction: 0 -
The
m_AddressMUST equal the prefab basename without.prefaband match[ScreenInfo(nameof(MyScreenView))]on the script side.
7.3 Refresh
Section titled “7.3 Refresh”mcp__UnityMCP__refresh_unity(scope="assets") so Unity picks up the group asset change.
Cross-reference: the t1k:unity:tof:addressables skill covers full TOF Addressables group taxonomy.
8. Final Checklist (before declaring done)
Section titled “8. Final Checklist (before declaring done)”- Prefab is a variant of
BasePuzzleScreen.prefaborBasePuzzlePopup.prefab— file starts with!u!1001 &and ends withm_SourcePrefab: {fileID: 100100000, guid: <base-guid>} - Prefab name
<ScreenName>View.prefabmatches the future[ScreenInfo]key - Every
[SerializeField]GameObject/Component field on the View has a matching child underContent(orBackground/Foregroundif explicitly placed there per §9) - Every UI child has explicit
UnityEngine.RectTransformincomponents_to_add— never plainTransform(would warn under Canvas) - Every child’s RectTransform fields (anchor/pivot/sizeDelta/anchoredPos) were set via §9.2.5 post-create — verified by
get_hierarchyreadback or responsetransform.position - Every UI child was re-parented to
FullScreenLayout/Background/Foregroundviamodify_contents(target, parent)— NOT left at prefab root - Every such field is wired on the View component via direct YAML fileID edit per §9.3 Track A — no null refs except documented
<Widget>Prefabuser-assigns. Post-wire verification:manage_prefabs(action="get_info")MUST returnprefabType: "Variant",isVariant: true,parentPrefab: "<base>.prefab". IfprefabType: "Prefab"the variant link is gone — restore from git and re-author. - Full-bleed art placed under
Background(no SafeArea inset) - Main UI / popup body placed under
Content(SafeArea-respecting) - Overlays / toasts placed under
Foreground(no SafeArea inset) -
Timeline_Grpintact; per-screen Appear/Disappear Timelines overridden on the variant if customized - No second SafeArea added; no CanvasScaler on root; no AspectRatioFitter on root
- Every child’s pivot sits on the same edge as its anchor (§5)
- Any widget used 2+ times is referenced as a prefab instance, not duplicated
- Grids use
FlexibleLayoutGroupVer2, notGridLayoutGroup - Prefab added to the correct Addressables group with address = prefab basename
-
refresh_unitysucceeded with zero console errors - If layout image was provided: every text-on-button region has a nested
<Button>/LabelTMP child withm_textset + RT placement applied (§9.4.B) - If layout image was provided: every standalone text region has a
Txt<Name>TMP child underFullScreenLayout(orForeground) withm_textset + RT placement applied (§9.4.C) - No TMP child was spawned for text that ALREADY has a
[SerializeField]TMP field on the View — those are covered by the §9.4.A field-driven path - Every TMP child has
m_HorizontalAlignmentandm_VerticalAlignmentset per §9.4.E (not TMP’s left-top defaultm_HorizontalAlignment: 1/m_VerticalAlignment: 256)
9. Materialize Content Children from View Fields (MANDATORY)
Section titled “9. Materialize Content Children from View Fields (MANDATORY)”A skeleton variant with no children is incomplete — the Presenter’s OnViewReady will null-ref on this.View.BtnPause.onClick.AddListener(...) because BtnPause was never spawned. This section mandates field-driven child creation.
9.1 Inspect the View script
Section titled “9.1 Inspect the View script”Parse the View class for [SerializeField] and [field: SerializeField] declarations. Capture for each field:
- Field name (becomes child GameObject name)
- Field type (drives §9.2 widget table)
- Default value (for primitive fields — keep, no spawning)
Field-driven inspection is the INTERACTIVE-content driver (Buttons, Transforms, runtime-mutated widgets), but it is NOT the only driver. If a layout image is supplied, §9.4.B / §9.4.C ALSO spawn TMP decorations (text-on-button labels, standalone text badges) that have NO corresponding [SerializeField] field — purely visual content the Presenter never touches. Don’t suppress them just because the View doesn’t declare them.
9.2 Widget-type → child template mapping
Section titled “9.2 Widget-type → child template mapping”For each field, create a child GameObject using this table. ALWAYS include UnityEngine.RectTransform in components_to_add — never let MCP default to plain Transform, which is invalid under a Canvas. (Verified 2026-05-28: create_child without explicit components_to_add ships a plain Transform.)
| Field type | components_to_add (mandatory order) | Default placement (applied via §9.2.5) |
|---|---|---|
UnityEngine.UI.Button | ["UnityEngine.RectTransform", "UnityEngine.UI.Image", "UnityEngine.UI.Button"] | Top-right HUD: anchor (1,1)-(1,1), pivot (1,1), size 120×120, anchoredPos (-40,-40) |
UnityEngine.RectTransform / UnityEngine.Transform (container) | ["UnityEngine.RectTransform"] — for Transform fields too. If name contains Container/Group/Layout/Row/List, also add "UnityEngine.UI.HorizontalLayoutGroup" | Bottom-stretched bar: anchor (0,0)-(1,0), pivot (0.5,0), sizeDelta (0,200), anchoredPos (0,200). For full-bleed parent containers like FeatureHolder / DefaultParent: anchor (0,0)-(1,1), pivot (0.5,0.5), sizeDelta (0,0), anchoredPos (0,0). |
UnityEngine.UI.Image | ["UnityEngine.RectTransform", "UnityEngine.UI.Image"] | Centered: anchor (0.5,0.5)-(0.5,0.5), pivot (0.5,0.5), size 200×200 |
TMPro.TMP_Text / TextMeshProUGUI (field-driven path only) | ["UnityEngine.RectTransform", "TMPro.TextMeshProUGUI"] | Top: anchor (0,1)-(1,1), pivot (0.5,1), sizeDelta (0,80), anchoredPos (0,-40). Alignment defaults per §9.4.E (MANDATORY — never leave at TMP’s left-top default). For text-on-button OR standalone text NOT driven by a [SerializeField] field, see §9.4.B (nested Label) / §9.4.C (standalone Txt<Name>) — those are auto-spawned from the layout image without needing a field. |
UnityEngine.UI.Slider | ["UnityEngine.RectTransform", "UnityEngine.UI.Slider"] | Centered, size 300×40 |
UnityEngine.UI.ScrollRect | ["UnityEngine.RectTransform", "UnityEngine.UI.ScrollRect"] (then build Viewport + Content as separate create_child calls under it) | Stretch: anchor (0,0)-(1,1), pivot (0.5,0.5), offsets 20 |
<CustomWidget> where the type IS a MonoBehaviour AND a project prefab named <CustomWidget>.prefab exists | create_child with source_prefab_path: "<path/to/<CustomWidget>.prefab>" instead of components_to_add | Inherit from prefab; offset to default placement |
Field name ends in Prefab (e.g. BoosterButtonPrefab, ItemPrefab) — used by Presenter to spawn at runtime | Do NOT spawn a child. Document as user-assigns-in-Inspector. | n/a |
Primitive (string, int, float, bool, enum) | Skip — not a GameObject ref | n/a |
If a field has [Header("Background")] / [Header("Foreground")] attribute, override the parent from Content to that layer.
create_child’s parent_path argument is IGNORED (verified 2026-05-28) — every child lands at the prefab root regardless. Workaround: omit parent_path, then issue a separate modify_contents(target: "<ChildName>", parent: "<LayerName>") call to re-parent to FullScreenLayout (= Content), Background, or Foreground. See §10 for the exact pattern.
9.2.5 Apply RectTransform AFTER create_child (MANDATORY)
Section titled “9.2.5 Apply RectTransform AFTER create_child (MANDATORY)”create_child does NOT set anchor/pivot/sizeDelta/anchoredPosition — the new child is born with default-zeros and Unity’s “centered, 100×100” implicit defaults. Without this step, every child stacks at the screen center regardless of the §9.2 default or §9.4 image-derived placement.
For EACH child spawned in §9.2, issue a second modify_contents call:
mcp__UnityMCP__manage_prefabs( action="modify_contents", prefab_path="<target>.prefab", target="<ChildName>", # name lookup inside the prefab parent="<LayerName>", # one of: FullScreenLayout / Background / Foreground (omit to keep at root) component_properties={ "UnityEngine.RectTransform": { "m_AnchorMin.x": <num>, "m_AnchorMin.y": <num>, "m_AnchorMax.x": <num>, "m_AnchorMax.y": <num>, "m_Pivot.x": <num>, "m_Pivot.y": <num>, "m_AnchoredPosition.x": <num>, "m_AnchoredPosition.y": <num>, "m_SizeDelta.x": <num>, "m_SizeDelta.y": <num> } })Property name form is mandatory flat-axis — m_AnchorMin.x and m_AnchorMin.y as SEPARATE keys. The Vector2 object form m_AnchorMin: {x:0, y:0} returns Unsupported SerializedPropertyType: Vector2 (verified 2026-05-28). Same flat-axis rule for m_AnchorMax, m_Pivot, m_AnchoredPosition, m_SizeDelta.
Verification: after the call, the response includes transform.position reflecting anchoredPosition — quick sanity check. For deeper verify, call get_hierarchy and confirm the child path is <Root>/FullScreenLayout/<ChildName>.
9.3 Wire references on the View component (direct YAML — MCP gap workaround)
Section titled “9.3 Wire references on the View component (direct YAML — MCP gap workaround)”MCP modify_contents(component_properties) cannot wire in-prefab object references today (verified 2026-05-28 against The1Studio/unity-mcp@beta). The resolver at ComponentOps.cs:719 routes {"name":...} payloads through ResolveSceneObjectByName which hardcodes scene-only lookup (ComponentOps.cs:875); the loaded prefab root from ManagePrefabs.ModifyContents:617 is never forwarded into the resolver. GameObjectLookup only falls back to PrefabStageUtility.GetCurrentPrefabStage() when a prefab is OPEN in Prefab Mode (GameObjectLookup.cs:164,292) — an in-memory LoadPrefabContents is invisible to that fallback. All three payload forms (name / instanceID / path) return scene-scoped errors on the prefab-asset path.
Use the direct YAML edit path below. It preserves the variant link (no Prefab-Mode-collapse risk) and works against any MCP version.
Procedure (Track A — direct YAML, MANDATORY today)
Section titled “Procedure (Track A — direct YAML, MANDATORY today)”-
Find each child component’s fileID. After §9.2 + §9.2.5 saved the prefab, parse the file with this awk script to map
m_Name→ component fileIDs:Terminal window awk '/^--- !u!1 &/ { cur_name="" }/^ m_Name: / { if (!cur_name) cur_name=$2; print "GO " cur_name }/^--- !u!114 &/ { comp=$3; sub("&","",comp); print " MB " comp " under " cur_name }/^--- !u!224 &/ { comp=$3; sub("&","",comp); print " RT " comp " under " cur_name }' <prefab.prefab>Component type discrimination (when a child has multiple MonoBehaviours, e.g. Image + Button on a button): read 8 lines past the
!u!114 &<fileID>anchor and matchm_EditorClassIdentifier:— Button =UnityEngine.UI::UnityEngine.UI.Button, Image =UnityEngine.UI::UnityEngine.UI.Image, TMP =Unity.TextMeshPro::TMPro.TextMeshProUGUI. For Transform-typed View fields, use the!u!224RectTransform fileID directly. -
Locate the View MonoBehaviour block. After §9.2’s
components_to_add: ["<View>"]call, the View MB is appended near the end of the file as:--- !u!114 &<view-mb-fileID>MonoBehaviour:...m_Script: {fileID: 11500000, guid: <ViewScript.cs.meta guid>, type: 3}m_EditorClassIdentifier: <Namespace>::<FullName><field1>: {fileID: 0}<field2>: {fileID: 0}...Every
[SerializeField]GameObject/Component field starts as{fileID: 0}. -
Replace each
{fileID: 0}with the matching component fileID viaEdittool. Map per the §9.2 widget-type column:Field type Wire to UnityEngine.UI.Buttonthe MB whose m_EditorClassIdentifierends inUnityEngine.UI.Button(typically the 2nd MB after Image, per step 1’s identifier check)UnityEngine.UI.Imagethe MB whose m_EditorClassIdentifierends inUnityEngine.UI.Image(typically the 1st MB)TMPro.TMP_Text/TextMeshProUGUITMP MB fileID UnityEngine.RectTransform/UnityEngine.TransformRectTransform ( !u!224) fileID<CustomWidget>MonoBehaviourthe corresponding !u!114fileID on the child -
Refresh + verify. Call
mcp__UnityMCP__refresh_unity(scope="assets", mode="force"), thenmcp__UnityMCP__manage_prefabs(action="get_info", prefab_path=...). Required readback:prefabType: "Variant"ANDisVariant: trueANDparentPrefab: "<base>.prefab". IfprefabType: "Prefab"(standalone), the variant link is gone — restore from git and re-author per §1. -
Read console for errors.
mcp__UnityMCP__read_console(types=["error"])— must be 0 errors mentioning the View type orMissingReferenceException.
Track B — Prefab Mode opt-in (OPT-IN, collapses Track-B variants)
Section titled “Track B — Prefab Mode opt-in (OPT-IN, collapses Track-B variants)”If you ACCEPT losing the variant link (becomes a standalone prefab — same Prefab-Mode-save-collapse failure mode as the existing Gotcha), you can drive Prefab Mode through MCP:
- Open the prefab in Prefab Mode (currently no headless MCP action exists —
manage_assethas noopenaction;execute_menu_item("Assets/Open Prefab Asset")requires Project-window selection which MCP cannot set). Practical reality: user must double-click the prefab in the Unity Editor to enter Prefab Mode. - While the stage is open:
mcp__UnityMCP__manage_components(action="set_property", target="<RootName>", search_method="by_name", component_type="<ViewType>", properties={...})— works becauseGameObjectLookup.SearchGameObjectsfalls back toPrefabStageUtility.GetCurrentPrefabStage()(verifiedGameObjectLookup.cs:164). mcp__UnityMCP__manage_editor(action="close_prefab_stage")to save + exit.- Verify the prefab is NOT a variant anymore:
get_infowill returnprefabType: "Prefab". Document the collapse in the commit message.
Use Track A unless you have a deliberate reason to abandon variant inheritance.
Why this gap exists
Section titled “Why this gap exists”Upstream root cause (file the gap via /t1k:issue against The1Studio/unity-mcp@beta if it doesn’t already exist):
ComponentOps.SetObjectReference:719→ callsResolveSceneObjectByName(prop, name, ...)ResolveSceneObjectByName:861-887→GameObjectLookup.SearchGameObjects(ByName, ...)→ returns"No GameObject named 'X' found in scene."when no scene match (line 875)ManagePrefabs.ModifyContents:617→PrefabUtility.LoadPrefabContents(path)→ does NOT enter Prefab Stage; the loaded root is never passed intoComponentOps- Proposed fix: overload
SetObjectReference/ResolveSceneObjectByNameto accept optionalGameObject prefabRoot. When set, searchprefabRoot.GetComponentsInChildren<Transform>(true)instead of the active scene.
9.4 Layout image input (optional)
Section titled “9.4 Layout image input (optional)”If the user provided a layout image path (PNG/JPG/sketch) as a skill argument:
- Read the image with the
Readtool — Claude’s multimodal vision handles PNG/JPG natively. - Identify regions in the image:
- Interactive widgets — buttons, sliders, scroll lists, HUD counters (drive §9.4.A field-driven mapping).
- Text-inside-button labels — text rendered ON TOP of a button region (drive §9.4.B nested-label spawning).
- Standalone text decorations — text NOT inside any button (titles, badges, subtitles, the “Normal” caption above a “Level 999” pill, etc.) (drive §9.4.C standalone TMP spawning).
§9.4.A — Map field-driven regions (interactive widgets)
Section titled “§9.4.A — Map field-driven regions (interactive widgets)”For each [SerializeField] field on the View, map its name to the closest visual element in the image:
BtnPause→ small button-shaped region; prefer pause-icon (||) if visible.BoosterContainer→ horizontal row of similar-sized cells, typically near the bottom.CoinView/CurrencyView→ top-right counter widget.- When the field type is
RectTransformand image shows a grid of items, mark asFlexibleLayoutGroupVer2.
Override the §9.2 default placement table with image-derived anchors / pivots / sizes. Conversion formulas (image is Y-down, Unity anchor space is Y-up):
anchor.x = image_px_x / image_widthanchor.y = 1 - (image_px_y / image_height)pivot.{x,y}MUST match anchor edge per §5 (top-left corner → pivot (0,1); bottom-center → pivot (0.5,0); etc.)sizeDelta= approximate element width/height in pixels at the screen’s reference resolution fromHyperCasualRootUI’s CanvasScaler (commonly 1080×1920 for portrait mobile).anchoredPosition= offset from anchor point to pivot, in reference-resolution px.
Applied via §9.2.5 (these REPLACE the §9.2 defaults in the second modify_contents call). There is no separate “merge” step.
For any TMP field-driven child, ALSO apply TMP alignment per §9.4.E — default H=2 (Center) / V=512 (Middle) unless the image clearly indicates otherwise.
§9.4.B — Auto-spawn nested TMP labels for text-on-button
Section titled “§9.4.B — Auto-spawn nested TMP labels for text-on-button”When the image shows text rendered ON TOP of a field-driven Button child (e.g. “Level 999” inside BtnPlay, “Levels” inside BtnLevelSelect):
-
Skip if the View already declares a TMP field for the label (e.g.
[SerializeField] TMP_Text btnPlayLabel). The §9.4.A field-driven path already covers that case — do NOT double-spawn. -
Create a NESTED child under the parent Button GO via two-step pattern (create + re-parent, per §9.2.5 + §10):
mcp__UnityMCP__manage_prefabs(action="modify_contents",prefab_path="<target>.prefab",create_child=[{"name": "Label", "components_to_add": ["UnityEngine.RectTransform", "TMPro.TextMeshProUGUI"]}])# then re-parent to the button:mcp__UnityMCP__manage_prefabs(action="modify_contents",prefab_path="<target>.prefab",target="Label",parent="BtnPlay")Canonical name:
Label. If the button shows multiple text regions, suffix:LabelTitle,LabelValue, etc. -
Apply RectTransform placement via §9.2.5. Default for centered text inside a button: anchor (0,0)-(1,1), pivot (0.5,0.5), sizeDelta (0,0), anchoredPos (0,0) — stretches to fill the button. For top-aligned subtitle (e.g. badge above a value): anchor (0,1)-(1,1), pivot (0.5,1), sizeDelta (0,40), anchoredPos (0,-8). Image-derived footprint overrides these defaults.
-
Apply text content + alignment via
modify_contents— batch text, fontSize, AND alignment in a single call per §9.4.E:mcp__UnityMCP__manage_prefabs(action="modify_contents",prefab_path="<target>.prefab",target="Label",component_properties={"TMPro.TextMeshProUGUI": {"m_text": "Level 999","m_fontSize": 48,"m_HorizontalAlignment": 2,"m_VerticalAlignment": 512}})The serialized field name is
m_text(verified — seeCommon/Prefabs/titlePopup1.prefabfor a wired example). Do NOT usetext(public property) orm_Text(capitalised — wrong). Default alignment for nested text-on-button labels is H=2 (Center) / V=512 (Middle) — see §9.4.E for enum values and image-derived overrides. -
Naming collision guard: if
<Button>/Labelalready exists (variant re-author case), SKIP spawn — preserve Inspector hand-edits.
§9.4.C — Auto-spawn standalone TMP children for non-button text
Section titled “§9.4.C — Auto-spawn standalone TMP children for non-button text”When the image shows text NOT inside any field-driven Button region (titles, badges, captions like the “Normal” label above a “Level 999” pill):
-
Skip if the View already declares a TMP field for it (same dedup rule as §9.4.B).
-
Create a child under
FullScreenLayout(Content) — same two-step create + re-parent pattern. Parent isFullScreenLayoutby default; useForegroundif the image shows the text floats above other UI (toast-style overlay).Naming:
Txt<CamelCaseFromText>(e.g. text “Normal” →TxtNormal; “Level Complete!” →TxtLevelComplete). If text exceeds 20 chars, truncate to a representative slug (e.g.TxtDescription). -
Apply RectTransform placement per the image-derived footprint via §9.2.5 (same rules as §9.4.A).
-
Apply text content + alignment via the same
m_text+m_fontSize+ alignment batchedmodify_contentscall as §9.4.B step 4. Alignment for standalone TMP is image-derived (not always Center+Middle) — pick H/V from the §9.4.E lookup table based on where the text sits within its parent bounding box in the layout image (e.g. caption above pill = H=2/V=256 (Top); centered title = H=2/V=512 (Middle); footer subtitle = H=2/V=1024 (Bottom)). -
Default font size: derive from image text height relative to reference resolution. Heuristic:
m_fontSize = round(image_text_pixel_height * (reference_width / image_width))wherereference_widthcomes fromHyperCasualRootUI’s CanvasScaler (typically 1080 for portrait mobile). Typical values 36-72. User can adjust in Inspector if needed. -
Naming collision guard: same as §9.4.B step 5.
§9.4.D — Echo the full inferred layout (MANDATORY before any apply)
Section titled “§9.4.D — Echo the full inferred layout (MANDATORY before any apply)”Before applying any of §9.4.A/B/C, echo the COMPLETE detected layout back to the user — interactive widgets AND text decorations — as a single confirmation block:
“From the image I see:
- Field-driven (§9.4.A):
BtnProfiletop-left HUD (~70px from edges)BtnSettingtop-right HUD (~70px from edges)BtnPlaybottom-center pill (~500×200, y=140)BtnLevelSelectbottom-left square (~160×160, y=160)FeatureHolder,DefaultParentfull-bleed containers- Text-on-button labels (§9.4.B):
BtnPlay/Labeltext “Level 999” centered, stretchedBtnLevelSelect/Labeltext “Levels” bottom-aligned- Standalone text (§9.4.C):
TxtNormaltext “Normal” above BtnPlay (~y=180 from bottom)Apply? (yes / adjust)”
Wait for user confirmation. If user adjusts, capture the adjustments and re-apply.
If no image is provided, only §9.4.A defaults apply via §9.2.5 with no echo step. §9.4.B/C are no-ops without an image source.
§9.4.E — Apply TMP alignment (MANDATORY)
Section titled “§9.4.E — Apply TMP alignment (MANDATORY)”Every TMP child spawned by §9.2 (widget-table TMP row), §9.4.A (field-driven), §9.4.B (nested label), or §9.4.C (standalone) MUST have explicit m_HorizontalAlignment and m_VerticalAlignment set. TMP children spawn with left-top defaults (m_HorizontalAlignment: 1, m_VerticalAlignment: 256) that almost never match the layout image — text-on-button labels then render upper-left of the button rect instead of centered.
Enum values (serialized — write these integers directly into component_properties):
| Property | Value | Meaning |
|---|---|---|
m_HorizontalAlignment | 1 | Left |
m_HorizontalAlignment | 2 | Center |
m_HorizontalAlignment | 4 | Right |
m_HorizontalAlignment | 8 | Justified |
m_HorizontalAlignment | 16 | Flush |
m_HorizontalAlignment | 32 | Geometry Center |
m_VerticalAlignment | 256 | Top |
m_VerticalAlignment | 512 | Middle |
m_VerticalAlignment | 1024 | Bottom |
m_VerticalAlignment | 2048 | Baseline |
m_VerticalAlignment | 4096 | Midline |
m_VerticalAlignment | 8192 | Capline |
Defaults by section:
| Source | Default H | Default V | Override rule |
|---|---|---|---|
| §9.2 widget-table TMP row | 2 (Center) | 512 (Middle) | Image-derived per §9.4.A if image provided |
| §9.4.A field-driven | 2 (Center) | 512 (Middle) | Override only if image clearly indicates otherwise |
| §9.4.B nested label (text-on-button) | 2 (Center) | 512 (Middle) | Hard default — text-on-button is almost always center+middle |
| §9.4.C standalone TMP | image-derived (see lookup below) | image-derived | Always image-derived; no fixed default |
§9.4.C image-derived lookup heuristic:
Locate the text region within its parent bounding box in the layout image. Pick H/V from this table:
| Image position within parent | H | V |
|---|---|---|
| Caption ABOVE a pill / panel (top of parent) | 2 (Center) | 256 (Top) |
| Centered title / banner (middle of parent) | 2 (Center) | 512 (Middle) |
| Footer subtitle / fine print (bottom of parent) | 2 (Center) | 1024 (Bottom) |
| Left-aligned header / section title | 1 (Left) | 256 (Top) |
| Right-aligned counter / badge value | 4 (Right) | 512 (Middle) |
| Inline numeric value flush against right edge | 4 (Right) | 512 (Middle) |
| Bottom-left corner stamp / timestamp | 1 (Left) | 1024 (Bottom) |
| Bottom-right corner stamp / count | 4 (Right) | 1024 (Bottom) |
For diagonal/intermediate positions not in the table, snap to the nearest row.
Exact MCP call pattern (batched — alignment goes with text + fontSize in a single modify_contents):
mcp__UnityMCP__manage_prefabs( action="modify_contents", prefab_path="<target>.prefab", target="<TmpChildName>", component_properties={ "TMPro.TextMeshProUGUI": { "m_text": "<text from image>", "m_fontSize": <derived size>, "m_HorizontalAlignment": <enum int from table above>, "m_VerticalAlignment": <enum int from table above> } })Do NOT set m_textAlignment — that is the LEGACY combined enum (TMP serializes it as 65535 by default and ignores it once m_HorizontalAlignment / m_VerticalAlignment are set). Leave m_textAlignment untouched.
9.5 Checklist gates (already in §8)
Section titled “9.5 Checklist gates (already in §8)”Gates relevant to §9 (see §8 for the full list):
- Every
[SerializeField]GameObject/Component field has a matching child - Every UI child has explicit
UnityEngine.RectTransformincomponents_to_add(never plainTransform) - Every child’s RectTransform (anchor/pivot/sizeDelta/anchoredPos) was set via §9.2.5 — verified via
get_hierarchyreadback or responsetransform.position - Every UI child was re-parented to
FullScreenLayout/Background/Foregroundvia §9.2.5’sparent:arg (not at prefab root) - Every such field is wired on the View component via direct YAML fileID edit per §9.3 Track A — no null refs except documented
<Widget>Prefabuser-assigns; post-wireget_infoconfirmsisVariant: true
10. MCP Tool Capability Matrix
Section titled “10. MCP Tool Capability Matrix”Quick-reference status as of 2026-05-28 against The1Studio/unity-mcp@beta. Re-test before assuming a row is still accurate.
| Operation | MCP call | Status | Notes |
|---|---|---|---|
| Instantiate prefab in scene | manage_gameobject(action="create", prefab_path=…) | ✅ Works | Use this to bring base into temp scene |
| Add component to instance | manage_gameobject(action="modify", components_to_add=[…]) | ✅ Works | Add View MonoBehaviour here |
| Save GameObject as standalone prefab | manage_prefabs(action="create_from_gameobject", unlink_if_instance=true) | ✅ Works | Loses variant link — not what §1 wants |
| Save GameObject as prefab VARIANT | manage_prefabs(action="create_from_gameobject", variant=true) | ❌ Schema lacks variant param | Use §1 Track B (minimal-YAML fallback) |
| Read prefab hierarchy | manage_prefabs(action="get_hierarchy", prefab_path=…) | ⚠️ Assets/ only | Errors on Packages/… paths |
| Modify prefab contents — create child | manage_prefabs(action="modify_contents", create_child=[…]) | ✅ Works | Default GO has plain Transform. ALWAYS pass components_to_add: ["UnityEngine.RectTransform", …] for UI children. |
create_child’s parent_path arg | create_child=[{name, parent_path, …}] | ❌ IGNORED | Children land at prefab root regardless. Workaround: separate modify_contents(target, parent) call (next row). |
| Re-parent existing child | modify_contents(target="<ChildName>", parent="<LayerName>") | ✅ Works | Name lookup works; parent is a sibling/layer name. Use to move under FullScreenLayout / Background / Foreground. |
| Set RectTransform anchor/pivot/size | modify_contents(component_properties={"UnityEngine.RectTransform": {"m_AnchorMin.x": …, …}}) | ✅ Works with flat-axis form | Vector2 form (m_AnchorMin: {x,y}) returns Unsupported SerializedPropertyType. Use m_AnchorMin.x + m_AnchorMin.y as separate keys. |
| Upgrade plain Transform → RectTransform | modify_contents(target, components_to_add=["UnityEngine.RectTransform"]) | ✅ Works | Replaces the Transform in-place (RectTransform inherits). Safe on FeatureHolder/DefaultParent. |
| Wire in-prefab object reference (headless) | `modify_contents(component_properties={ViewType: {field: {“name | instanceID | path”: …, “component”: …}}})` |
| Wire in-prefab object reference (Prefab Mode) | manage_components(action="set_property", search_method="by_name", target="<Root>", component_type="<View>", properties={...}) while Prefab Mode is open | ⚠️ Works but COLLAPSES Track-B variants | GameObjectLookup falls back to PrefabStageUtility.GetCurrentPrefabStage() (GameObjectLookup.cs:164). Save-on-close rewrites the file as a standalone (!u!1 not !u!1001). Use only if you accept losing the variant link. See §9.3 Track B. |
| Set component property (top-level tool) | manage_components(action="set_property") | ⚠️ Scene-only + tool deferred | Only resolves live scene objects, not prefab-asset internals. For prefab assets use modify_contents. |
| Add asset to Addressables group | manage_asset(action="addressables_add") | ❌ Not in schema | Edit the *.asset YAML directly (§7.2) |
| Force re-import | refresh_unity(scope="assets", mode="force") | ✅ Works | Call after any direct-YAML edit |
| Read console for errors | read_console(types=["error"]) | ✅ Works | Filter by message text via filter_text |
| Find GameObjects in scene | find_gameobjects(search_term=…) | ✅ Works | Use for cleanup-after-instantiate |
| Search assets by name | manage_asset(action="search", search_pattern=…) | ⚠️ Inconsistent | Sometimes returns 0 for clearly-existing assets; fall back to Bash find for filesystem-level lookup |
When a row marked ❌ blocks progress: file a [t1k:mcp-gap] marker per rules/workflow-failure-auto-issue.md against The1Studio/unity-mcp.
Authoring of .prefab assets under Packages/TheOneFeature.PuzzleUI/, Packages/TheOneFeature/UI/, and Assets/Prefabs/UIs/. Does NOT cover: View/Presenter C# scripts, [ScreenInfo] attribute wiring, VContainer registration, or runtime open/close calls — those are handled by the implementation skills.
Gotchas
Section titled “Gotchas”- Layout-image text decorations are auto-spawned even without a
[SerializeField]field. Text inside a field-driven button (e.g. “Level 999” insideBtnPlay) becomes a nested<Button>/LabelTMP child (§9.4.B); standalone text (e.g. “Normal” caption above a pill) becomes aTxt<Name>child underFullScreenLayout(§9.4.C). Both get full RT placement per §9.2.5 +m_textcontent from the image. They are NOT wired to the View script — the Presenter never touches them, they are purely visual. User can later promote to[SerializeField]if interactivity is added. Without this, image-driven UIs ship with empty buttons and missing labels (failure mode observed 2026-05-28 on HomeScreenView authoring run). - TMP serialized text property is
m_text, NOTtextorm_Text. Verified againstPackages/TheOneFeature.PuzzleUI/Common/Prefabs/titlePopup1.prefab. Thetextform is the runtime C# property (TMP_Text.text); them_Textform (capitalised M) is wrong. MCPmodify_contents(component_properties={"TMPro.TextMeshProUGUI": {"m_text": "<value>"}})is the only working form. - TMP children spawn with left-top alignment by default. New
TMPro.TextMeshProUGUIcomponents serialize withm_HorizontalAlignment: 1(Left) andm_VerticalAlignment: 256(Top). Without §9.4.E setting H=2 (Center) / V=512 (Middle), text-on-button labels like “Level 999” or “Levels” render in the upper-left of the button rect instead of visually centered as the layout image shows. Observed 2026-05-28 on HomeScreenView authoring (LabelPlay,LabelLevelSelectmisaligned). The alignment property names arem_HorizontalAlignment/m_VerticalAlignment— NOTm_textAlignment(that field is the legacy combined enum, serialized as65535by default; leave it at default and set the two split-axis fields instead). Enum values + image-derived heuristics: §9.4.E. - Headless field-wiring via
modify_contents(component_properties)does NOT work for in-prefab object references. Verified 2026-05-28: ALL three payload forms —{"name":...},{"instanceID":...},{"path":...}— fail with scene-scoped errors when targeting children inside a prefab ASSET (not a Prefab Stage). Root cause:ComponentOps.SetObjectReference:719routes name lookups throughResolveSceneObjectByNamewhich hardcodes scene search atComponentOps.cs:875; the prefab root loaded byManagePrefabs.ModifyContents:617viaPrefabUtility.LoadPrefabContentsis never forwarded into the resolver.GameObjectLookuponly falls back toPrefabStageUtility.GetCurrentPrefabStage()when a prefab is OPEN in Prefab Mode (GameObjectLookup.cs:164,292) — an in-memory load is invisible to that fallback. Workaround: direct YAML edit of<field>: {fileID: <ComponentFileID>}on the View’s MonoBehaviour block (§9.3 Track A). Preserves variant link, works against any MCP version. Track B (Prefab Mode +manage_components.set_property) does work but collapses Track-B variants to standalone — use only if you accept losing the variant link. create_childignoresparent_path. Verified 2026-05-28: regardless of the value passed, the new child lands at the prefab root. Always follow with a separatemodify_contents(target: "<ChildName>", parent: "<LayerName>")call to re-parent. The skill checklist gates this.create_childdefaults to plainTransform. Without explicitcomponents_to_add, the new GameObject ships withUnityEngine.Transform— invalid under a Canvas (Unity warns + breaks layout). ALWAYS passcomponents_to_add: ["UnityEngine.RectTransform", …]for UI children (§9.2). If you forgot, recover viamodify_contents(target, components_to_add: ["UnityEngine.RectTransform"])— this replaces the Transform in-place because RectTransform inherits.- RectTransform Vector2 properties need flat-axis form. The MCP serializer rejects
m_AnchorMin: {x: 0, y: 1}withUnsupported SerializedPropertyType: Vector2. Use separate keysm_AnchorMin.xandm_AnchorMin.y. Same form_AnchorMax,m_Pivot,m_AnchoredPosition,m_SizeDelta. - Children without §9.2.5 position step stack at center.
create_childdoes NOT set anchor/pivot/sizeDelta/anchoredPosition — the new GO uses Unity’s implicit “centered 100×100” defaults regardless of the §9.2 default placement column. The two-step pattern (create_childthenmodify_contents(component_properties.RectTransform)) is MANDATORY, not advisory. create_from_gameobjectproduces a STANDALONE prefab, not a variant. The MCP tool’s schema has novariantflag (verified 2026-05-28). Use §1 Track B minimal-YAML fallback until upstream The1Studio/unity-mcp adds the parameter. Symptom of the silent failure: resulting.prefabstarts with--- !u!1 &<id>\nGameObject:instead of--- !u!1001 &<id>\nPrefabInstance:.- Prefab-Mode save collapses a Track B variant to standalone. Verified 2026-05-28: if you instantiate the Track B variant in scene Prefab Mode, add structural changes (new children), then
close_prefab_stage(or any Unity-side save), Unity rewrites the file as a fully-fleshed!u!1 GameObjectprefab — them_SourcePrefablink is gone. To avoid: skip Prefab Mode entirely for structural changes — usemcp__UnityMCP__manage_prefabs.modify_contents(create_child=[...], component_properties=...)directly against the asset path.modify_contentspreserves variant overrides because it operates on the prefab asset, not a scene instance. - Addressables group name is project-specific. Older skill versions hardcoded
TOFUIs.asset; the TheOneFeatureProject actually usesUIs.asset. Always detect via §7.1 grep, never assume. - Skeleton prefab without children → runtime NullReferenceException. Skipping §9 leaves
[SerializeField]refs unwired; the Presenter’sOnViewReadywill null-ref on first access. §9 is MANDATORY, not advisory. - Field-name
*Prefabfields are user-assigns, not children.BoosterButtonPrefab,ItemPrefab, etc. are prefab references the Presenter uses for runtime spawning — they MUST NOT be materialized as children. Skill emits a “user-assigns this in Inspector” note instead. - Layout image hallucination. When using §9.4, the skill MUST echo its interpretation to the user before applying. Don’t silently spawn children from a guessed layout — show the parse and wait for confirmation.
- Variant-of-variant root-GO fileID lookup. For deep variant chains (e.g.
BasePuzzleScreen → BaseUIScreen), the root GO fileID in the variant’starget:lines points to the grandparent’s GO, not the immediate parent’s. See §1.5 Case B. - Duplicating the base instead of variant-ing it — duplicated screens stop receiving base-prefab fixes. Always Track A→B for true variant.
- Prefab name = Addressables address =
[ScreenInfo]key. Drift between the three is the #1 silent-failure mode. Always usenameof(MyScreenView)on the script side. - Full-bleed background placed under
Content— SafeArea insets it on notched devices, revealing letterboxing. Background images go underBackground. - Tap-targets placed under
BackgroundorForeground— they can fall under the notch or gesture bar. Interactive UI MUST live underContent. - Deleting
Timeline_Grp— open/close transitions silently no-op. Never delete it. - Editing the base’s Timeline assets directly — changes every screen. Author per-screen Timeline assets and override the binding on the variant.
- Adding a second SafeArea — double inset. The base ships exactly one on
Content— leave it as the only one. - CanvasScaler / AspectRatioFitter on the variant root — fights
HyperCasualRootUI’s scaler. Never add either to the variant. GridLayoutGroupfor responsive grids — fixed cell count breaks on landscape/portrait. UseFlexibleLayoutGroupVer2withBaseOnContentpreset.- Backdrop above PopupBody (popup variant) — Backdrop renders on top, hiding the popup. Backdrop must be the FIRST child of its parent.
- Pivot not matching anchor edge — element drifts on different resolutions. Apply §5 rigidly.
- Renaming a prefab after Addressables registration without updating the address — Addressables does NOT auto-rename. Re-edit the group asset entry after any rename.
- Hand-editing
.prefabYAML beyond the §1 Track B template — corrupts variant overrides silently. The Track B template is a fixed pattern; do not improvise inside it.