TemblorCamara.cs

A Scene component implementing ICameraModifier that applies a camera "trauma" shake effect. Systems call TemblorCamara.Sacudir(scene, intensidad) to add trauma; the modifier runs last (order 9999) and applies positional and rotational random offsets scaled by a quadratic trauma curve, then decays trauma over time.

Native Interop
namespace SBH;

using Sandbox;

/// <summary>
/// [SBHBase] TEMBLOR DE CÁMARA genérico (2026-08-10, pedido de Héctor para
/// los pisotones del Siren Head). Modelo de "trauma": los golpes suman
/// trauma, el trauma decae solo, y mientras haya trauma la vista vibra
/// (offset + roll aleatorios, curva cuadrática).
///
/// Es un ICameraModifier con el ORDEN MÁS ALTO: corre al final de la cadena
/// de cámara del engine, sobre la vista YA calculada del frame — primera
/// persona, posesión (órbita), cine — así que no hay realimentación posible
/// (la versión anterior mutaba la cámara post-update y la órbita de la
/// posesión heredaba la sacudida: "se buguea la cámara", 2026-08-10).
///
/// Cualquier sistema puede llamar <see cref="Sacudir"/>: pisadas de gigante,
/// explosiones, el trípode cuando vuelva del taller...
/// </summary>
public sealed class TemblorCamara : Component, ICameraModifier
{
	static TemblorCamara _instancia;
	float _trauma;

	/// <summary> Suma un golpe de temblor (0.1 = apenas, 1 = terremoto). </summary>
	public static void Sacudir( Scene scene, float intensidad )
	{
		if ( intensidad <= 0f ) return;
		var t = Asegurar( scene );
		if ( t.IsValid() )
			t._trauma = ( t._trauma + intensidad ).Clamp( 0f, 1.5f );
	}

	// Patrón FadeHorror.Asegurar: un solo GameObject de servicio en la escena
	static TemblorCamara Asegurar( Scene scene )
	{
		if ( _instancia.IsValid() ) return _instancia;
		if ( scene == null ) return null;

		var go = scene.CreateObject();
		go.Name = "sbh_temblor_camara";
		go.Flags |= GameObjectFlags.NotSaved | GameObjectFlags.NotNetworked;
		_instancia = go.Components.Create<TemblorCamara>();
		return _instancia;
	}

	int ICameraModifier.CameraOrder => 9999; // el último de la cadena

	void ICameraModifier.ModifyCamera( CameraComponent camera, ref CameraView view )
	{
		if ( _trauma <= 0.001f ) { _trauma = 0f; return; }

		var fuerza = _trauma * _trauma;
		view.Position += Vector3.Random * ( 6f * fuerza );
		view.Rotation *= Rotation.From(
			Game.Random.Float( -1.2f, 1.2f ) * fuerza,
			Game.Random.Float( -1.2f, 1.2f ) * fuerza,
			Game.Random.Float( -2.5f, 2.5f ) * fuerza );

		// Decae solo: un golpe de 0.4 dura ~un tercio de segundo
		_trauma = System.MathF.Max( 0f, _trauma - Time.Delta * 2.2f );
	}
}