A component that controls blood particle emission and effects for a game object. It initializes randomized timings, emission rates and easing, updates particle emitter rate and particle gradient based on parent renderer alpha, and spawns blood decal prefabs when particles hit the ground.
using Sandbox;
using System;
public sealed class BloodSprayer : Component
{
[Property] public ParticleEmitter Emitter { get; set; }
[Property] public ParticleEffect ParticleEffect { get; set; }
private TimeSince _timeSinceSpawn;
private float _bloodRateStart;
private float _bloodStartTime;
private float _bloodStopTime;
private EasingType _bloodEasingType;
private ModelRenderer _parentRenderer;
public bool StopSpraying { get; set; } = false;
protected override void OnStart()
{
base.OnStart();
Emitter.Rate = 0f;
_timeSinceSpawn = 0f;
_bloodStartTime = Game.Random.Float( 0.15f, 0.4f );
_bloodStopTime = Game.Random.Float( 1.5f, 5f );
_bloodRateStart = Game.Random.Float( 8f, 50f );
int rand = Game.Random.Int( 0, 5 );
switch( rand )
{
case 0: _bloodEasingType = EasingType.SineOut; break;
case 1: _bloodEasingType = EasingType.SineOut; break;
case 2: _bloodEasingType = EasingType.Linear; break;
case 3: _bloodEasingType = EasingType.Linear; break;
case 4: _bloodEasingType = EasingType.SineIn; break;
case 5: _bloodEasingType = EasingType.QuadIn; break;
}
if ( GameObject.Parent == null )
return;
//var gibFader = GameObject.Parent.GetComponent<GibFader>();
//gibFader.BloodSprayer = this;
_parentRenderer = GameObject.Parent.GetComponent<ModelRenderer>();
}
protected override void OnUpdate()
{
if ( StopSpraying || !_parentRenderer.IsValid() )
return;
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"{_timeSinceSpawn} / {_bloodStopTime}\n{Emitter.Rate.ConstantValue}\nparentAlpha: {parentAlpha}", new global::Transform( WorldPosition ) );
if ( _timeSinceSpawn > _bloodStartTime )
{
Emitter.Rate = Utils.Map(_timeSinceSpawn, _bloodStartTime, _bloodStopTime, _bloodRateStart, 0f, _bloodEasingType );
}
//Log.Info($"Emitter.Rate: {Emitter.Rate.ConstantValue}" );
ParticleEffect.Gradient = ParticleEffect.Gradient.ConstantValue.WithAlpha( MathX.Clamp( Utils.Map( _parentRenderer.Tint.a, 1f, 0f, 1f, 0f, EasingType.QuadIn ), 0f, 1f ) );
//ParticleEffect.Gradient = Color.Lerp( Color.Red, Color.Blue, Utils.Map( parentAlpha, 1f, 0f, 0f, 1f ) );
}
public static void OnParticleHitGround( Particle particle )
{
if ( Game.IsEditor && !Game.IsPlaying ) // necessary?
return;
if ( particle.Position.z > 10f )
return;
var vel2D = new Vector2( particle.Velocity.x, particle.Velocity.y );
float yaw = Utils.GetAngleDegreesFromVectorAlt( vel2D );
//Log.Info( $"{(Vector2)particle.Velocity} -- yaw: {yaw}" );
var sidewaysSpeed = vel2D.Length;
//Log.Info( $"sidewaysSpeed: {sidewaysSpeed}" );
float skew = Utils.Map( sidewaysSpeed, 0f, 1000f, 1f, Game.Random.Float(2f, 2.5f), EasingType.SineIn );
var pos = new Vector3( particle.Position.x, particle.Position.y, MathF.Min( particle.Position.z, 5f ) + 0.5f );
var rot = new Angles( 90f, yaw, 0f );
//var scale = Game.Random.Float( 0.3f, 0.45f );
var scale = Game.Random.Float( 0.4f, 0.45f );
//scale = 1f;
var bloodDecalSplatGo = GameObject.Clone( "prefabs/effects/decal_blood_splat.prefab", new global::Transform( pos, rot, new Vector3( 2f, scale * skew, scale ) ) );
var bloodDecalSplat = bloodDecalSplatGo.GetComponent<BloodSplatDecal>();
bloodDecalSplat.Lifetime = Game.Random.Float( 2.25f, 3.25f ) * Utils.Map( Manager.Instance.NumEnemies, 0, 120, 2f, 0.75f );
bloodDecalSplat.FullAlpha = MathX.Clamp( particle.Color.a, 0f, 1f );
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Line(particle.Position, particle.Position - particle.Velocity * 50f);
//Log.Info( $"vel2D: {vel2D} velZ: {particle.Velocity.z} horizontalness: {vel2D.Length / particle.Velocity.z}" );
//Log.Info( $"OnParticleHitGround: {particle.Velocity}" );
//Log.Info( $"particle.Color: {particle.Color}" );
}
public void StopSprayingBlood()
{
StopSpraying = true;
Emitter.Rate = 0f;
}
}