Restructured for new direction.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
@@ -23,41 +22,38 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
/// - Settings set "base" volumes (in dB) per mixer parameter.
|
||||
/// - Runtime states (Pause duck, Voice duck) apply "modifiers" (extra dB offsets).
|
||||
/// - Effective mixer value is always: effectiveDb = baseDb + modifiersDb
|
||||
/// - UI and Ambience route through SFX channel/group.
|
||||
/// - SFX pool is transient — new channels are spawned on demand and
|
||||
/// finished channels are reaped before each play.
|
||||
/// </summary>
|
||||
public class AudioManager : IDisposable, IManager
|
||||
{
|
||||
private const int INITIAL_AMBIENCE_SOURCES = 3;
|
||||
private const int INITIAL_SFX_SOURCES = 6;
|
||||
|
||||
private const float PAUSE_DUCK_TARGET_DB = -18f;
|
||||
private const float PAUSE_DUCK_FADE_SECONDS = 0.25f;
|
||||
private const int INITIAL_SFX_SOURCES = 8;
|
||||
private const float PAUSE_DUCK_TARGET_DB = -18f;
|
||||
private const float PAUSE_DUCK_FADE_SECONDS = 0.25f;
|
||||
private const float DEFAULT_VOICE_DUCK_TARGET_DB = -20f;
|
||||
|
||||
private readonly AudioMixer _audioMixer;
|
||||
private readonly AudioRegistry _audioRegistry;
|
||||
private readonly AudioMixer _audioMixer;
|
||||
private readonly AudioRegistry _audioRegistry;
|
||||
private readonly EventCoordinator _eventCoordinator;
|
||||
|
||||
private readonly Dictionary<string, float> _baseDb = new();
|
||||
private readonly List<GameObject> _createdAudioObjects = new();
|
||||
private readonly List<AudioSource> _ambienceSources = new();
|
||||
private readonly List<AudioFileSo> _currentAmbienceTracks = new();
|
||||
private readonly List<SfxChannel> _sfxChannels = new();
|
||||
private readonly Dictionary<string, float> _baseDb = new();
|
||||
private readonly List<GameObject> _createdAudioObjects = new();
|
||||
private readonly List<SfxChannel> _sfxChannels = new();
|
||||
|
||||
private AudioSource _musicSourceA;
|
||||
private AudioSource _musicSourceB;
|
||||
private AudioSource _voiceSource;
|
||||
private AudioSource _uiSource;
|
||||
|
||||
private string _activeVoiceSubtitleId;
|
||||
private string _activeVoiceSubtitleId;
|
||||
private AudioFileSo _currentMusicTrack;
|
||||
|
||||
private CancellationTokenSource _musicDuckCts;
|
||||
private CancellationTokenSource _musicFadeCts;
|
||||
private CancellationTokenSource _voiceCts;
|
||||
|
||||
private float _musicDuckDbCurrent;
|
||||
private float _pauseDuckDbCurrent;
|
||||
|
||||
private float _musicDuckDbCurrent;
|
||||
private float _pauseDuckDbCurrent;
|
||||
private Sequence _musicDuckSequence;
|
||||
private Sequence _pauseDuckSequence;
|
||||
|
||||
@@ -69,8 +65,8 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
[Inject]
|
||||
public AudioManager(AudioMixer mainMixer, AudioRegistry audioRegistry, EventCoordinator eventCoordinator)
|
||||
{
|
||||
_audioMixer = mainMixer;
|
||||
_audioRegistry = audioRegistry;
|
||||
_audioMixer = mainMixer;
|
||||
_audioRegistry = audioRegistry;
|
||||
_eventCoordinator = eventCoordinator;
|
||||
}
|
||||
|
||||
@@ -137,53 +133,41 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
}
|
||||
|
||||
_createdAudioObjects.Clear();
|
||||
_ambienceSources.Clear();
|
||||
_sfxChannels.Clear();
|
||||
_currentAmbienceTracks.Clear();
|
||||
_baseDb.Clear();
|
||||
|
||||
_musicSourceA = null;
|
||||
_musicSourceB = null;
|
||||
_voiceSource = null;
|
||||
_uiSource = null;
|
||||
_currentMusicTrack = null;
|
||||
_activeVoiceSubtitleId = null;
|
||||
_musicSourceA = null;
|
||||
_musicSourceB = null;
|
||||
_voiceSource = null;
|
||||
_currentMusicTrack = null;
|
||||
_activeVoiceSubtitleId = null;
|
||||
_voiceFinishedPublished = false;
|
||||
Initialized = false;
|
||||
Initialized = false;
|
||||
}
|
||||
|
||||
// ── Source creation ───────────────────────────────────────────
|
||||
|
||||
private void CreateSources()
|
||||
{
|
||||
_musicSourceA = CreateAudioSource("Music_Source_A", AudioMixerGroups.MUSIC_GROUP);
|
||||
_musicSourceB = CreateAudioSource("Music_Source_B", AudioMixerGroups.MUSIC_GROUP);
|
||||
|
||||
_voiceSource = CreateAudioSource("Voice_Source", AudioMixerGroups.VOICE_GROUP);
|
||||
_uiSource = CreateAudioSource("UI_Source", AudioMixerGroups.UI_GROUP);
|
||||
_voiceSource = CreateAudioSource("Voice_Source", AudioMixerGroups.VOICE_GROUP);
|
||||
|
||||
for (var i = 0; i < INITIAL_SFX_SOURCES; i++)
|
||||
{
|
||||
var src = CreateAudioSource($"SFX_Source_{i}", AudioMixerGroups.SFX_GROUP);
|
||||
_sfxChannels.Add(new SfxChannel
|
||||
{
|
||||
Source = src,
|
||||
StartedAtUnscaled = -999f
|
||||
});
|
||||
}
|
||||
|
||||
for (var i = 0; i < INITIAL_AMBIENCE_SOURCES; i++)
|
||||
{
|
||||
_ambienceSources.Add(CreateAudioSource($"Ambience_Source_{i}", AudioMixerGroups.AMBIENCE_GROUP));
|
||||
_sfxChannels.Add(new SfxChannel { Source = src, StartedAtUnscaled = -999f });
|
||||
}
|
||||
}
|
||||
|
||||
// ── Volume ────────────────────────────────────────────────────
|
||||
|
||||
private void PrimeMixerBaseValues()
|
||||
{
|
||||
PrimeBaseFromMixer(AudioMixerParameters.MASTER_VOLUME);
|
||||
PrimeBaseFromMixer(AudioMixerParameters.MUSIC_VOLUME);
|
||||
PrimeBaseFromMixer(AudioMixerParameters.SFX_VOLUME);
|
||||
PrimeBaseFromMixer(AudioMixerParameters.AMBIENCE_VOLUME);
|
||||
PrimeBaseFromMixer(AudioMixerParameters.VOICE_VOLUME);
|
||||
PrimeBaseFromMixer(AudioMixerParameters.UI_VOLUME);
|
||||
}
|
||||
|
||||
public void SetVolume(string parameter, float value01)
|
||||
@@ -194,25 +178,18 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
return;
|
||||
}
|
||||
|
||||
var linear = Mathf.Clamp01(value01);
|
||||
var db = Linear01ToDb(linear);
|
||||
|
||||
_baseDb[parameter] = db;
|
||||
_baseDb[parameter] = Linear01ToDb(Mathf.Clamp01(value01));
|
||||
ApplyEffectiveVolume(parameter);
|
||||
}
|
||||
|
||||
private static float Linear01ToDb(float linear01)
|
||||
{
|
||||
var lin = Mathf.Max(linear01, 0.0001f);
|
||||
return Mathf.Log10(lin) * 20f;
|
||||
return Mathf.Log10(Mathf.Max(linear01, 0.0001f)) * 20f;
|
||||
}
|
||||
|
||||
private void PrimeBaseFromMixer(string parameter)
|
||||
{
|
||||
if (_audioMixer != null && _audioMixer.GetFloat(parameter, out var db))
|
||||
_baseDb[parameter] = db;
|
||||
else
|
||||
_baseDb[parameter] = 0f;
|
||||
_baseDb[parameter] = _audioMixer != null && _audioMixer.GetFloat(parameter, out var db) ? db : 0f;
|
||||
}
|
||||
|
||||
private void ApplyAllEffectiveVolumes()
|
||||
@@ -220,9 +197,7 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
ApplyEffectiveVolume(AudioMixerParameters.MASTER_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.MUSIC_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.SFX_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.AMBIENCE_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.VOICE_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.UI_VOLUME);
|
||||
}
|
||||
|
||||
private void ApplyEffectiveVolume(string parameter)
|
||||
@@ -236,8 +211,7 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
var effective = baseDb;
|
||||
|
||||
if (parameter == AudioMixerParameters.MUSIC_VOLUME ||
|
||||
parameter == AudioMixerParameters.SFX_VOLUME ||
|
||||
parameter == AudioMixerParameters.AMBIENCE_VOLUME)
|
||||
parameter == AudioMixerParameters.SFX_VOLUME)
|
||||
{
|
||||
effective += _pauseDuckDbCurrent;
|
||||
}
|
||||
@@ -248,10 +222,11 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
_audioMixer.SetFloat(parameter, effective);
|
||||
}
|
||||
|
||||
// ── UI stack / pause duck ─────────────────────────────────────
|
||||
|
||||
private void OnUIStackChanged(UIStackChangedEvent e)
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
if (!Initialized) return;
|
||||
|
||||
if (e.AnyUIOpen)
|
||||
OnGamePausedInternal().Forget();
|
||||
@@ -279,20 +254,18 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
_pauseDuckSequence = default;
|
||||
}
|
||||
|
||||
seconds = Mathf.Max(0f, seconds);
|
||||
|
||||
var from = _pauseDuckDbCurrent;
|
||||
|
||||
_pauseDuckSequence = Sequence.Create(useUnscaledTime: true)
|
||||
.Group(Tween.Custom(
|
||||
from,
|
||||
targetDb,
|
||||
seconds,
|
||||
Mathf.Max(0f, seconds),
|
||||
v =>
|
||||
{
|
||||
_pauseDuckDbCurrent = v;
|
||||
ApplyEffectiveVolume(AudioMixerParameters.MUSIC_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.SFX_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.AMBIENCE_VOLUME);
|
||||
},
|
||||
Ease.OutCubic,
|
||||
useUnscaledTime: true));
|
||||
@@ -303,15 +276,13 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
|
||||
public void PauseVoiceSource(bool paused)
|
||||
{
|
||||
if (!Initialized || _voiceSource == null)
|
||||
return;
|
||||
|
||||
if (paused)
|
||||
_voiceSource.Pause();
|
||||
else
|
||||
_voiceSource.UnPause();
|
||||
if (!Initialized || _voiceSource == null) return;
|
||||
if (paused) _voiceSource.Pause();
|
||||
else _voiceSource.UnPause();
|
||||
}
|
||||
|
||||
// ── Play ──────────────────────────────────────────────────────
|
||||
|
||||
public void Play(string audioName)
|
||||
{
|
||||
if (!Initialized)
|
||||
@@ -344,21 +315,11 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
break;
|
||||
|
||||
case TrackType.Ambience:
|
||||
if (!_currentAmbienceTracks.Contains(audioData))
|
||||
{
|
||||
_currentAmbienceTracks.Add(audioData);
|
||||
PlayOnAvailableAmbienceSource(audioData);
|
||||
}
|
||||
break;
|
||||
|
||||
case TrackType.UIFX:
|
||||
case TrackType.Sfx:
|
||||
PlaySfx(audioData);
|
||||
break;
|
||||
|
||||
case TrackType.UIFX:
|
||||
PlayOneShotAsync(_uiSource, audioData).Forget();
|
||||
break;
|
||||
|
||||
case TrackType.Voice:
|
||||
PlayVoiceLine(audioData).Forget();
|
||||
break;
|
||||
@@ -368,6 +329,8 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
DuckMusicAsync(audioData.Clip.length, audioData.FadeTime).Forget();
|
||||
}
|
||||
|
||||
// ── Voice ─────────────────────────────────────────────────────
|
||||
|
||||
private async UniTaskVoid PlayVoiceLine(AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || _voiceSource == null || audioData?.Clip == null)
|
||||
@@ -377,15 +340,15 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
_voiceCts = new CancellationTokenSource();
|
||||
var token = _voiceCts.Token;
|
||||
|
||||
_activeVoiceSubtitleId = SubtitleIdentifiers.Get(audioData.MatchingSubtitleID);
|
||||
_activeVoiceSubtitleId = SubtitleIdentifiers.Get(audioData.MatchingSubtitleID);
|
||||
_voiceFinishedPublished = false;
|
||||
|
||||
_eventCoordinator.Publish(new VoiceLineStartedEvent(_activeVoiceSubtitleId));
|
||||
|
||||
_voiceSource.clip = audioData.Clip;
|
||||
_voiceSource.pitch = audioData.Pitch;
|
||||
_voiceSource.volume = audioData.Volume;
|
||||
_voiceSource.loop = false;
|
||||
_voiceSource.clip = audioData.Clip;
|
||||
_voiceSource.pitch = audioData.Pitch;
|
||||
_voiceSource.volume = audioData.Volume;
|
||||
_voiceSource.loop = false;
|
||||
_voiceSource.priority = audioData.Priority;
|
||||
_voiceSource.Play();
|
||||
|
||||
@@ -404,31 +367,210 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
|
||||
private void PublishVoiceFinishedIfNeeded()
|
||||
{
|
||||
if (_voiceFinishedPublished)
|
||||
return;
|
||||
if (_voiceFinishedPublished) return;
|
||||
|
||||
if (!string.IsNullOrEmpty(_activeVoiceSubtitleId))
|
||||
_eventCoordinator.Publish(new VoiceLineFinishedEvent(_activeVoiceSubtitleId));
|
||||
|
||||
_voiceFinishedPublished = true;
|
||||
_activeVoiceSubtitleId = null;
|
||||
_activeVoiceSubtitleId = null;
|
||||
}
|
||||
|
||||
public void StopVoice()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
StopAndDispose(ref _voiceCts);
|
||||
|
||||
if (_voiceSource != null && _voiceSource.isPlaying)
|
||||
_voiceSource.Stop();
|
||||
|
||||
PublishVoiceFinishedIfNeeded();
|
||||
}
|
||||
|
||||
// ── SFX (transient pool) ──────────────────────────────────────
|
||||
|
||||
private void PlaySfx(AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || audioData == null || audioData.Clip == null)
|
||||
return;
|
||||
|
||||
// Reap finished channels first so we don't accumulate stale entries
|
||||
ReapFinishedSfxChannels();
|
||||
|
||||
// Try to find a free channel from the existing pool
|
||||
AudioSource src = null;
|
||||
var channelIndex = -1;
|
||||
|
||||
for (var i = 0; i < _sfxChannels.Count; i++)
|
||||
{
|
||||
var s = _sfxChannels[i].Source;
|
||||
if (s != null && !s.isPlaying)
|
||||
{
|
||||
src = s;
|
||||
channelIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No free channel — spawn a transient one
|
||||
if (src == null)
|
||||
{
|
||||
src = CreateAudioSource($"SFX_Source_Transient_{_sfxChannels.Count}", AudioMixerGroups.SFX_GROUP);
|
||||
_sfxChannels.Add(new SfxChannel { Source = src, StartedAtUnscaled = -999f });
|
||||
channelIndex = _sfxChannels.Count - 1;
|
||||
|
||||
Debug.Log($"[AudioManager] SFX pool expanded to {_sfxChannels.Count} channels.");
|
||||
}
|
||||
|
||||
src.priority = audioData.Priority;
|
||||
src.pitch = audioData.Pitch;
|
||||
src.loop = audioData.Loopable;
|
||||
src.PlayOneShot(audioData.Clip, audioData.Volume);
|
||||
|
||||
_sfxChannels[channelIndex] = new SfxChannel
|
||||
{
|
||||
Source = src,
|
||||
StartedAtUnscaled = Time.unscaledTime
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes finished transient channels from the pool to prevent unbounded growth.
|
||||
/// Preserves the initial pool channels even when idle.
|
||||
/// </summary>
|
||||
private void ReapFinishedSfxChannels()
|
||||
{
|
||||
for (var i = _sfxChannels.Count - 1; i >= INITIAL_SFX_SOURCES; i--)
|
||||
{
|
||||
var src = _sfxChannels[i].Source;
|
||||
if (src == null || src.isPlaying)
|
||||
continue;
|
||||
|
||||
// Destroy the transient GameObject and remove from pool
|
||||
if (src.gameObject != null)
|
||||
{
|
||||
_createdAudioObjects.Remove(src.gameObject);
|
||||
Object.Destroy(src.gameObject);
|
||||
}
|
||||
|
||||
_sfxChannels.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void StopAllSfx()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
for (var i = _sfxChannels.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var src = _sfxChannels[i].Source;
|
||||
if (src == null) continue;
|
||||
|
||||
src.Stop();
|
||||
|
||||
// Destroy transient channels, reset initial ones
|
||||
if (i >= INITIAL_SFX_SOURCES)
|
||||
{
|
||||
if (src.gameObject != null)
|
||||
{
|
||||
_createdAudioObjects.Remove(src.gameObject);
|
||||
Object.Destroy(src.gameObject);
|
||||
}
|
||||
|
||||
_sfxChannels.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
_sfxChannels[i] = new SfxChannel { Source = src, StartedAtUnscaled = -999f };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Music ─────────────────────────────────────────────────────
|
||||
|
||||
public async UniTask CrossfadeMusic(AudioFileSo newTrack, float duration)
|
||||
{
|
||||
if (!Initialized || !newTrack || !newTrack.Clip) return;
|
||||
if (_currentMusicTrack == newTrack) return;
|
||||
|
||||
StopAndDispose(ref _musicFadeCts);
|
||||
_musicFadeCts = new CancellationTokenSource();
|
||||
var token = _musicFadeCts.Token;
|
||||
|
||||
var activeSource = _musicSourceA.isPlaying ? _musicSourceA
|
||||
: _musicSourceB.isPlaying ? _musicSourceB
|
||||
: null;
|
||||
|
||||
var inactiveSource = activeSource == _musicSourceA ? _musicSourceB : _musicSourceA;
|
||||
|
||||
PlayOnSource(inactiveSource, newTrack);
|
||||
|
||||
if (activeSource == null)
|
||||
{
|
||||
inactiveSource.volume = newTrack.Volume;
|
||||
_currentMusicTrack = newTrack;
|
||||
_eventCoordinator.Publish(new MusicTrackChangedEvent(newTrack));
|
||||
return;
|
||||
}
|
||||
|
||||
duration = Mathf.Max(0.0001f, duration);
|
||||
|
||||
var elapsed = 0f;
|
||||
var startVolume = activeSource.volume;
|
||||
|
||||
try
|
||||
{
|
||||
while (elapsed < duration)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var t = elapsed / duration;
|
||||
activeSource.volume = Mathf.Lerp(startVolume, 0f, t);
|
||||
inactiveSource.volume = Mathf.Lerp(0f, newTrack.Volume, t);
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
await UniTask.Yield(PlayerLoopTiming.Update, token);
|
||||
}
|
||||
|
||||
activeSource.volume = 0f;
|
||||
inactiveSource.volume = newTrack.Volume;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activeSource.Stop();
|
||||
activeSource.volume = startVolume;
|
||||
|
||||
_currentMusicTrack = newTrack;
|
||||
_eventCoordinator.Publish(new MusicTrackChangedEvent(newTrack));
|
||||
}
|
||||
|
||||
public void StopMusic()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
StopAndDispose(ref _musicFadeCts);
|
||||
|
||||
if (_musicSourceA != null) { _musicSourceA.Stop(); _musicSourceA.clip = null; _musicSourceA.volume = 0f; }
|
||||
if (_musicSourceB != null) { _musicSourceB.Stop(); _musicSourceB.clip = null; _musicSourceB.volume = 0f; }
|
||||
|
||||
_currentMusicTrack = null;
|
||||
}
|
||||
|
||||
private async UniTask DuckMusicAsync(float clipLengthSeconds, float fadeTimeSeconds)
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
if (!Initialized) return;
|
||||
|
||||
StopAndDispose(ref _musicDuckCts);
|
||||
_musicDuckCts = new CancellationTokenSource();
|
||||
var token = _musicDuckCts.Token;
|
||||
|
||||
fadeTimeSeconds = Mathf.Max(0.0001f, fadeTimeSeconds);
|
||||
var duckTarget = DEFAULT_VOICE_DUCK_TARGET_DB;
|
||||
|
||||
try
|
||||
{
|
||||
await TweenMusicDuckTo(duckTarget, fadeTimeSeconds, token);
|
||||
await TweenMusicDuckTo(DEFAULT_VOICE_DUCK_TARGET_DB, fadeTimeSeconds, token);
|
||||
|
||||
var hold = clipLengthSeconds - fadeTimeSeconds * 2f;
|
||||
if (hold > 0.01f)
|
||||
@@ -450,15 +592,13 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
_musicDuckSequence = default;
|
||||
}
|
||||
|
||||
seconds = Mathf.Max(0f, seconds);
|
||||
|
||||
var from = _musicDuckDbCurrent;
|
||||
|
||||
_musicDuckSequence = Sequence.Create(useUnscaledTime: true)
|
||||
.Group(Tween.Custom(
|
||||
from,
|
||||
targetDb,
|
||||
seconds,
|
||||
Mathf.Max(0f, seconds),
|
||||
v =>
|
||||
{
|
||||
_musicDuckDbCurrent = v;
|
||||
@@ -471,258 +611,22 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
_musicDuckSequence = default;
|
||||
}
|
||||
|
||||
public async UniTask CrossfadeMusic(AudioFileSo newTrack, float duration)
|
||||
{
|
||||
if (!Initialized || !newTrack || !newTrack.Clip)
|
||||
return;
|
||||
|
||||
if (_currentMusicTrack == newTrack)
|
||||
return;
|
||||
|
||||
StopAndDispose(ref _musicFadeCts);
|
||||
_musicFadeCts = new CancellationTokenSource();
|
||||
var token = _musicFadeCts.Token;
|
||||
|
||||
var activeSource = _musicSourceA.isPlaying
|
||||
? _musicSourceA
|
||||
: _musicSourceB.isPlaying
|
||||
? _musicSourceB
|
||||
: null;
|
||||
|
||||
var inactiveSource = activeSource == _musicSourceA ? _musicSourceB : _musicSourceA;
|
||||
|
||||
PlayOnSource(inactiveSource, newTrack);
|
||||
|
||||
if (activeSource == null)
|
||||
{
|
||||
inactiveSource.volume = newTrack.Volume;
|
||||
_currentMusicTrack = newTrack;
|
||||
_eventCoordinator.Publish(new MusicTrackChangedEvent(newTrack));
|
||||
return;
|
||||
}
|
||||
|
||||
duration = Mathf.Max(0.0001f, duration);
|
||||
|
||||
var elapsed = 0f;
|
||||
var startVolume = activeSource.volume;
|
||||
|
||||
try
|
||||
{
|
||||
while (elapsed < duration)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var t = elapsed / duration;
|
||||
activeSource.volume = Mathf.Lerp(startVolume, 0f, t);
|
||||
inactiveSource.volume = Mathf.Lerp(0f, newTrack.Volume, t);
|
||||
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
await UniTask.Yield(PlayerLoopTiming.Update, token);
|
||||
}
|
||||
|
||||
activeSource.volume = 0f;
|
||||
inactiveSource.volume = newTrack.Volume;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activeSource.Stop();
|
||||
activeSource.volume = startVolume;
|
||||
|
||||
_currentMusicTrack = newTrack;
|
||||
_eventCoordinator.Publish(new MusicTrackChangedEvent(newTrack));
|
||||
}
|
||||
|
||||
private void PlaySfx(AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || audioData == null || audioData.Clip == null)
|
||||
return;
|
||||
|
||||
var channelIndex = GetBestSfxChannelIndex(audioData.Priority);
|
||||
if (channelIndex < 0 || channelIndex >= _sfxChannels.Count)
|
||||
return;
|
||||
|
||||
var src = _sfxChannels[channelIndex].Source;
|
||||
if (src == null)
|
||||
return;
|
||||
|
||||
if (src.isPlaying)
|
||||
src.Stop();
|
||||
|
||||
src.priority = audioData.Priority;
|
||||
src.pitch = audioData.Pitch;
|
||||
src.PlayOneShot(audioData.Clip, audioData.Volume);
|
||||
|
||||
_sfxChannels[channelIndex] = new SfxChannel
|
||||
{
|
||||
Source = src,
|
||||
StartedAtUnscaled = Time.unscaledTime
|
||||
};
|
||||
}
|
||||
|
||||
private int GetBestSfxChannelIndex(int incomingPriority)
|
||||
{
|
||||
for (var i = 0; i < _sfxChannels.Count; i++)
|
||||
{
|
||||
var src = _sfxChannels[i].Source;
|
||||
if (src == null)
|
||||
continue;
|
||||
|
||||
if (!src.isPlaying)
|
||||
return i;
|
||||
}
|
||||
|
||||
var bestIndex = -1;
|
||||
var worstPriority = int.MinValue;
|
||||
var oldestStart = float.MaxValue;
|
||||
|
||||
for (var i = 0; i < _sfxChannels.Count; i++)
|
||||
{
|
||||
var src = _sfxChannels[i].Source;
|
||||
if (src == null)
|
||||
continue;
|
||||
|
||||
var p = src.priority;
|
||||
var started = _sfxChannels[i].StartedAtUnscaled;
|
||||
|
||||
if (p > worstPriority || (p == worstPriority && started < oldestStart))
|
||||
{
|
||||
worstPriority = p;
|
||||
oldestStart = started;
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
public void StopAmbience(AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || !audioData || !audioData.Clip || audioData.Type != TrackType.Ambience)
|
||||
return;
|
||||
|
||||
if (_currentAmbienceTracks.Remove(audioData))
|
||||
{
|
||||
foreach (var source in _ambienceSources.Where(s => s != null && s.clip == audioData.Clip))
|
||||
source.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void StopAllAmbience()
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
foreach (var s in _ambienceSources)
|
||||
{
|
||||
if (s != null)
|
||||
s.Stop();
|
||||
}
|
||||
|
||||
_currentAmbienceTracks.Clear();
|
||||
}
|
||||
|
||||
private void PlayOnAvailableAmbienceSource(AudioFileSo audioData)
|
||||
{
|
||||
var source = _ambienceSources.FirstOrDefault(s => s != null && !s.isPlaying);
|
||||
if (source == null)
|
||||
{
|
||||
source = CreateAudioSource(
|
||||
$"Ambience_Source_{_ambienceSources.Count}",
|
||||
AudioMixerGroups.AMBIENCE_GROUP);
|
||||
|
||||
_ambienceSources.Add(source);
|
||||
}
|
||||
|
||||
PlayOnSource(source, audioData);
|
||||
}
|
||||
|
||||
public void StopMusic()
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
StopAndDispose(ref _musicFadeCts);
|
||||
|
||||
if (_musicSourceA != null)
|
||||
{
|
||||
_musicSourceA.Stop();
|
||||
_musicSourceA.clip = null;
|
||||
_musicSourceA.volume = 0f;
|
||||
}
|
||||
|
||||
if (_musicSourceB != null)
|
||||
{
|
||||
_musicSourceB.Stop();
|
||||
_musicSourceB.clip = null;
|
||||
_musicSourceB.volume = 0f;
|
||||
}
|
||||
|
||||
_currentMusicTrack = null;
|
||||
}
|
||||
|
||||
public void StopVoice()
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
StopAndDispose(ref _voiceCts);
|
||||
|
||||
if (_voiceSource != null && _voiceSource.isPlaying)
|
||||
_voiceSource.Stop();
|
||||
|
||||
PublishVoiceFinishedIfNeeded();
|
||||
}
|
||||
|
||||
public void StopAllSfx()
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < _sfxChannels.Count; i++)
|
||||
{
|
||||
var src = _sfxChannels[i].Source;
|
||||
if (src == null)
|
||||
continue;
|
||||
|
||||
src.Stop();
|
||||
_sfxChannels[i] = new SfxChannel
|
||||
{
|
||||
Source = src,
|
||||
StartedAtUnscaled = -999f
|
||||
};
|
||||
}
|
||||
}
|
||||
// ── Stop all ──────────────────────────────────────────────────
|
||||
|
||||
public void StopAllAudio()
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
if (!Initialized) return;
|
||||
StopMusic();
|
||||
StopVoice();
|
||||
StopAllSfx();
|
||||
StopAllAmbience();
|
||||
|
||||
if (_uiSource != null)
|
||||
_uiSource.Stop();
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────
|
||||
|
||||
private static void StopAndDispose(ref CancellationTokenSource cts)
|
||||
{
|
||||
if (cts == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (cts == null) return;
|
||||
try { cts.Cancel(); } catch { }
|
||||
cts.Dispose();
|
||||
cts = null;
|
||||
}
|
||||
@@ -733,7 +637,7 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
Object.DontDestroyOnLoad(obj);
|
||||
_createdAudioObjects.Add(obj);
|
||||
|
||||
var src = obj.AddComponent<AudioSource>();
|
||||
var src = obj.AddComponent<AudioSource>();
|
||||
var group = _audioMixer.FindMatchingGroups(groupName);
|
||||
|
||||
if (group != null && group.Length > 0)
|
||||
@@ -744,28 +648,14 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
return src;
|
||||
}
|
||||
|
||||
private async UniTaskVoid PlayOneShotAsync(AudioSource source, AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || source == null || audioData == null || audioData.Clip == null)
|
||||
return;
|
||||
|
||||
source.priority = audioData.Priority;
|
||||
source.pitch = audioData.Pitch;
|
||||
source.PlayOneShot(audioData.Clip, audioData.Volume);
|
||||
|
||||
var seconds = audioData.Clip.length / Mathf.Max(audioData.Pitch, 0.0001f);
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(seconds));
|
||||
}
|
||||
|
||||
private void PlayOnSource(AudioSource source, AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || source == null || audioData == null)
|
||||
return;
|
||||
if (!Initialized || source == null || audioData == null) return;
|
||||
|
||||
source.clip = audioData.Clip;
|
||||
source.loop = audioData.Loopable;
|
||||
source.volume = audioData.Volume;
|
||||
source.pitch = audioData.Pitch;
|
||||
source.clip = audioData.Clip;
|
||||
source.loop = audioData.Loopable;
|
||||
source.volume = audioData.Volume;
|
||||
source.pitch = audioData.Pitch;
|
||||
source.priority = audioData.Priority;
|
||||
source.Play();
|
||||
}
|
||||
@@ -773,7 +663,7 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
private struct SfxChannel
|
||||
{
|
||||
public AudioSource Source;
|
||||
public float StartedAtUnscaled;
|
||||
public float StartedAtUnscaled;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user