Sync emission intensity across the network

Replicate MeshPainter.emissionIntensity (a global material multiplier, not
baked into the paint maps) as an owner-authoritative NetworkVariable<float> on
PlayerPaintSync. The owner publishes on change; remotes subscribe, seed the
current value on spawn (covers late-join) and let MeshPainter's per-frame
RefreshEmissionColor push it onto the material.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-27 16:40:58 +02:00
1 parent 10f2518640
commit dec537a60f
1 file changed
+30 -1
+30 -1
View File
@@ -16,6 +16,11 @@ using UnityEngine;
/// throttled to respect the transport's reliable window), buffers any live stamps that arrive during
/// the transfer, and after applying the snapshot replays the buffered stamps newer than the snapshot's
/// cutoff sequence - so no stroke is lost or double-applied across the join window.
///
/// Emission intensity is the one paint property that is NOT baked into the maps (it's a global
/// multiplier on the material's _EmissionColor), so it can't ride the stamp/snapshot path. It rides its
/// own owner-authoritative NetworkVariable; the remote painter's per-frame RefreshEmissionColor picks up
/// the applied field, and a late joiner gets the current value on spawn for free.
/// </summary>
[DisallowMultipleComponent]
public class PlayerPaintSync : NetworkBehaviour
@@ -36,6 +41,12 @@ public class PlayerPaintSync : NetworkBehaviour
private MeshPainter _painter;
private readonly List<PaintStampNet> _pending = new List<PaintStampNet>();
// Global emission-intensity multiplier (MeshPainter.emissionIntensity) — a per-player material value,
// not part of the painted maps. Owner-authoritative like the rest; the remote applies it to its
// painter, whose per-frame RefreshEmissionColor pushes it to the material (no extra material poke).
private readonly NetworkVariable<float> _emissionIntensity =
new(1f, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
// Monotonic batch counter on the owner; on every peer, the highest batch sequence accounted for
// (applied to a remote replica / sent by the owner). Used as the snapshot's cutoff marker.
private int _seq;
@@ -67,6 +78,10 @@ public class PlayerPaintSync : NetworkBehaviour
// clobber the local player's saved paint on teardown (same fix as the pose system).
_painter.persistPaint = false;
// Track the owner's emission intensity; seed the current (late-join) value right away.
_emissionIntensity.OnValueChanged += OnEmissionChanged;
_painter.emissionIntensity = _emissionIntensity.Value;
// The server (host) has been present since the start, so its replica already holds the full
// paint via the live stream; only a genuine late joiner needs the baseline pulled over.
if (!IsServer)
@@ -81,6 +96,12 @@ public class PlayerPaintSync : NetworkBehaviour
{
if (_painter && IsOwner)
_painter.OnStampCommitted -= OnStampCommitted;
_emissionIntensity.OnValueChanged -= OnEmissionChanged;
}
private void OnEmissionChanged(float previous, float current)
{
if (_painter) _painter.emissionIntensity = current;
}
// ---- Live stamp stream (owner -> server -> remotes) ----
@@ -100,7 +121,15 @@ public class PlayerPaintSync : NetworkBehaviour
_awaitingSnapshot = false;
}
if (!IsOwner || _pending.Count == 0)
if (!IsOwner)
return;
// Publish the owner's global emission intensity when the HUD slider changes it (delta-only; the
// NetworkVariable dirties only on an actual change, so assigning the same value is a no-op).
if (_painter && _emissionIntensity.Value != _painter.emissionIntensity)
_emissionIntensity.Value = _painter.emissionIntensity;
if (_pending.Count == 0)
return;
int seq = ++_seq;