Component that plays a looping "panic" sound attached to a Nextbot. It spawns a looping SoundHandle, keeps its position synced to the entity, finds the nearest PlayerController each update, and adjusts the handle volume from 0 to VolumenMaximo based on configurable min/max distances. It exposes Intensidad (0..1) for other components to read.
namespace SBH;
using Sandbox;
using System.Linq;
/// <summary>
/// Reproduce un sonido en loop cuyo volumen crece cuando el jugador
/// está cerca. El clásico "pánico" de nextbot.
/// Va en el mismo GameObject que el NextbotBrain.
/// </summary>
public sealed class PanicSound : Component
{
[Property, Description( "Sonido en loop (el SoundEvent debe tener Looping activado)" )]
public SoundEvent Sonido { get; set; }
[Property, Description( "Distancia a la que el sonido está a volumen máximo" )]
public float DistanciaMinima { get; set; } = 150f;
[Property, Description( "Distancia a la que el sonido se deja de oír" )]
public float DistanciaMaxima { get; set; } = 1500f;
[Property, Description( "Volumen máximo (0 a 1)" )]
public float VolumenMaximo { get; set; } = 1f;
/// <summary>
/// Qué tan fuerte está sonando el pánico ahora mismo (0 a 1).
/// Otros componentes lo usan, p.ej. ExpresionesNextbot mueve la boca
/// cuando el sprunki "canta".
/// </summary>
public float Intensidad { get; private set; }
SoundHandle _handle;
protected override void OnEnabled()
{
if ( Sonido == null ) return;
_handle = Sound.Play( Sonido, WorldPosition );
if ( _handle != null ) _handle.Volume = 0f;
}
protected override void OnDisabled()
{
_handle?.Stop();
_handle = null;
}
protected override void OnUpdate()
{
if ( _handle == null || !_handle.IsValid() ) return;
// El sonido viaja pegado al sprunki
_handle.Position = WorldPosition;
// Buscar al jugador más cercano
var jugador = Scene.GetAllComponents<PlayerController>()
.OrderBy( p => p.WorldPosition.Distance( WorldPosition ) )
.FirstOrDefault();
if ( jugador == null )
{
Intensidad = 0f;
_handle.Volume = 0f;
return;
}
// Volumen: 1 cuando está a DistanciaMinima o menos, 0 a DistanciaMaxima o más
var distancia = jugador.WorldPosition.Distance( WorldPosition );
var t = 1f - ( (distancia - DistanciaMinima) / (DistanciaMaxima - DistanciaMinima) );
Intensidad = t.Clamp( 0f, 1f );
_handle.Volume = Intensidad * VolumenMaximo;
}
}