Weapons/PeachLauncher/SplatEffect.cs
/// <summary>
/// SplatEffect — a lightweight action-graph-first component for defining
/// custom visual/audio/gameplay reactions to being splatted.
///
/// USAGE
/// ──────
/// Drop on any GO alongside SplatReceiver. Wire the action graph slots
/// in the inspector — no code needed for common reactions.
///
/// For more complex reactions (material swap, animation trigger, component
/// toggle) use the OnSplatted / OnSplatCleared action graph slots directly.
///
/// This component also implements ISplatReactor so it receives events from
/// SplatReceiver automatically without any manual wiring.
///
/// EXAMPLES (all done in inspector via action graph, zero code):
/// Peach tree: spawn a "leaves falling" particle on splat, reset on clear
/// Crate: tint the ModelRenderer orange on splat, revert on clear
/// Turret: disable the TurretComponent for SplatDuration, re-enable on clear
/// Wall tile: play a wet slap sound + spawn a drip particle
/// </summary>
[Title( "Splat Effect" )]
[Category( "Game / Peach" )]
public sealed class SplatEffect : Component, ISplatReactor
{
// ── Action graph slots ────────────────────────────────────────────────────
/// <summary>Fired when this object receives a splat. SplatInfo available as "Splat".</summary>
[Property] public Action<SplatInfo> OnSplatted { get; set; }
/// <summary>Fired when the splat state clears (duration expired or ClearSplat() called).</summary>
[Property] public Action OnCleared { get; set; }
// ── Optional built-in effects (no action graph needed for these) ──────────
/// <summary>Particle prefab to clone at the splat position on impact.</summary>
[Property] public GameObject ImpactParticlePrefab { get; set; }
/// <summary>Sound to play on splat.</summary>
[Property] public SoundEvent ImpactSound { get; set; }
/// <summary>
/// If set, tints this ModelRenderer to the splat colour on impact and
/// reverts it when the splat clears.
/// </summary>
[Property] public ModelRenderer TintTarget { get; set; }
[Property] public Color SplatTint { get; set; } = new Color( 1.0f, 0.6f, 0.2f ); // peach orange
[Property] public Color NormalTint { get; set; } = Color.White;
/// <summary>
/// If set, this component will be disabled for the duration of the splat
/// and re-enabled when it clears. Perfect for stunning turrets.
/// </summary>
[Property] public Component ComponentToDisable { get; set; }
// ── ISplatReactor ─────────────────────────────────────────────────────────
void ISplatReactor.OnSplatReceived( SplatInfo info )
{
// Built-in: impact particle
if ( ImpactParticlePrefab.IsValid() )
ImpactParticlePrefab.Clone( new Transform( info.Position, Rotation.LookAt( info.Normal ) ) );
// Built-in: impact sound
if ( ImpactSound.IsValid() )
Sound.Play( ImpactSound, info.Position );
// Built-in: tint
if ( TintTarget.IsValid() )
TintTarget.Tint = SplatTint;
// Built-in: disable component
if ( ComponentToDisable.IsValid() )
ComponentToDisable.Enabled = false;
// Action graph
OnSplatted?.Invoke( info );
}
void ISplatReactor.OnSplatCleared()
{
// Built-in: revert tint
if ( TintTarget.IsValid() )
TintTarget.Tint = NormalTint;
// Built-in: re-enable component
if ( ComponentToDisable.IsValid() )
ComponentToDisable.Enabled = true;
// Action graph
OnCleared?.Invoke();
}
}