Files
sbox-public/engine/Sandbox.Engine/Scene/GameObjectSystems/CollisionSoundSystem.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

112 lines
3.3 KiB
C#

namespace Sandbox;
/// <summary>
/// This system exists to collect pending collision sounds and filter them into a unique set, to avoid
/// unnesssary sounds playing, when they're going to be making the same sound anyway.
/// </summary>
[Expose]
public sealed class CollisionSoundSystem : GameObjectSystem<CollisionSoundSystem>, ISceneCollisionEvents
{
record struct PendingSound( Surface Surface, Vector3 Position, float Speed, bool Networked );
private readonly List<PendingSound> Pending = [];
public CollisionSoundSystem( Scene scene ) : base( scene )
{
Listen( Stage.FinishUpdate, 100, ProcessQueue, "CollisionSoundSystem Queue" );
}
/// <summary>
/// Register this physics collision with the sound system
/// </summary>
public void RegisterCollision( in Collision collision )
{
var self = collision.Self;
var other = collision.Other;
if ( !self.Body.EnableCollisionSounds ) return;
if ( !other.Body.EnableCollisionSounds ) return;
// Assume networked if colliding with an object replicated to clients
var networkRoot = other.GameObject?.NetworkRoot;
var networked = networkRoot is { NetworkMode: NetworkMode.Object } && networkRoot.Network?.IsProxy == false;
AddShapeCollision( other.Shape, other.Surface, collision.Contact, networked );
AddShapeCollision( self.Shape, self.Surface, collision.Contact, networked );
}
void ISceneCollisionEvents.OnCollisionHit( Collision collision )
{
RegisterCollision( collision );
}
/// <summary>
/// Add a collision sound for this shape
/// </summary>
public void AddShapeCollision( PhysicsShape shape, Surface surface, in Vector3 position, float speed, bool networked )
{
if ( !shape.IsValid() ) return;
if ( !shape.Body.IsValid() ) return;
if ( !shape.Body.EnableCollisionSounds ) return;
if ( speed < 100.0f ) return;
if ( surface is null ) return;
// If we have more than 4, remove any that are slower/less significant
if ( Pending.Count > 4 && Pending.RemoveAll( x => x.Speed < speed ) == 0 )
return;
// check for redundancies
for ( int i = 0; i < Pending.Count; i++ )
{
if ( Pending[i].Surface.Index != surface.Index ) continue;
if ( Pending[i].Speed >= speed ) return;
Pending[i] = new PendingSound( surface, position, speed, networked );
return;
}
Pending.Add( new PendingSound( surface, position, speed, networked ) );
}
public void AddShapeCollision( PhysicsShape shape, Surface surface, in PhysicsContact contact, bool networked )
{
AddShapeCollision( shape, surface, contact.Point, MathF.Abs( contact.NormalSpeed ), networked );
}
RealTimeSince lastRan = 0;
/// <summary>
/// Create the pending sounds
/// </summary>
void ProcessQueue()
{
if ( lastRan < 0.05f ) return;
lastRan = 0;
foreach ( var pending in Pending )
{
if ( pending.Surface is null ) continue;
if ( pending.Networked )
{
PlayCollisionSound( (ushort)pending.Surface.Index, pending.Position, pending.Speed );
}
else
{
pending.Surface.PlayCollisionSound( pending.Position, pending.Speed );
}
}
Pending.Clear();
}
[Rpc.Broadcast( NetFlags.Unreliable )]
private void PlayCollisionSound( ushort index, Vector3 pos, float speed )
{
var surface = Surface.FindByIndex( index );
if ( surface == null ) return;
surface.PlayCollisionSound( pos, speed );
}
}