Store ambient sound entities on a component tree. (#13110)
This commit is contained in:
@@ -3,8 +3,10 @@ using Content.Shared.CCVar;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -20,13 +22,16 @@ namespace Content.Client.Audio
|
||||
/// </summary>
|
||||
public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
{
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly AmbientSoundTreeSystem _treeSys = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
protected override void QueueUpdate(EntityUid uid, AmbientSoundComponent ambience)
|
||||
=> _treeSys.QueueTreeUpdate(uid, ambience);
|
||||
|
||||
private AmbientSoundOverlay? _overlay;
|
||||
private int _maxAmbientCount;
|
||||
private bool _overlayEnabled;
|
||||
@@ -35,13 +40,6 @@ namespace Content.Client.Audio
|
||||
private TimeSpan _targetTime = TimeSpan.Zero;
|
||||
private float _ambienceVolume = 0.0f;
|
||||
|
||||
// Note that except for some rare exceptions, every ambient sound source appears to be static. So:
|
||||
// TODO AMBIENT SOUND Use only static queries
|
||||
// This would make all the lookups significantly faster. There are some rare exceptions, like flies, vehicles,
|
||||
// and the singularity. But those can just play sounds via some other system. Alternatively: give ambient sound
|
||||
// its own client-side tree to avoid this issue altogether.
|
||||
private static LookupFlags _flags = LookupFlags.Static | LookupFlags.Dynamic | LookupFlags.Sundries;
|
||||
|
||||
private static AudioParams _params = AudioParams.Default.WithVariation(0.01f).WithLoop(true).WithAttenuation(Attenuation.LinearDistance);
|
||||
|
||||
/// <summary>
|
||||
@@ -172,40 +170,48 @@ namespace Content.Client.Audio
|
||||
_playingCount.Clear();
|
||||
}
|
||||
|
||||
private Dictionary<string, List<(float Importance, AmbientSoundComponent)>> GetNearbySources(TransformComponent playerXform, MapCoordinates coords, EntityQuery<TransformComponent> xformQuery)
|
||||
private readonly struct QueryState
|
||||
{
|
||||
var sourceDict = new Dictionary<string, List<(float, AmbientSoundComponent)>>(16);
|
||||
var ambientQuery = GetEntityQuery<AmbientSoundComponent>();
|
||||
public readonly Dictionary<string, List<(float Importance, AmbientSoundComponent)>> SourceDict = new();
|
||||
public readonly Vector2 MapPos;
|
||||
public readonly TransformComponent Player;
|
||||
public readonly EntityQuery<TransformComponent> Query;
|
||||
|
||||
// TODO add variant of GetComponentsInRange that also returns the transform component.
|
||||
foreach (var entity in _lookup.GetEntitiesInRange(coords, _maxAmbientRange, flags: _flags))
|
||||
public QueryState(Vector2 mapPos, TransformComponent player, EntityQuery<TransformComponent> query)
|
||||
{
|
||||
if (!ambientQuery.TryGetComponent(entity, out var ambientComp) || !ambientComp.Enabled)
|
||||
continue;
|
||||
|
||||
var xform = xformQuery.GetComponent(entity);
|
||||
var delta = xform.ParentUid == playerXform.ParentUid
|
||||
? xform.LocalPosition - playerXform.LocalPosition
|
||||
: xform.WorldPosition - coords.Position;
|
||||
|
||||
var range = delta.Length;
|
||||
if (range >= ambientComp.Range)
|
||||
continue;
|
||||
|
||||
string key;
|
||||
|
||||
if (ambientComp.Sound is SoundPathSpecifier path)
|
||||
key = path.Path?.ToString() ?? string.Empty;
|
||||
else
|
||||
key = ((SoundCollectionSpecifier) ambientComp.Sound).Collection ?? string.Empty;
|
||||
|
||||
var list = sourceDict.GetOrNew(key);
|
||||
|
||||
// Prioritize far away & loud sounds.
|
||||
list.Add((range * (ambientComp.Volume + 32), ambientComp));
|
||||
MapPos = mapPos;
|
||||
Player = player;
|
||||
Query = query;
|
||||
}
|
||||
}
|
||||
|
||||
return sourceDict;
|
||||
private static bool Callback(
|
||||
ref QueryState state,
|
||||
in ComponentTreeEntry<AmbientSoundComponent> value)
|
||||
{
|
||||
var (ambientComp, xform) = value;
|
||||
|
||||
DebugTools.Assert(ambientComp.Enabled);
|
||||
|
||||
var delta = xform.ParentUid == state.Player.ParentUid
|
||||
? xform.LocalPosition - state.Player.LocalPosition
|
||||
: xform.WorldPosition - state.MapPos;
|
||||
|
||||
var range = delta.Length;
|
||||
if (range >= ambientComp.Range)
|
||||
return true;
|
||||
|
||||
string key;
|
||||
|
||||
if (ambientComp.Sound is SoundPathSpecifier path)
|
||||
key = path.Path?.ToString() ?? string.Empty;
|
||||
else
|
||||
key = ((SoundCollectionSpecifier) ambientComp.Sound).Collection ?? string.Empty;
|
||||
|
||||
// Prioritize far away & loud sounds.
|
||||
var importance = range * (ambientComp.Volume + 32);
|
||||
state.SourceDict.GetOrNew(key).Add((importance, ambientComp));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -240,8 +246,13 @@ namespace Content.Client.Audio
|
||||
if (_playingSounds.Count >= _maxAmbientCount)
|
||||
return;
|
||||
|
||||
var pos = mapPos.Position;
|
||||
var state = new QueryState(pos, playerXform, query);
|
||||
var worldAabb = new Box2(pos - _maxAmbientRange, pos + _maxAmbientRange);
|
||||
_treeSys.QueryAabb(ref state, Callback, mapPos.MapId, worldAabb);
|
||||
|
||||
// Add in range ambiences
|
||||
foreach (var (key, sources) in GetNearbySources(playerXform, mapPos, query))
|
||||
foreach (var (key, sources) in state.SourceDict)
|
||||
{
|
||||
if (_playingSounds.Count >= _maxAmbientCount)
|
||||
break;
|
||||
|
||||
14
Content.Client/Audio/AmbientSoundTreeComponent.cs
Normal file
14
Content.Client/Audio/AmbientSoundTreeComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Content.Shared.Audio;
|
||||
using Robust.Shared.ComponentTrees;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Content.Client.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Samples nearby <see cref="AmbientSoundComponent"/> and plays audio.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class AmbientSoundTreeComponent : Component, IComponentTreeComponent<AmbientSoundComponent>
|
||||
{
|
||||
public DynamicTree<ComponentTreeEntry<AmbientSoundComponent>> Tree { get; set; } = default!;
|
||||
}
|
||||
31
Content.Client/Audio/AmbientSoundTreeSystem.cs
Normal file
31
Content.Client/Audio/AmbientSoundTreeSystem.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Content.Shared.Audio;
|
||||
using Robust.Shared.ComponentTrees;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Content.Client.Audio;
|
||||
|
||||
public sealed class AmbientSoundTreeSystem : ComponentTreeSystem<AmbientSoundTreeComponent, AmbientSoundComponent>
|
||||
{
|
||||
#region Component Tree Overrides
|
||||
protected override bool DoFrameUpdate => false;
|
||||
protected override bool DoTickUpdate => true;
|
||||
protected override int InitialCapacity => 256;
|
||||
protected override bool Recursive => true;
|
||||
|
||||
protected override Box2 ExtractAabb(in ComponentTreeEntry<AmbientSoundComponent> entry, Vector2 pos, Angle rot)
|
||||
=> new Box2(pos - entry.Component.Range, pos + entry.Component.Range);
|
||||
|
||||
protected override Box2 ExtractAabb(in ComponentTreeEntry<AmbientSoundComponent> entry)
|
||||
{
|
||||
if (entry.Component.TreeUid == null)
|
||||
return default;
|
||||
|
||||
var pos = XformSystem.GetRelativePosition(
|
||||
entry.Transform,
|
||||
entry.Component.TreeUid.Value,
|
||||
GetEntityQuery<TransformComponent>());
|
||||
|
||||
return ExtractAabb(in entry, pos, default);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user