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
}