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 0fdd76022d
commit 63c5f6766c
10 changed files with 291 additions and 36 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
+231 -1
View File
@@ -807,6 +807,154 @@ Transform:
- {fileID: 1667985901} - {fileID: 1667985901}
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &647197059
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 647197060}
- component: {fileID: 647197064}
- component: {fileID: 647197063}
- component: {fileID: 647197062}
- component: {fileID: 647197061}
m_Layer: 5
m_Name: StopSound
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &647197060
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 647197059}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1759335265}
m_Father: {fileID: 1667985901}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 160, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &647197061
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 647197059}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3bdbaddf2c05142e19686a9a82ef92c3, type: 3}
m_Name:
m_EditorClassIdentifier:
callbackFlags: 0
inputField: {fileID: 1490886059}
--- !u!114 &647197062
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 647197059}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 647197063}
m_OnClick:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 647197061}
m_TargetAssemblyTypeName: PlaySoundBind, Assembly-CSharp
m_MethodName: StopSound
m_Mode: 1
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!114 &647197063
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 647197059}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &647197064
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 647197059}
m_CullTransparentMesh: 1
--- !u!1 &876276281 --- !u!1 &876276281
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -1933,6 +2081,7 @@ RectTransform:
- {fileID: 1987857384} - {fileID: 1987857384}
- {fileID: 1490886058} - {fileID: 1490886058}
- {fileID: 1394234348} - {fileID: 1394234348}
- {fileID: 647197060}
- {fileID: 1161878326} - {fileID: 1161878326}
- {fileID: 392790004} - {fileID: 392790004}
- {fileID: 1542973983} - {fileID: 1542973983}
@@ -2035,6 +2184,85 @@ MonoBehaviour:
m_ChildScaleWidth: 0 m_ChildScaleWidth: 0
m_ChildScaleHeight: 0 m_ChildScaleHeight: 0
m_ReverseArrangement: 0 m_ReverseArrangement: 0
--- !u!1 &1759335264
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1759335265}
- component: {fileID: 1759335267}
- component: {fileID: 1759335266}
m_Layer: 5
m_Name: Text (Legacy)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1759335265
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1759335264}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 647197060}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1759335266
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1759335264}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 10
m_MaxSize: 40
m_Alignment: 4
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: PlaySound
--- !u!222 &1759335267
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1759335264}
m_CullTransparentMesh: 1
--- !u!1 &1762080064 --- !u!1 &1762080064
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -2376,7 +2604,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 0} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 200, y: 300} m_SizeDelta: {x: 200, y: 250}
m_Pivot: {x: 0.5, y: 1} m_Pivot: {x: 0.5, y: 1}
--- !u!114 &1987857385 --- !u!114 &1987857385
MonoBehaviour: MonoBehaviour:
@@ -2599,6 +2827,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 5ce1f814dd5d46d48bc33c18ba11c44c, type: 3} m_Script: {fileID: 11500000, guid: 5ce1f814dd5d46d48bc33c18ba11c44c, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
startWithMusic: 0
logLevel: 0
--- !u!4 &2093584671 --- !u!4 &2093584671
Transform: Transform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -66,6 +66,7 @@ public static class AudioObjectDefinitions
{ "Chinese Number 08", 49 }, { "Chinese Number 08", 49 },
{ "Chinese Number 09", 49 }, { "Chinese Number 09", 49 },
{ "Chinese Number 10", 49 }, { "Chinese Number 10", 49 },
{ "Japanese Number 02", 51 },
{ "Bar", 52 }, { "Bar", 52 },
{ "Beat", 53 }, { "Beat", 53 },
{ "Grid", 54 }, { "Grid", 54 },
@@ -104,6 +105,7 @@ public static class AudioObjectDefinitions
{ "au_sfx_ui_button_corePlay_clear_cloud2", 70 }, { "au_sfx_ui_button_corePlay_clear_cloud2", 70 },
{ "au_sfx_ui_button_corePlay_clear_cloud3", 70 }, { "au_sfx_ui_button_corePlay_clear_cloud3", 70 },
{ "sfx_notice_test", 71 }, { "sfx_notice_test", 71 },
{ "Spanish Number 03", 73 },
}; };
public static readonly HashSet<string> AmbiguousNames = new() public static readonly HashSet<string> AmbiguousNames = new()
@@ -10,8 +10,12 @@ namespace OCES.Audio
public class AudioSystem : MonoBehaviour public class AudioSystem : MonoBehaviour
{ {
public static AudioSystem Instance { get; private set; } public static AudioSystem Instance { get; private set; }
public bool startWithMusic;
public ConsoleLogLevel logLevel;
// ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once MemberCanBePrivate.Global
public IReadOnlyDictionary<Type, Enum> ActiveStates { get; private set; } public IReadOnlyDictionary<Type, Enum> ActiveStates { get; private set; }
public enum ConsoleLogLevel { Off, Log, Warning, Error } //TODO 实现这个功能
const string k_audioConfigPath = "AudioData"; const string k_audioConfigPath = "AudioData";
const string k_audioResourcePath = "Audios"; const string k_audioResourcePath = "Audios";
@@ -170,6 +174,11 @@ namespace OCES.Audio
} }
} }
public void Stop(uint audioId)
{
this.m_sfxSystem.Stop(audioId);
}
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
// 初始化 // 初始化
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
@@ -244,8 +253,11 @@ namespace OCES.Audio
{ {
// ── 启动默认音乐与环境音 ── // ── 启动默认音乐与环境音 ──
// 触发一次初始状态,让音乐系统从默认状态开始匹配 // 触发一次初始状态,让音乐系统从默认状态开始匹配
if (this.startWithMusic)
{
SetState(GameState.Home); SetState(GameState.Home);
} }
}
AudioObject ResolveSwitchContainer(AudioObject switchContainer) AudioObject ResolveSwitchContainer(AudioObject switchContainer)
{ {
@@ -12,9 +12,9 @@ namespace OCES.Audio
internal class SyncPointState internal class SyncPointState
{ {
public SyncPoint Mode; public SyncPoint Mode;
public int BaseTimeSamples; // 读取Source 的 timeSamples public int BaseTimeSamples; // 读取Source Segment 的 timeSamples
public double BaseDspTime; // 读取Source 的 dspTime public double BaseDspTime; // 读取Source Segment 的 dspTime
public int SampleRate; // Source 的采样率 public int SampleRate; // Source Segment 的采样率
public bool Ready; public bool Ready;
} }
@@ -107,9 +107,9 @@ namespace OCES.Audio
if (syncState is { Mode: SyncPoint.SameAsCurrentSegment }) if (syncState is { Mode: SyncPoint.SameAsCurrentSegment })
{ {
AudioSource newSource = CurrentHandle?.GetFirstLeafSource(); 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; double elapsedSeconds = AudioSettings.dspTime - syncState.BaseDspTime;
int elapsedSamples = (int)(elapsedSeconds * syncState.SampleRate); int elapsedSamples = (int)(elapsedSeconds * syncState.SampleRate);
int targetTimeSamples = syncState.BaseTimeSamples + elapsedSamples; int targetTimeSamples = syncState.BaseTimeSamples + elapsedSamples;
@@ -2,7 +2,6 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using OCES.Audio.HandWritten;
using OCES.Haptic; using OCES.Haptic;
using UnityEngine; using UnityEngine;
using UnityEngine.Audio; using UnityEngine.Audio;
@@ -37,7 +36,7 @@ namespace OCES.Audio
PitchStepResolver m_pitchStepResolver; PitchStepResolver m_pitchStepResolver;
VolumeStepResolver m_volumeStepResolver; VolumeStepResolver m_volumeStepResolver;
#if UNITY_EDITOR #if UNITY_EDITOR || DEVELOPMENT_BUILD
void Update() void Update()
{ {
Editor.DebugInfoCollector.Instance.ActiveSounds = this.m_activeSounds; Editor.DebugInfoCollector.Instance.ActiveSounds = this.m_activeSounds;
@@ -91,6 +90,15 @@ namespace OCES.Audio
AudioMixerGroup[] Find(string path) => mixer.FindMatchingGroups(path); 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);
}
}
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
// 节流 & 调度入口 // 节流 & 调度入口
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
@@ -198,15 +206,6 @@ namespace OCES.Audio
StartTime = Time.realtimeSinceStartupAsDouble, StartTime = Time.realtimeSinceStartupAsDouble,
}; };
if (audioObject.InitialDelay > 0)
{
this.m_activeSounds.Add(active);
IncrementClipCount(audioObject.Id);
active.Coroutine = StartCoroutine(PlayAfterDelay(active, audioObject));
return;
}
ExecutePlay(active); ExecutePlay(active);
} }
@@ -216,6 +215,10 @@ namespace OCES.Audio
IEnumerator PlayContainerContinuous(AudioSource source, AudioObject audioObject, ActiveSound chainActive, int startIndex) IEnumerator PlayContainerContinuous(AudioSource source, AudioObject audioObject, ActiveSound chainActive, int startIndex)
{ {
bool isRandom = audioObject.ContainerType == ContainerType.Random; bool isRandom = audioObject.ContainerType == ContainerType.Random;
if (chainActive.AudioObject.InitialDelay > 0)
{
chainActive.State = ActiveSoundState.Pending;
}
// 初始化 Random 模式的辅助结构 // 初始化 Random 模式的辅助结构
List<int> remainingPool = isRandom ? new List<int>(Enumerable.Range(0, audioObject.Name.Count)) : null; List<int> remainingPool = isRandom ? new List<int>(Enumerable.Range(0, audioObject.Name.Count)) : null;
@@ -241,7 +244,15 @@ namespace OCES.Audio
yield break; yield break;
} }
if (chainActive.State == ActiveSoundState.Pending)
{
chainActive.State = ActiveSoundState.Playing;
StartPlayback(source, audioObject.InitialDelay);
}
else
{
StartPlayback(source); StartPlayback(source);
}
yield return new WaitWhile(() => source.isPlaying); 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)
{ {
if (delay > 0f)
source.PlayDelayed(delay);
else
source.Play(); 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); 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!"); Debug.LogWarning($"[Haptic System] Blend container {activeSound.AudioObject.Id} should not have haptic feedback!");
} }
@@ -414,7 +419,7 @@ namespace OCES.Audio
continue; continue;
} }
StartPlayback(source); StartPlayback(source, audioObject.InitialDelay);
RegisterActiveSound(child, source); RegisterActiveSound(child, source);
} }
@@ -466,7 +471,7 @@ namespace OCES.Audio
return; return;
} }
StartPlayback(sourceSingle); StartPlayback(sourceSingle, active.AudioObject.InitialDelay);
RegisterActiveSound(active, sourceSingle, isRegistered); RegisterActiveSound(active, sourceSingle, isRegistered);
} }
IEnumerator RemoveWhenFinished(ActiveSound active) IEnumerator RemoveWhenFinished(ActiveSound active)
@@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace OCES.Audio.HandWritten namespace OCES.Audio
{ {
public class VolumeStepResolver public class VolumeStepResolver
{ {
+6
View File
@@ -16,6 +16,12 @@ public class PlaySoundBind : MonoBehaviour
AudioSystem.Instance.Play(audioId); AudioSystem.Instance.Play(audioId);
} }
public void StopSound()
{
uint.TryParse(this.inputField.text, out uint audioId);
AudioSystem.Instance.Stop(audioId);
}
public void PlaySoundOnTrigger() public void PlaySoundOnTrigger()
{ {
uint.TryParse(this.inputField.text, out uint audioId); uint.TryParse(this.inputField.text, out uint audioId);