解决重复切换State的时候会导致重复播放对应Segment的问题。

修复FadeIn读取了FadeOut参数的问题。
增加Initial Delay功能。
重构AudioScheduler.ConfigureSource() -> SetupSource(), RegisterActiveSound(), StartPlayBack()。
移动长音频相关功能至LongAudio文件夹。
This commit is contained in:
2026-03-25 17:01:38 +08:00
parent e46e57d580
commit b6393449c9
19 changed files with 307 additions and 74 deletions
@@ -11,6 +11,8 @@ namespace OCES.Audio
/// </summary>
public class AudioScheduler : MonoBehaviour
{
public UnityEngine.UI.Text audioSystemTextBox;
const int k_maxGlobalConcurrent = 128;
//记录某个 AudioObject 最近一次触发播放的时刻,用于 MinInterval 节流判断,是"上次什么时候播过"。
@@ -18,7 +20,7 @@ namespace OCES.Audio
readonly Dictionary<uint, int> m_clipConcurrentCount = new();
readonly List<ActiveSound> m_activeSounds = new();
// 复用列表,避免 TryPlay 每帧分配临时 List(原 LINQ .Where().ToList()
// 复用列表,避免 TryPlay 每帧分配临时 List
readonly List<ActiveSound> m_tempSameObject = new();
readonly List<ActiveSound> m_tempSameGroup = new();
readonly List<ActiveSound> m_tempLowerPriority = new();
@@ -31,6 +33,14 @@ namespace OCES.Audio
AudioContainerSelector m_containerSelector;
PitchStepManager m_pitchStepManager;
#if UNITY_EDITOR
void Update()
{
Editor.DebugInfoCollector.Instance.ActiveSounds = this.m_activeSounds;
Editor.DebugInfoCollector.Instance.ClipConcurrentCount = this.m_clipConcurrentCount;
}
#endif
int GetClipCount(uint id)
{
@@ -40,7 +50,7 @@ namespace OCES.Audio
void IncrementClipCount(uint id)
{
this.m_clipConcurrentCount[id] = GetClipCount(id) + 1;
//Debug.Log($"{id} count added to {GetClipCount(id)}");
Debug.Log($"{id} count added to {GetClipCount(id)}");
}
void DecrementClipCount(uint id)
@@ -167,45 +177,24 @@ namespace OCES.Audio
void PlayNewSound(AudioObject audioObject, float pitch)
{
// Blend:同时播放所有音轨,无需走后续逻辑
if (audioObject.ContainerType == ContainerType.Blend)
ActiveSound active = new()
{
for (int i = 0; i < audioObject.Name.Count; i++)
ConfigureSource(this.m_pool.AcquireAudioSource(), audioObject, pitch, clipIndex: i, registerRemove: true);
return;
// TODO BlendRanges, BlendCrossFadeType, BlendReactParam 支持
}
AudioSource source = this.m_pool.AcquireAudioSource();
// 持续播放模式(Continuous
if (audioObject.Name.Count > 1 && audioObject.ContainerPlayMode)
{
ActiveSound chainActive = new()
{
Source = source,
AudioObject = audioObject,
StartTime = Time.realtimeSinceStartupAsDouble,
Pitch = pitch,
};
this.m_activeSounds.Add(chainActive);
IncrementClipCount(audioObject.Id);
int continuousStart = audioObject.ContainerType == ContainerType.Random ? -1 : 0;
chainActive.Coroutine =
StartCoroutine(PlayContainerContinuous(source, audioObject, chainActive, continuousStart, pitch));
return;
}
// 单次播放(步进模式)
int startIndex = audioObject.ContainerType switch
{
ContainerType.Random => this.m_containerSelector.PickShuffleIndex(audioObject),
ContainerType.Sequence => this.m_containerSelector.GetNextSequenceIndex(audioObject),
_ => 0,
AudioObject = audioObject,
Pitch = pitch,
State = ActiveSoundState.Pending,
StartTime = Time.realtimeSinceStartupAsDouble,
};
ConfigureSource(source, audioObject, pitch, startIndex, registerRemove: true);
if (audioObject.InitialDelay > 0)
{
this.m_activeSounds.Add(active);
IncrementClipCount(audioObject.Id);
active.Coroutine = StartCoroutine(PlayAfterDelay(active, audioObject, pitch));
return;
}
ExecutePlay(active);
}
/// <summary>
@@ -233,11 +222,13 @@ namespace OCES.Audio
limitRepetition);
// 配置并播放
if (!ConfigureSource(source, audioObject, pitch, index, registerRemove: false))
if (!SetupSource(source, audioObject, pitch, index))
{
Debug.LogError($"音频文件未找到:{audioObject.Name[index]}");
yield break;
}
StartPlayback(source);
yield return new WaitWhile(() => source.isPlaying);
@@ -261,6 +252,20 @@ namespace OCES.Audio
}
}
}
static void StartPlayback(AudioSource source)
{
source.Play();
}
IEnumerator PlayAfterDelay(ActiveSound active, AudioObject audioObject, float pitch)
{
Debug.Log($"Delaying for {audioObject.InitialDelay} second(s).");
yield return new WaitForSeconds(audioObject.InitialDelay);
if (!this.m_activeSounds.Contains(active)) yield break;
ExecutePlay(active, isRegistered: true);
}
// ─────────────────────────────────────────────
// 工具方法
@@ -312,42 +317,121 @@ namespace OCES.Audio
_ => this.m_sfxGroup,
};
bool ConfigureSource(AudioSource source, AudioObject audioObject, float pitch, int clipIndex = 0, bool registerRemove = true)
bool SetupSource(AudioSource source, AudioObject audioObject, float pitch, int clipIndex = 0)
{
// TODO 现用现找可能会导致主线程卡死,尤其是低端机。需要配合Decompress配置优化性能。
AudioClip clip = Resources.Load<AudioClip>($"Audios/{audioObject.Name[clipIndex]}");
AudioClip clip = Resources.Load<AudioClip>($"Audios/{audioObject.Name[clipIndex]}"); // TODO 抽象同一资源加载接口
if (!clip)
{
Debug.LogError($"音频文件未找到:{audioObject.Name[clipIndex]}");
return false;
}
source.clip = clip;
source.loop = audioObject.LoopCount < 0;
source.clip = clip;
source.loop = audioObject.LoopCount < 0;
source.priority = audioObject.Priority;
source.outputAudioMixerGroup = GetMixerGroup(audioObject.MixingType);
source.pitch = pitch;
source.Play();
if (registerRemove)
{
IncrementClipCount(audioObject.Id);
ActiveSound active = new()
{
Source = source,
AudioObject = audioObject,
StartTime = Time.realtimeSinceStartupAsDouble,
Pitch = pitch,
};
this.m_activeSounds.Add(active);
active.Coroutine = StartCoroutine(RemoveWhenFinished(active));
}
return true;
}
void RegisterActiveSound(ActiveSound activeSound, AudioSource audioSource, bool isRegistered = false)
{
activeSound.Source = audioSource;
activeSound.State = ActiveSoundState.Playing;
activeSound.StartTime = Time.realtimeSinceStartupAsDouble;
if (!isRegistered)
{
this.m_activeSounds.Add(activeSound);
IncrementClipCount(activeSound.AudioObject.Id);
}
activeSound.Coroutine = StartCoroutine(RemoveWhenFinished(activeSound));
}
void ExecutePlay(ActiveSound active, bool isRegistered = false)
{
AudioObject audioObject = active.AudioObject;
float pitch = active.Pitch;
// =======================
// Blend(每个clip一个ActiveSound
// =======================
if (audioObject.ContainerType == ContainerType.Blend)
{
for (int i = 0; i < audioObject.Name.Count; i++)
{
AudioSource source = this.m_pool.AcquireAudioSource();
ActiveSound child = new()
{
AudioObject = audioObject,
Pitch = pitch,
State = ActiveSoundState.Playing
};
if (!SetupSource(source, audioObject, pitch, i))
{
this.m_pool.ReturnToPool(source.gameObject);
continue;
}
StartPlayback(source);
RegisterActiveSound(child, source);
}
// 删除 pending 占位
if (this.m_activeSounds.Contains(active))
{
DecrementClipCount(audioObject.Id);
this.m_activeSounds.Remove(active);
}
return;
}
AudioSource sourceSingle = this.m_pool.AcquireAudioSource();
// =======================
// Continuous
// =======================
if (audioObject.Name.Count > 1 && audioObject.ContainerPlayMode)
{
active.Source = sourceSingle;
active.State = ActiveSoundState.Playing;
this.m_activeSounds.Add(active);
IncrementClipCount(audioObject.Id);
int start = audioObject.ContainerType == ContainerType.Random ? -1 : 0;
active.Coroutine = StartCoroutine(
PlayContainerContinuous(sourceSingle, audioObject, active, start, pitch)
);
return;
}
// =======================
// 单次播放
// =======================
int index = audioObject.ContainerType switch
{
ContainerType.Random => m_containerSelector.PickShuffleIndex(audioObject),
ContainerType.Sequence => m_containerSelector.GetNextSequenceIndex(audioObject),
_ => 0
};
if (!SetupSource(sourceSingle, audioObject, pitch, index))
{
m_pool.ReturnToPool(sourceSingle.gameObject);
return;
}
StartPlayback(sourceSingle);
RegisterActiveSound(active, sourceSingle, isRegistered);
}
IEnumerator RemoveWhenFinished(ActiveSound active)
{
if (active.AudioObject.LoopCount < 0)
@@ -372,7 +456,7 @@ namespace OCES.Audio
bool PlayAgain(ActiveSound active)
{
active.Source.Play();
StartPlayback(active.Source);
return true;
}
@@ -384,7 +468,7 @@ namespace OCES.Audio
//Debug.Log($"[StopSound] 协程已终止: {active.AudioObject.Name[0]}");
}
active.Source.Stop();
if(active.Source) active.Source.Stop();
DecrementClipCount(active.AudioObject.Id);
this.m_activeSounds.Remove(active);
this.m_pool.ReturnToPool(active.Source.gameObject);
@@ -405,5 +489,6 @@ namespace OCES.Audio
public int CurrentLoopCount;
public Coroutine Coroutine;
public float Pitch;
public ActiveSoundState State;
}
}