CameraShake.cs

Static helper that applies a decaying camera shake. Add() raises a 0 to 1 trauma value, and Apply() offsets the camera's rotation and position each frame using Perlin noise scaled by trauma squared.

namespace HorizonRP;

/// <summary>
/// L'écran qui tremble, chez ce client : un choc en voiture secoue la vue de ceux qui sont à
/// bord, d'autant plus fort que le choc est violent. Une « secousse » de 0 à 1 qui retombe
/// en un peu moins d'une seconde ; la vue bouge selon son carré (un petit choc à peine, un mur
/// à fond franchement). Du bruit lissé et non du hasard image par image : la vue tremble, elle
/// ne saute pas.
///
/// Appliquée après le placement de la caméra (RoleplayPlayer.OnPreRender), en décalage : la
/// caméra est reposée à chaque image par le PlayerController, rien ne s'accumule.
/// </summary>
public static class CameraShake
{
	static float trauma;
	static RealTimeSince sinceApplied;

	/// <summary>Décroissance par seconde.</summary>
	public static float Decay { get; set; } = 1.3f;
	/// <summary>À pleine secousse : tangage et lacet (°), roulis (°), déplacement (pouces).</summary>
	public static float MaxAngle { get; set; } = 3.5f;
	public static float MaxRoll { get; set; } = 5f;
	public static float MaxOffset { get; set; } = 3f;

	/// <summary>Un choc, de 0 (léger) à 1 (violent) : la secousse ne fait que monter, jusqu'à 1.</summary>
	public static void Add( float amount ) => trauma = MathF.Min( 1, trauma + MathF.Max( 0, amount ) );

	public static void Apply( CameraComponent camera )
	{
		var dt = MathF.Min( sinceApplied, 0.1f );
		sinceApplied = 0;
		if ( trauma <= 0 || !camera.IsValid() )
			return;
		trauma = MathF.Max( 0, trauma - Decay * dt );

		var shake = trauma * trauma;
		var t = RealTime.Now * 22f;
		// Le bruit de Perlin reste presque toujours entre 0,3 et 0,7 : étiré pour aller de -1 à 1.
		float N( float seed ) => Math.Clamp( (Sandbox.Utility.Noise.Perlin( t, seed * 37.1f ) - 0.5f) * 4f, -1f, 1f );
		camera.WorldRotation *= new Angles( N( 1 ) * MaxAngle * shake, N( 2 ) * MaxAngle * shake, N( 3 ) * MaxRoll * shake ).ToRotation();
		camera.WorldPosition += camera.WorldRotation * new Vector3( 0, N( 4 ), N( 5 ) ) * MaxOffset * shake;
	}
}