BloodSplatter.cs
using SpriteTools;

public class BloodSplatter : Component
{
	[Property] public SpriteRendererLayer Sprite { get; set; }

	private TimeSince _spawnTime;
	private RealTimeSince _realTimeSinceSpawn;
	public float Lifetime { get; set; }

	public bool ShouldUseRealTime { get; set; }

	protected override void OnAwake()
	{
		base.OnAwake();

		Sprite.PlayAnimation( $"blood_splatter_{Game.Random.Int( 0, 3 )}" );
		Sprite.PlaybackSpeed = Sandbox.Game.Random.Float( 4f, 6.5f );

		if ( Game.Random.Float( 0f, 1f ) < 0.5f )
			Sprite.SpriteFlags = SpriteFlags.HorizontalFlip;

		LocalRotation = new Angles( 0f, -90f, 0f );
		//LocalScale = new Vector3( Game.Random.Float( 0.4f, 0.6f ), Game.Random.Float( 0.15f, 0.25f ), 1f ) * Globals.SPRITE_SCALE;
		LocalScale = new Vector3( Game.Random.Float( 0.33f, 0.48f ), 1f, Game.Random.Float( 0.4f, 0.63f ) ) * 2.2f * Globals.SPRITE_SCALE;

		_spawnTime = 0f;
		_realTimeSinceSpawn = 0f;
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();

		if ( !Manager.Instance.ShouldUpdateThings )
			return;

		var opacity = ShouldUseRealTime
			? Utils.Map( _realTimeSinceSpawn, 0f, 1.2f, 0f, 1f )
			: Utils.Map( _spawnTime, 0f, 0.14f, 0f, 1f ) * Utils.Map( _spawnTime, 0f, Lifetime - 0.02f, 1f, 0f );

		Sprite.Tint = Color.White.WithAlpha( opacity );

		if ( _spawnTime > Lifetime )
		{
			Manager.Instance.RemoveBloodSplatter( this );
			GameObject.Destroy();
		}
	}
}