Camera shake component for the game. It is an ICameraModifier that keeps a single scene-scoped service instance, accumulates a "trauma" value via static Sacudir(scene,intensidad), and on ModifyCamera applies randomized position and rotation offsets scaled by a quadratic curve while the trauma decays over time.
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 );
}
}