Effects/ScreenShake.cs
/// <summary>
/// Abstract base for screen shake effects. Active shakes are stored in a static list
/// and applied to the scene camera each frame via ScreenShake.Apply().
/// </summary>
public abstract class ScreenShake
{
private static readonly List<ScreenShake> _active = new();
/// <summary>Called each frame. Return false to remove this shake.</summary>
public abstract bool Update();
/// <summary>Apply all active screen shakes to the scene camera.</summary>
public static void Apply()
{
var camera = Game.ActiveScene?.Camera;
if ( camera == null ) return;
for ( int i = _active.Count - 1; i >= 0; i-- )
{
if ( !_active[i].Update() )
_active.RemoveAt( i );
}
}
public static void Add( ScreenShake shake ) => _active.Add( shake );
public static void Remove( ScreenShake shake ) => _active.Remove( shake );
public static void ClearAll() => _active.Clear();
// -----------------------------------------------------------------------
// Speed-based continuous vibration (scales with player speed)
// -----------------------------------------------------------------------
public sealed class Charge : ScreenShake
{
public float Size { get; set; }
public Charge() => Add( this );
public void Destroy() => Remove( this );
public override bool Update()
{
var camera = Game.ActiveScene?.Camera;
if ( camera == null ) return true;
var right = camera.WorldRotation.Right;
var up = camera.WorldRotation.Up;
var rand = new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) );
camera.WorldPosition += (right * rand.x + up * rand.y) * Size;
return true; // keep alive indefinitely (removed when player is replaced)
}
}
// -----------------------------------------------------------------------
// Time-decaying random shake (used for impacts/explosions)
// -----------------------------------------------------------------------
public sealed class Random : ScreenShake
{
public float Length { get; set; }
public float Size { get; set; }
private readonly RealTimeSince _started = 0f;
public Random( float length, float size )
{
Length = length;
Size = size;
Add( this );
}
public override bool Update()
{
var delta = _started / Length;
if ( delta >= 1f ) return false;
var camera = Game.ActiveScene?.Camera;
if ( camera == null ) return false;
var right = camera.WorldRotation.Right;
var up = camera.WorldRotation.Up;
var rand = new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) );
camera.WorldPosition += (right * rand.x + up * rand.y) * Size * (1f - delta);
return true;
}
}
}
// -----------------------------------------------------------------------
// Speed-based continuous vibration (scales with player speed)
// -----------------------------------------------------------------------
public sealed class Charge : ScreenShake
{
public float Size { get; set; }
public Charge() => Add( this );
public override bool Update()
{
var camera = Game.ActiveScene?.Camera;
if ( camera == null ) return true;
var right = camera.WorldRotation.Right;
var up = camera.WorldRotation.Up;
var rand = new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) );
camera.WorldPosition += (right * rand.x + up * rand.y) * Size;
return true; // keep alive indefinitely (removed when player is replaced)
}
}
// -----------------------------------------------------------------------
// Time-decaying random shake (used for impacts/explosions)
// -----------------------------------------------------------------------
public sealed class Random : ScreenShake
{
public float Length { get; set; }
public float Size { get; set; }
private readonly RealTimeSince _started = 0f;
public Random( float length, float size )
{
Length = length;
Size = size;
Add( this );
}
public override bool Update()
{
var delta = _started / Length;
if ( delta >= 1f ) return false;
var camera = Game.ActiveScene?.Camera;
if ( camera == null ) return false;
var right = camera.WorldRotation.Right;
var up = camera.WorldRotation.Up;
var rand = new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) );
camera.WorldPosition += (right * rand.x + up * rand.y) * Size * (1f - delta);
return true;
}
}