diff --git a/Assets/Scripts/PaintHud.Persistence.cs b/Assets/Scripts/PaintHud.Persistence.cs index 6d300b7..e4a5d15 100644 --- a/Assets/Scripts/PaintHud.Persistence.cs +++ b/Assets/Scripts/PaintHud.Persistence.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.IO; using UnityEngine; -using UnityEngine.SceneManagement; public partial class PaintHud { @@ -13,25 +12,40 @@ public partial class PaintHud // and scalars), so it goes to a human-readable JSON file - unlike the painter's bulk pixel maps // (MeshPainter.Persistence) which use a raw byte dump. The two saves are independent: this one // owns the HUD layout + brush values, the painter owns the canvas pixels. + // + // The brush values are also mirrored into an in-memory snapshot (_carry) on this persistent HUD, so + // they survive the avatar respawn that a scene switch (lobby -> map) triggers: the new painter gets + // the carried values instead of its prefab defaults. The save key is derived from the HUD's OWN + // scene (a stable persistent scene), not the active scene - the latter now changes across the + // additive lobby/map loads, which would split save and restore into different files. [Header("Persistence")] [Tooltip("Save the panel positions and their values on quit / play-stop and restore them on the " + "next start. Off = the HUD opens at its authored layout / painter defaults every session.")] public bool persistHud = true; - [Tooltip("Save file identifier. Empty = derived from the active scene name. Set explicitly to keep " + + [Tooltip("Save file identifier. Empty = derived from the HUD's own scene name. Set explicitly to keep " + "the layout stable across scene renames or to share one layout between scenes.")] public string hudSaveId = ""; private const int HudFileVersion = 1; private bool _hudSavedOnTeardown; // guards a double save across OnApplicationQuit + OnDestroy + // In-memory mirror of the brush values, kept on this persistent HUD so they survive the avatar + // respawn a scene switch (lobby <-> map) triggers: the freshly spawned painter is seeded with these + // instead of its prefab defaults. Single instance with mutable fields -> no per-frame allocation. + private readonly HudValues _carry = new HudValues(); + private bool _hasCarry; + private static string HudSaveDir => Path.Combine(Application.persistentDataPath, "MeshPaint"); private string HudSavePath() { + // Derive from the HUD's OWN scene (a stable persistent scene), not the active scene: the latter + // now changes across the additive lobby/map loads, so a key from it would split save and restore + // into different files and silently lose the saved layout. string key = string.IsNullOrEmpty(hudSaveId) - ? SanitizeHudKey(SceneManager.GetActiveScene().name + "_hud") + ? SanitizeHudKey(gameObject.scene.name + "_hud") : SanitizeHudKey(hudSaveId); return Path.Combine(HudSaveDir, key + ".hudlayout"); } @@ -101,28 +115,14 @@ public partial class PaintHud if (painter) { + CopyValues(painter, state.values); + state.hasValues = true; + } + else if (_hasCarry) + { + // No painter bound at teardown (e.g. quit with no avatar) -> persist the last mirrored values. + state.values = _carry; state.hasValues = true; - var v = state.values; - v.paintAlbedo = painter.paintAlbedo; - v.paintMetallicSmoothness = painter.paintMetallicSmoothness; - v.paintOcclusion = painter.paintOcclusion; - v.paintEmission = painter.paintEmission; - v.spray = painter.spray; - v.brushColor = painter.brushColor; - v.metallic = painter.metallic; - v.smoothness = painter.smoothness; - v.metallicSmoothnessOpacity = painter.metallicSmoothnessOpacity; - v.occlusion = painter.occlusion; - v.occlusionOpacity = painter.occlusionOpacity; - v.emissionColor = painter.emissionColor; - v.emissionIntensity = painter.emissionIntensity; - v.brushSize = painter.brushSize; - v.activeBrushIndex = painter.activeBrushIndex; - v.sprayDots = painter.sprayDots; - v.sprayDotSizeMin = painter.sprayDotSizeMin; - v.sprayDotSizeMax = painter.sprayDotSizeMax; - v.sprayDotAlphaMin = painter.sprayDotAlphaMin; - v.sprayDotAlphaMax = painter.sprayDotAlphaMax; } Directory.CreateDirectory(HudSaveDir); @@ -148,27 +148,7 @@ public partial class PaintHud if (painter && state.hasValues && state.values != null) { - var v = state.values; - painter.paintAlbedo = v.paintAlbedo; - painter.paintMetallicSmoothness = v.paintMetallicSmoothness; - painter.paintOcclusion = v.paintOcclusion; - painter.paintEmission = v.paintEmission; - painter.spray = v.spray; - painter.brushColor = v.brushColor; - painter.metallic = v.metallic; - painter.smoothness = v.smoothness; - painter.metallicSmoothnessOpacity = v.metallicSmoothnessOpacity; - painter.occlusion = v.occlusion; - painter.occlusionOpacity = v.occlusionOpacity; - painter.emissionColor = v.emissionColor; - painter.emissionIntensity = v.emissionIntensity; - painter.brushSize = v.brushSize; - painter.activeBrushIndex = v.activeBrushIndex; - painter.sprayDots = v.sprayDots; - painter.sprayDotSizeMin = v.sprayDotSizeMin; - painter.sprayDotSizeMax = v.sprayDotSizeMax; - painter.sprayDotAlphaMin = v.sprayDotAlphaMin; - painter.sprayDotAlphaMax = v.sprayDotAlphaMax; + ApplyValues(painter, state.values); SyncFromPainter(); } @@ -181,6 +161,80 @@ public partial class PaintHud rt.anchoredPosition = p.pos; } + // --- In-session carry: brush values survive the avatar respawn a scene switch triggers --- + + private void LateUpdate() + { + // Mirror the live painter every frame; the next BindPainter (after a respawn) seeds the fresh + // painter from this snapshot instead of its prefab defaults. Captured while the painter lives, so + // it is immune to the destroy-pending timing of the despawn. + if (painter) CaptureCarry(); + } + + /// Snapshots the bound painter's brush values into the in-memory mirror. + public void CaptureCarry() + { + if (!painter) return; + CopyValues(painter, _carry); + _hasCarry = true; + } + + /// Writes the mirrored brush values onto the (freshly bound) painter and pushes them to the widgets. + public void ApplyCarry() + { + if (!painter || !_hasCarry) return; + ApplyValues(painter, _carry); + SyncFromPainter(); + } + + private static void CopyValues(MeshPainter from, HudValues v) + { + v.paintAlbedo = from.paintAlbedo; + v.paintMetallicSmoothness = from.paintMetallicSmoothness; + v.paintOcclusion = from.paintOcclusion; + v.paintEmission = from.paintEmission; + v.spray = from.spray; + v.brushColor = from.brushColor; + v.metallic = from.metallic; + v.smoothness = from.smoothness; + v.metallicSmoothnessOpacity = from.metallicSmoothnessOpacity; + v.occlusion = from.occlusion; + v.occlusionOpacity = from.occlusionOpacity; + v.emissionColor = from.emissionColor; + v.emissionIntensity = from.emissionIntensity; + v.brushSize = from.brushSize; + v.activeBrushIndex = from.activeBrushIndex; + v.sprayDots = from.sprayDots; + v.sprayDotSizeMin = from.sprayDotSizeMin; + v.sprayDotSizeMax = from.sprayDotSizeMax; + v.sprayDotAlphaMin = from.sprayDotAlphaMin; + v.sprayDotAlphaMax = from.sprayDotAlphaMax; + } + + private static void ApplyValues(MeshPainter to, HudValues v) + { + to.paintAlbedo = v.paintAlbedo; + to.paintMetallicSmoothness = v.paintMetallicSmoothness; + to.paintOcclusion = v.paintOcclusion; + to.paintEmission = v.paintEmission; + to.spray = v.spray; + to.brushColor = v.brushColor; + to.metallic = v.metallic; + to.smoothness = v.smoothness; + to.metallicSmoothnessOpacity = v.metallicSmoothnessOpacity; + to.occlusion = v.occlusion; + to.occlusionOpacity = v.occlusionOpacity; + to.emissionColor = v.emissionColor; + to.emissionIntensity = v.emissionIntensity; + to.brushSize = v.brushSize; + to.activeBrushIndex = v.activeBrushIndex; + to.sprayDots = v.sprayDots; + to.sprayDotSizeMin = v.sprayDotSizeMin; + to.sprayDotSizeMax = v.sprayDotSizeMax; + to.sprayDotAlphaMin = v.sprayDotAlphaMin; + to.sprayDotAlphaMax = v.sprayDotAlphaMax; + } + private HudState LoadHudState() { string path = HudSavePath(); diff --git a/Assets/Scripts/PaintHud.cs b/Assets/Scripts/PaintHud.cs index 3e10ee5..1bae6ac 100644 --- a/Assets/Scripts/PaintHud.cs +++ b/Assets/Scripts/PaintHud.cs @@ -156,8 +156,12 @@ public partial class PaintHud : MonoBehaviour // Re-sync every widget when the pipette adopts a sampled material (painter -> HUD). painter.Picked += SyncFromPainter; - // Restore the saved panel positions + widget values now that the painter exists. - RestoreHudState(); + // First bind this session: restore panel positions + brush values from disk (cross-session). + // A later bind is an avatar respawn (scene switch) -> carry the in-session brush values onto the + // fresh painter instead, so they are not reset to its prefab defaults. + if (_hasCarry) ApplyCarry(); + else RestoreHudState(); + CaptureCarry(); // seed/refresh the mirror from the now-current painter UpdateButtonStates(); // enable Save now that a painter is bound }