refactor: use PlayDelayed API for audio delay and add Stop by ID support

- Replace PlayAfterDelay coroutine with AudioSource.PlayDelayed
- Add Stop(audioId) public API
- Add startWithMusic toggle
- Clean up namespace and debug macros
This commit is contained in:
2026-04-17 11:20:17 +08:00
parent 2b34d0bf94
commit accd311e1c
10 changed files with 291 additions and 36 deletions
@@ -10,8 +10,12 @@ namespace OCES.Audio
public class AudioSystem : MonoBehaviour
{
public static AudioSystem Instance { get; private set; }
public bool startWithMusic;
public ConsoleLogLevel logLevel;
// ReSharper disable once MemberCanBePrivate.Global
public IReadOnlyDictionary<Type, Enum> ActiveStates { get; private set; }
public enum ConsoleLogLevel { Off, Log, Warning, Error } //TODO 实现这个功能
const string k_audioConfigPath = "AudioData";
const string k_audioResourcePath = "Audios";
@@ -170,6 +174,11 @@ namespace OCES.Audio
}
}
public void Stop(uint audioId)
{
this.m_sfxSystem.Stop(audioId);
}
// ─────────────────────────────────────────────
// 初始化
// ─────────────────────────────────────────────
@@ -244,7 +253,10 @@ namespace OCES.Audio
{
// ── 启动默认音乐与环境音 ──
// 触发一次初始状态,让音乐系统从默认状态开始匹配
SetState(GameState.Home);
if (this.startWithMusic)
{
SetState(GameState.Home);
}
}
AudioObject ResolveSwitchContainer(AudioObject switchContainer)
@@ -12,9 +12,9 @@ namespace OCES.Audio
internal class SyncPointState
{
public SyncPoint Mode;
public int BaseTimeSamples; // 读取Source 的 timeSamples
public double BaseDspTime; // 读取Source 的 dspTime
public int SampleRate; // Source 的采样率
public int BaseTimeSamples; // 读取Source Segment 的 timeSamples
public double BaseDspTime; // 读取Source Segment 的 dspTime
public int SampleRate; // Source Segment 的采样率
public bool Ready;
}
@@ -107,9 +107,9 @@ namespace OCES.Audio
if (syncState is { Mode: SyncPoint.SameAsCurrentSegment })
{
AudioSource newSource = CurrentHandle?.GetFirstLeafSource();
if (newSource != null && newSource.clip != null)
if (newSource && newSource.clip)
{
// 计算从读取Source 到现在经过的 samples
// 计算从读取Source Segment 到现在经过的 samples
double elapsedSeconds = AudioSettings.dspTime - syncState.BaseDspTime;
int elapsedSamples = (int)(elapsedSeconds * syncState.SampleRate);
int targetTimeSamples = syncState.BaseTimeSamples + elapsedSamples;
@@ -2,7 +2,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using OCES.Audio.HandWritten;
using OCES.Haptic;
using UnityEngine;
using UnityEngine.Audio;
@@ -37,7 +36,7 @@ namespace OCES.Audio
PitchStepResolver m_pitchStepResolver;
VolumeStepResolver m_volumeStepResolver;
#if UNITY_EDITOR
#if UNITY_EDITOR || DEVELOPMENT_BUILD
void Update()
{
Editor.DebugInfoCollector.Instance.ActiveSounds = this.m_activeSounds;
@@ -91,6 +90,15 @@ namespace OCES.Audio
AudioMixerGroup[] Find(string path) => mixer.FindMatchingGroups(path);
}
internal void Stop(uint audioId)
{
List<ActiveSound> stopTargets = this.m_activeSounds.FindAll(activeSound => activeSound.AudioObject.Id == audioId);
foreach (ActiveSound stopTarget in stopTargets)
{
StopSound(stopTarget);
}
}
// ─────────────────────────────────────────────
// 节流 & 调度入口
// ─────────────────────────────────────────────
@@ -197,16 +205,7 @@ namespace OCES.Audio
State = ActiveSoundState.Pending,
StartTime = Time.realtimeSinceStartupAsDouble,
};
if (audioObject.InitialDelay > 0)
{
this.m_activeSounds.Add(active);
IncrementClipCount(audioObject.Id);
active.Coroutine = StartCoroutine(PlayAfterDelay(active, audioObject));
return;
}
ExecutePlay(active);
}
@@ -216,6 +215,10 @@ namespace OCES.Audio
IEnumerator PlayContainerContinuous(AudioSource source, AudioObject audioObject, ActiveSound chainActive, int startIndex)
{
bool isRandom = audioObject.ContainerType == ContainerType.Random;
if (chainActive.AudioObject.InitialDelay > 0)
{
chainActive.State = ActiveSoundState.Pending;
}
// 初始化 Random 模式的辅助结构
List<int> remainingPool = isRandom ? new List<int>(Enumerable.Range(0, audioObject.Name.Count)) : null;
@@ -241,7 +244,15 @@ namespace OCES.Audio
yield break;
}
StartPlayback(source);
if (chainActive.State == ActiveSoundState.Pending)
{
chainActive.State = ActiveSoundState.Playing;
StartPlayback(source, audioObject.InitialDelay);
}
else
{
StartPlayback(source);
}
yield return new WaitWhile(() => source.isPlaying);
@@ -266,20 +277,14 @@ namespace OCES.Audio
}
}
}
static void StartPlayback(AudioSource source)
static void StartPlayback(AudioSource source, float delay = 0f)
{
source.Play();
if (delay > 0f)
source.PlayDelayed(delay);
else
source.Play();
}
IEnumerator PlayAfterDelay(ActiveSound active, AudioObject audioObject)
{
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);
}
// ─────────────────────────────────────────────
// 工具方法
@@ -373,7 +378,7 @@ namespace OCES.Audio
IncrementClipCount(activeSound.AudioObject.Id);
}
if (activeSound.AudioObject.ContainerType == ContainerType.Blend)
if (activeSound.AudioObject.ContainerType == ContainerType.Blend && activeSound.AudioObject.Haptic > 0)
{
Debug.LogWarning($"[Haptic System] Blend container {activeSound.AudioObject.Id} should not have haptic feedback!");
}
@@ -414,7 +419,7 @@ namespace OCES.Audio
continue;
}
StartPlayback(source);
StartPlayback(source, audioObject.InitialDelay);
RegisterActiveSound(child, source);
}
@@ -466,7 +471,7 @@ namespace OCES.Audio
return;
}
StartPlayback(sourceSingle);
StartPlayback(sourceSingle, active.AudioObject.InitialDelay);
RegisterActiveSound(active, sourceSingle, isRegistered);
}
IEnumerator RemoveWhenFinished(ActiveSound active)
@@ -496,7 +501,7 @@ namespace OCES.Audio
StartPlayback(active.Source);
return true;
}
void StopSound(ActiveSound active)
{
if (active.Coroutine != null)
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using UnityEngine;
namespace OCES.Audio.HandWritten
namespace OCES.Audio
{
public class VolumeStepResolver
{