Add in-game paint HUD bound to the mesh painter

Add a code-driven HUD that appears in paint mode and binds live to the
active MeshPainter: four channel toggles, HSV pickers for albedo and
emission, sliders for metallic/smoothness/occlusion, emission intensity
and brush size. The HUD self-assembles its own overlay canvas and event
system at runtime.

Add a ModeChanged event to GameModeManager so the HUD can follow the
active mode, and guard MeshPainter against painting through the UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-24 08:26:56 +02:00
1 parent 6555db1487
commit 04c22f0ac1
8 files changed
+594

No files matched your search

+124
View File
@@ -0,0 +1,124 @@
using System;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// Compact runtime HSV colour picker: a saturation/value square, a hue bar and a live
/// swatch. Builds its own UI as children when <see cref="Build"/> is called and raises
/// <see cref="Changed"/> on user interaction. The square is a pure-hue base with two
/// static gradient overlays on top (white-out -> saturation, black-out -> value), so
/// only the base tint changes per hue - no per-frame texture rebuilds. Hue is never
/// lost while dragging the square (the square only edits S/V, the bar only edits H).
/// </summary>
public class HsvColorPicker : MonoBehaviour
{
/// <summary>Raised with the new colour when the user moves the square or hue bar.</summary>
public event Action<Color> Changed;
private float _h, _s, _v, _a = 1f;
private Image _hueBase; // pure-hue background of the SV square
private RectTransform _svHandle;
private RectTransform _hueHandle;
private Image _swatch;
/// <summary>Current colour (HSV -> RGB, with the preserved alpha).</summary>
public Color Value
{
get
{
Color c = Color.HSVToRGB(_h, _s, _v);
c.a = _a;
return c;
}
}
/// <summary>Shows <paramref name="c"/> without raising <see cref="Changed"/> (external set).</summary>
public void SetColor(Color c)
{
Color.RGBToHSV(c, out _h, out _s, out _v);
_a = c.a;
Refresh();
}
/// <summary>Builds the picker (square + hue bar + swatch) under this object, top-left aligned.</summary>
public void Build(float squareSize, float hueHeight)
{
// Saturation / value square: base hue + two gradient overlays, top-left of this rect.
var sv = PaintHud.NewRect("SV", transform);
TopLeft(sv, new Vector2(0f, 0f), new Vector2(squareSize, squareSize));
_hueBase = PaintHud.AddImage(sv.gameObject, Color.red); // raycast target for the drag relay
PaintHud.AddOverlay(sv, PaintHud.SaturationSprite());
PaintHud.AddOverlay(sv, PaintHud.ValueSprite());
sv.gameObject.AddComponent<UiPointerSurface>().Picked += OnSvPicked;
_svHandle = PaintHud.NewRect("Handle", sv);
_svHandle.sizeDelta = new Vector2(12f, 12f);
var svHandleImg = PaintHud.AddImage(_svHandle.gameObject, Color.white);
svHandleImg.raycastTarget = false;
// Hue bar below the square.
var hue = PaintHud.NewRect("Hue", transform);
TopLeft(hue, new Vector2(0f, -(squareSize + 6f)), new Vector2(squareSize, hueHeight));
var hueImg = hue.gameObject.AddComponent<Image>();
hueImg.sprite = PaintHud.HueSprite();
hueImg.type = Image.Type.Simple;
hue.gameObject.AddComponent<UiPointerSurface>().Picked += OnHuePicked;
_hueHandle = PaintHud.NewRect("Handle", hue);
_hueHandle.sizeDelta = new Vector2(4f, hueHeight + 4f);
var hueHandleImg = PaintHud.AddImage(_hueHandle.gameObject, Color.white);
hueHandleImg.raycastTarget = false;
// Swatch, to the right of the square.
var sw = PaintHud.NewRect("Swatch", transform);
TopLeft(sw, new Vector2(squareSize + 10f, 0f), new Vector2(34f, 34f));
_swatch = PaintHud.AddImage(sw.gameObject, Color.white);
_swatch.raycastTarget = false;
Refresh();
}
private void OnSvPicked(Vector2 p)
{
_s = p.x;
_v = p.y;
Refresh();
Changed?.Invoke(Value);
}
private void OnHuePicked(Vector2 p)
{
_h = p.x;
Refresh();
Changed?.Invoke(Value);
}
private void Refresh()
{
if (_hueBase) _hueBase.color = Color.HSVToRGB(_h, 1f, 1f);
if (_svHandle)
{
_svHandle.anchorMin = _svHandle.anchorMax = new Vector2(_s, _v);
_svHandle.anchoredPosition = Vector2.zero;
}
if (_hueHandle)
{
_hueHandle.anchorMin = _hueHandle.anchorMax = new Vector2(_h, 0.5f);
_hueHandle.anchoredPosition = Vector2.zero;
}
if (_swatch) _swatch.color = Color.HSVToRGB(_h, _s, _v);
}
/// <summary>Anchors a child to this rect's top-left corner with the given offset and size.</summary>
private static void TopLeft(RectTransform rt, Vector2 offset, Vector2 size)
{
rt.anchorMin = rt.anchorMax = new Vector2(0f, 1f);
rt.pivot = new Vector2(0f, 1f);
rt.anchoredPosition = offset;
rt.sizeDelta = size;
}
}