Persist material scalars, reset the HUD, and split alpha from the picker
Save and restore the emission intensity and the three camo knobs (half-Lambert, half-Lambert brightness, specular dampen) with a named painting. The .paint format moves to v3 with a small scalar block between the header and the pixel maps; v2 paintings still load with their painter values left untouched. Loading re-syncs the emission and camo sliders, and peers pick the values up for free through the existing owner-authoritative paint NetworkVariables. Add a HUD reset (Lib_ResetHud button plus context-menu fallback) that behaves as if the saved hud-layout file were deleted: it removes the file, restores panels to their authored prefab positions, and resets the brush values to the painter/material defaults captured before any carry/restore runs. Remove the alpha bar from HsvColorPicker so alpha is driven by standalone Slider_AlbedoAlpha / Slider_EmissionAlpha bindings; the picker now writes RGB only and the sliders write alpha only, with no feedback between them. Add a generic SliderCurve component that reshapes a slider's handle position into a non-linear value (with an inverse for setting values back), and make the slider wiring curve-aware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
fe8f12b8bb
commit
057c99fa1d
7 files changed
+313
-128
No files matched your search
+45
-30
@@ -51,6 +51,8 @@ public partial class PaintHud : MonoBehaviour
|
||||
private const string TogglePipette = "Toggle_Pipette";
|
||||
private const string PickerAlbedo = "Picker_Albedo";
|
||||
private const string PickerEmission = "Picker_Emission";
|
||||
private const string SliderAlbedoAlpha = "Slider_AlbedoAlpha";
|
||||
private const string SliderEmissionAlpha = "Slider_EmissionAlpha";
|
||||
private const string SliderMetallic = "Slider_Metallic";
|
||||
private const string SliderSmoothness = "Slider_Smoothness";
|
||||
private const string SliderMetallicSmoothnessAlpha = "Slider_MetallicSmoothnessAlpha";
|
||||
@@ -121,6 +123,10 @@ public partial class PaintHud : MonoBehaviour
|
||||
_panel = go;
|
||||
}
|
||||
|
||||
// Snapshot the authored panel positions now, while the layout is untouched - before RestoreHudState
|
||||
// (in BindPainter) overwrites them. The reset button restores exactly these.
|
||||
CaptureAuthoredLayout();
|
||||
|
||||
// The paint library now lives inside the authored layout prefab; bind its widgets like the brush panel.
|
||||
BindLibrary(_panel ? _panel.transform : null);
|
||||
|
||||
@@ -167,6 +173,11 @@ public partial class PaintHud : MonoBehaviour
|
||||
// Re-sync the brush-size slider when Shift+wheel resizes the brush in-world (painter -> HUD).
|
||||
painter.BrushSizeChanged += SyncBrushSize;
|
||||
|
||||
// Snapshot the painter's prefab-default brush values before carry/restore mutate them; the reset
|
||||
// button returns to exactly these. Once per HUD (a freshly spawned respawn painter holds the same
|
||||
// prefab defaults, so re-capturing would be redundant).
|
||||
CaptureDefaults();
|
||||
|
||||
// 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.
|
||||
@@ -259,18 +270,23 @@ public partial class PaintHud : MonoBehaviour
|
||||
WirePanelFade(panel, PanelEmission, ToggleEmission);
|
||||
WirePanelFade(panel, PanelSpray, ToggleSpray);
|
||||
|
||||
// Picker drives only RGB (keeping the painter's current alpha); a standalone slider drives alpha.
|
||||
WirePicker(panel, PickerAlbedo, painter ? painter.brushColor : Color.red,
|
||||
c => { if (painter) painter.brushColor = c; });
|
||||
c => { if (painter) painter.brushColor = WithRgb(painter.brushColor, c); });
|
||||
WireSlider(panel, SliderAlbedoAlpha, painter ? painter.brushColor.a : .6f,
|
||||
v => { if (painter) painter.brushColor = WithAlpha(painter.brushColor, v); });
|
||||
|
||||
WireSlider(panel, SliderMetallic, painter ? painter.metallic : 0f, v => { if (painter) painter.metallic = v; });
|
||||
WireSlider(panel, SliderSmoothness, painter ? painter.smoothness : 0.5f, v => { if (painter) painter.smoothness = v; });
|
||||
WireSlider(panel, SliderMetallicSmoothnessAlpha, painter ? painter.metallicSmoothnessOpacity : 1f, v => { if (painter) painter.metallicSmoothnessOpacity = v; });
|
||||
WireSlider(panel, SliderMetallicSmoothnessAlpha, painter ? painter.metallicSmoothnessOpacity : .6f, v => { if (painter) painter.metallicSmoothnessOpacity = v; });
|
||||
WireSlider(panel, SliderOcclusion, painter ? painter.occlusion : 0.5f, v => { if (painter) painter.occlusion = v; });
|
||||
WireSlider(panel, SliderOcclusionAlpha, painter ? painter.occlusionOpacity : 1f, v => { if (painter) painter.occlusionOpacity = v; });
|
||||
WireSlider(panel, SliderOcclusionAlpha, painter ? painter.occlusionOpacity : .6f, v => { if (painter) painter.occlusionOpacity = v; });
|
||||
|
||||
WirePicker(panel, PickerEmission, painter ? painter.emissionColor : Color.black,
|
||||
c => { if (painter) painter.emissionColor = c; });
|
||||
c => { if (painter) painter.emissionColor = WithRgb(painter.emissionColor, c); });
|
||||
WireSlider(panel, SliderEmissionIntensity, painter ? painter.emissionIntensity : 1f, v => { if (painter) painter.emissionIntensity = v; });
|
||||
WireSlider(panel, SliderEmissionAlpha, painter ? painter.emissionColor.a : .6f,
|
||||
v => { if (painter) painter.emissionColor = WithAlpha(painter.emissionColor, v); });
|
||||
|
||||
WireSlider(panel, SliderBrushSize, painter ? painter.brushSize : 16f, v => { if (painter) painter.brushSize = Mathf.RoundToInt(v); });
|
||||
|
||||
@@ -279,8 +295,8 @@ public partial class PaintHud : MonoBehaviour
|
||||
WireSlider(panel, SliderSprayDots, painter ? painter.sprayDots : 24f, v => { if (painter) painter.sprayDots = Mathf.RoundToInt(v); });
|
||||
WireSlider(panel, SliderSprayDotSizeMin, painter ? painter.sprayDotSizeMin : .05f, v => { if (painter) painter.sprayDotSizeMin = v; });
|
||||
WireSlider(panel, SliderSprayDotSizeMax, painter ? painter.sprayDotSizeMax : .18f, v => { if (painter) painter.sprayDotSizeMax = v; });
|
||||
WireSlider(panel, SliderSprayDotAlphaMin, painter ? painter.sprayDotAlphaMin : .25f, v => { if (painter) painter.sprayDotAlphaMin = v; });
|
||||
WireSlider(panel, SliderSprayDotAlphaMax, painter ? painter.sprayDotAlphaMax : 1f, v => { if (painter) painter.sprayDotAlphaMax = v; });
|
||||
WireSlider(panel, SliderSprayDotAlphaMin, painter ? painter.sprayDotAlphaMin : .2f, v => { if (painter) painter.sprayDotAlphaMin = v; });
|
||||
WireSlider(panel, SliderSprayDotAlphaMax, painter ? painter.sprayDotAlphaMax : .6f, v => { if (painter) painter.sprayDotAlphaMax = v; });
|
||||
|
||||
// Camouflage knobs on the player's PlayerCamoLit material instance (material-backed, not painter
|
||||
// fields). The values are not readable here yet - the HUD binds in the avatar's OnNetworkSpawn,
|
||||
@@ -314,6 +330,8 @@ public partial class PaintHud : MonoBehaviour
|
||||
|
||||
SetPicker(panel, PickerAlbedo, painter.brushColor);
|
||||
SetPicker(panel, PickerEmission, painter.emissionColor);
|
||||
SetSlider(panel, SliderAlbedoAlpha, painter.brushColor.a);
|
||||
SetSlider(panel, SliderEmissionAlpha, painter.emissionColor.a);
|
||||
|
||||
SetSlider(panel, SliderMetallic, painter.metallic);
|
||||
SetSlider(panel, SliderSmoothness, painter.smoothness);
|
||||
@@ -369,7 +387,10 @@ public partial class PaintHud : MonoBehaviour
|
||||
{
|
||||
var cell = FindDeep(panel, name);
|
||||
var slider = cell ? cell.GetComponentInChildren<Slider>() : null;
|
||||
if (slider) slider.value = value; // listener refreshes the value label + painter
|
||||
if (!slider) return;
|
||||
var curve = slider.GetComponent<SliderCurve>();
|
||||
if (curve) curve.SetOutput(value); // inverts the value onto the handle, then fires the slider listener
|
||||
else slider.value = value; // listener refreshes the value label + painter
|
||||
}
|
||||
|
||||
private static void SetPicker(Transform panel, string name, Color color)
|
||||
@@ -378,6 +399,12 @@ public partial class PaintHud : MonoBehaviour
|
||||
if (picker) picker.SetColor(color);
|
||||
}
|
||||
|
||||
// Albedo/emission colour is split across two widgets: the picker drives RGB (keeping the painter's
|
||||
// current alpha), the standalone alpha slider drives alpha (keeping RGB). Kept independent so neither
|
||||
// widget's edit clobbers the other's channel.
|
||||
private static Color WithRgb(Color current, Color rgb) => new Color(rgb.r, rgb.g, rgb.b, current.a);
|
||||
private static Color WithAlpha(Color current, float a) => new Color(current.r, current.g, current.b, a);
|
||||
|
||||
// Active = accent frame, inactive = subtle frame. Selection lives on the painter (activeBrushIndex);
|
||||
// the swatch click only sets it - the painter resolves the new mask on its next Update.
|
||||
private static readonly Color MaskFrameSelected = new Color(0.30f, 0.62f, 1f, 1f);
|
||||
@@ -509,18 +536,23 @@ public partial class PaintHud : MonoBehaviour
|
||||
if (!cell) return;
|
||||
var slider = cell.GetComponentInChildren<Slider>();
|
||||
if (!slider) return;
|
||||
var curve = slider.GetComponent<SliderCurve>(); // optional non-linear response on the same object
|
||||
var valueRt = cell.Find("Value");
|
||||
var valueText = valueRt ? valueRt.GetComponent<TMPro.TMP_Text>() : null;
|
||||
|
||||
bool whole = slider.wholeNumbers;
|
||||
slider.value = value;
|
||||
// With a curve the slider handle is linear but the bound value is the curved output; whole-number
|
||||
// quantisation moves onto the curve so the slider itself stays continuous.
|
||||
if (curve) curve.SetOutputWithoutNotify(value); // seed the handle by inverting the value (no listener yet)
|
||||
else slider.value = value;
|
||||
|
||||
Func<float> read = () => curve ? curve.Value : slider.value;
|
||||
|
||||
Action<float> setText = v =>
|
||||
{
|
||||
if (valueText) valueText.text = whole ? Mathf.RoundToInt(v).ToString() : v.ToString("0.00");
|
||||
if (valueText) valueText.text = slider.wholeNumbers ? Mathf.RoundToInt(v).ToString() : v.ToString("0.00");
|
||||
};
|
||||
setText(slider.value); // show the actual (clamped) value, not the unclamped input
|
||||
slider.onValueChanged.AddListener(v => { setText(v); onChanged(v); });
|
||||
setText(read()); // show the actual (clamped/curved) value, not the unclamped input
|
||||
slider.onValueChanged.AddListener(_ => { float v = read(); setText(v); onChanged(v); });
|
||||
}
|
||||
|
||||
private void WirePicker(Transform panel, string name, Color color, Action<Color> onChanged)
|
||||
@@ -581,7 +613,7 @@ public partial class PaintHud : MonoBehaviour
|
||||
}
|
||||
|
||||
// Cached gradient sprites for the colour picker.
|
||||
private static Sprite _satSprite, _valSprite, _hueSprite, _alphaSprite;
|
||||
private static Sprite _satSprite, _valSprite, _hueSprite;
|
||||
|
||||
/// <summary>Horizontal white-out: opaque white (left) -> transparent (right) = saturation.</summary>
|
||||
public static Sprite SaturationSprite()
|
||||
@@ -615,23 +647,6 @@ public partial class PaintHud : MonoBehaviour
|
||||
return _valSprite;
|
||||
}
|
||||
|
||||
/// <summary>Horizontal white transparency ramp: transparent (left) -> opaque (right) = alpha.
|
||||
/// Tinted to the current colour by the picker so it reads as a colour-over-backdrop fade.</summary>
|
||||
public static Sprite AlphaSprite()
|
||||
{
|
||||
if (_alphaSprite) return _alphaSprite;
|
||||
const int n = 64;
|
||||
var tex = NewGradientTex(n, 1);
|
||||
for (int x = 0; x < n; x++)
|
||||
{
|
||||
byte a = (byte)Mathf.RoundToInt(255f * (x / (float)(n - 1)));
|
||||
tex.SetPixel(x, 0, new Color32(255, 255, 255, a));
|
||||
}
|
||||
tex.Apply(false);
|
||||
_alphaSprite = ToSprite(tex);
|
||||
return _alphaSprite;
|
||||
}
|
||||
|
||||
/// <summary>Horizontal full-saturation hue ramp 0..1.</summary>
|
||||
public static Sprite HueSprite()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user