From 1d5676ab3c325e049ccad4a3bbf6598112a42f1a Mon Sep 17 00:00:00 2001 From: MelekTaus Moribundus Date: Sat, 11 Jul 2026 22:21:56 +0200 Subject: [PATCH] Stop paint particles when no stamp lands during a held stroke Particles kept playing at the last hit position when the cursor left the mesh mid-stroke. New _stampLandedThisFrame flag: reset before each stamp attempt, set only where the dab actually paints (after placement and corner guard); no landed stamp -> StopPaintParticles immediately. Co-Authored-By: Claude Fable 5 --- .../Components/MeshPainter/MeshPainter.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Assets/Scripts/Components/MeshPainter/MeshPainter.cs b/Assets/Scripts/Components/MeshPainter/MeshPainter.cs index 71db930..36cd65f 100644 --- a/Assets/Scripts/Components/MeshPainter/MeshPainter.cs +++ b/Assets/Scripts/Components/MeshPainter/MeshPainter.cs @@ -335,6 +335,7 @@ public partial class MeshPainter : MonoBehaviour "(Start Color is overwritten). +Z of the instance points along the surface normal.")] public ParticleSystem paintParticlesPrefab; private ParticleSystem _paintParticles; + private bool _stampLandedThisFrame; // any dab actually hit the mesh this frame (gates the particles) [Header("Canvas")] [Tooltip("Resolution of the paint textures (square).")] @@ -940,6 +941,7 @@ public partial class MeshPainter : MonoBehaviour if (stamp) { + _stampLandedThisFrame = false; BeginBackendStroke(); if (!firstPress && paintInterval <= 0f) @@ -957,6 +959,10 @@ public partial class MeshPainter : MonoBehaviour } EndBackendStroke(); + + // Held button but nothing landed (cursor off the mesh / corner guard): kill the splatter + // immediately instead of letting it play at the last hit until release. + if (!_stampLandedThisFrame) StopPaintParticles(); } _lastScreen = screenPos; @@ -986,6 +992,7 @@ public partial class MeshPainter : MonoBehaviour else UseResolvedStampMask(); DrawBackendStamp(pLocal, radiusLocal, u, v, nLocal); + _stampLandedThisFrame = true; UpdatePaintParticles(pLocal, radiusLocal, nLocal); EmitStamp(pLocal, radiusLocal, u, v, nLocal); @@ -994,8 +1001,8 @@ public partial class MeshPainter : MonoBehaviour /// Moves/scales/tints the stroke particle effect at a landed stamp and keeps it playing. /// Scale = brush world radius (author the prefab for radius 1, scaling mode Hierarchy), tint = - /// brush albedo color with a small per-particle random offset (alpha forced opaque - the brush - /// alpha is paint opacity, not particle fade). + /// brush albedo color with a small per-particle random offset; particle alpha = brush albedo + /// alpha (faint paint = faint splatter). private void UpdatePaintParticles(Vector3 pLocal, float radiusLocal, Vector3 nLocal) { if (!paintParticlesPrefab) return; @@ -1009,11 +1016,12 @@ public partial class MeshPainter : MonoBehaviour var main = _paintParticles.main; Color c = brushColor; - c.a = 1f; - // Per-particle random tint offset around the albedo (darker <-> lighter). + // Per-particle random tint offset around the albedo (darker <-> lighter); + // alpha follows the brush albedo alpha (paint opacity) so faint paint splatters faintly; + // +0.4 bias keeps the splatter readable even at low paint opacity. Color dark = Color.Lerp(c, Color.black, 0.13f); Color light = Color.Lerp(c, Color.white, 0.09f); - dark.a = light.a = 1f; + dark.a = light.a = Mathf.Clamp01(c.a + 0.4f); main.startColor = new ParticleSystem.MinMaxGradient(dark, light); if (!_paintParticles.isPlaying) _paintParticles.Play();