BloodSprayer.cs

A component that controls a blood particle emitter and associated particle effect on a game object. It randomizes timing, initial rate and easing, updates emitter rate over time, adjusts effect gradient alpha based on parent renderer tint, spawns blood decal prefabs when particles hit the ground, and can be stopped.

File Access
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;
	}
}