fix(prefab): edit prefab assets without prefab-stage save corruption (set_prefab_property) - #45
Conversation
…corruption
Add set_prefab_property / set_prefab_properties: edit a component field inside a
.prefab asset in place via SerializedObject, without opening a prefab stage.
Because no live isolated scene/Canvas is created, edit-mode drivers (UGUI layout,
ContentSizeFitter, TMP auto-size, Spine SkeletonGraphic) never recompute and
mutate the graph, so only the field you set changes. This is the safe alternative
to open_prefab_stage -> set_component_property -> save_prefab_stage for single-field
edits; value format and Object-reference handling reuse ComponentSerializer.
Also: save_prefab_stage / close_prefab_stage now warn when the open prefab contains
components that recompute in a stage, since PrefabUtility.SaveAsPrefabAsset
re-serializes the whole in-memory graph and freezes those transient values into the
asset (RectTransform/font freezing; Spine skeletonDataAsset zeroed to {fileID:0}).
Adds 10 EditMode tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Merged with additional safety hardening in commit 7ce89ca. The asset-level approach from this PR is preserved. Before landing it, I added normalized Assets/**/*.prefab validation, read-only/regular-or-variant checks, explicit duplicate hierarchy/component handling, serialized-only writes (no reflection fallback), dedicated PrefabUtility saving, synchronous reimport, and persisted readback. The descriptions now avoid promising that Unity serialization callbacks can never touch another field. The new setters are also in the default core profile, and the bundled workflow skill was updated. Final validation:
Coverage includes invalid/read-only paths, duplicate paths and components, serialized-only enforcement, all-field failure, object references, forced-import persistence, prefab variants, nested prefab overrides, and Prefab Stage warnings. Thanks for the detailed root-cause analysis and implementation. |
Why
Editing a single field on a prefab that contains UGUI layout drivers, auto-sizing TMP text, or Spine components via the
open_prefab_stage→set_component_property→save_prefab_stagethree-piece silently corrupts unrelated fields in the.prefabasset.Root cause (reproduced deterministically on Unity
6000.3.13f1, a pure open+save with zero edits already corrupts):open_prefab_stage(PrefabStageUtility.OpenPrefab) instantiates the prefab into a live isolated scene with its own Canvas.[ExecuteAlways]edit-mode drivers then run without Play Mode:LayoutGroup/ContentSizeFitterand TMP auto-size dirty intoCanvasUpdateRegistryand rebuild against the isolated canvas's default metrics, mutatingRectTransform.m_SizeDelta/m_AnchoredPosition/m_LocalScaleand TMPm_fontSizein memory; SpineSkeletonGraphic(which has noIsPartOfPrefabAssetguard) rewrites rects and, when an import/refresh reinitializes it, nulls itsskeletonDataAsset.save_prefab_stage/close_prefab_stagethen callPrefabUtility.SaveAsPrefabAsset(stage.prefabContentsRoot, …), a wholesale re-serialize with no per-field change tracking, which faithfully writes those transient values back to disk.Observed on real prefabs (each pure open+save, no intended edit):
m_AnchoredPosition {155,-27} → {0,-27},m_LocalScale 0.9885 → 1frozen.skeletonDataAsset {fileID:11400000, guid:…, type:2} → {fileID:0}— runtime animation permanently lost (the{fileID:0}is bare with no guid, i.e. the field was genuinely null in memory, not a guid-reassignment churn).There is no way to make
SaveAsPrefabAssetwrite only the intended field, so a single-field edit through the stage is inherently unsafe on these prefabs.What changed
New tools —
set_prefab_property/set_prefab_properties(inComponentPropertyFunctions): edit a component field (or fields) inside a.prefabasset in place, without opening a prefab stage:Because the prefab is never instantiated into a live scene, no layout/TMP/Spine rebuild tick runs, so only the field you set changes. Value format,
[SerializeField] privatesupport, and Object-reference handling ({"fileID":id}/{"assetPath":"Assets/…"}) are reused verbatim fromset_component_property; the response echoes the post-write value (newValue/applied). Structural edits (add/reparent components) still use the stage.Guard on
save_prefab_stage/close_prefab_stage: before the wholesale save, scan the stage root for corruption-prone drivers (UGUIILayoutController/ILayoutGroup, auto-sizing TMP text, Spine components — detected by interface/type name via reflection, no new UI/TMP/Spine assembly dependency). When present, the tool message carries a⚠ WARNINGnaming the risk kinds and steering single-field edits toset_prefab_property. The save still proceeds (structural edits legitimately need it) — the corruption is just no longer silent.Benefits
AssetDatabase.ImportAsset(ForceUpdate)" workaround:SerializedObjectresolves Object references ({fileID, guid, type}) correctly, which hand-written YAML routinely gets wrong.ComponentSerializermachinery, so type coercion / private-field / object-ref behavior stays identical toset_component_property.Compatibility
set_prefab_property/set_prefab_propertiesare new tools; nothing else changes behavior.save_prefab_stage/close_prefab_stagekeep their existing string return and success message verbatim — the warning is appended only when risk drivers are detected, so existing callers andDoes.Contain("Prefab stage saved")-style checks are unaffected.com.unity.ugui, TextMeshPro, or Spine.set_prefab_property— may upgrade a prefab authored by an older Unity/TMP version to the current serialization format. That is Unity's normal, harmless format migration and is orthogonal to this fix.Testing & verification
6000.3.13f1editor via the MCP tools: on the exact layout-heavy Spine prefab that the three-piece corrupts (3RectTransformfields rewritten on a no-op save),set_prefab_propertyproduces a 2-line diff — only the target field — andsave_prefab_stagenow emits the⚠warning listinglayout / Spine / TMP auto-size.PREFAB_NOT_FOUND,PREFAB_GAMEOBJECT_NOT_FOUND,COMPONENT_NOT_FOUND_ON_TARGETwith anavailablecomponent list,PROPERTY_REQUIRED,PROPERTIES_REQUIRED).PrefabPropertyFunctionsTests) covering: only-target-field-changes + no-stage-opened, post-write echo, multi-field apply, root-path resolution, all error paths, and the layout-driver save warning. All pass; no regressions in the rest of the EditMode suite.Changelog
Added under
## Unreleased(Added + Fixed).