From accd311e1c910bac4b51e683e25fb170493a0519 Mon Sep 17 00:00:00 2001 From: Oliver Wong Date: Fri, 17 Apr 2026 11:20:17 +0800 Subject: [PATCH] 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 --- Assets/Resources/AudioData/AudioObject.bytes | Bin 6526 -> 6605 bytes Assets/Resources/AudioData/MusicPath.bytes | Bin 52 -> 52 bytes .../Resources/AudioData/MusicTransition.bytes | Bin 34 -> 34 bytes Assets/Scenes/SampleScene.unity | 232 +++++++++++++++++- .../Audio/Generated/AudioObjectDefinitions.cs | 2 + .../OCES/Audio/HandWritten/AudioSystem.cs | 14 +- .../HandWritten/LongAudio/ChannelFader.cs | 10 +- .../OCES/Audio/HandWritten/SfxSystem.cs | 61 ++--- .../Audio/HandWritten/VolumeStepResolver.cs | 2 +- Assets/Scripts/OCES/PlaySoundBind.cs | 6 + 10 files changed, 291 insertions(+), 36 deletions(-) diff --git a/Assets/Resources/AudioData/AudioObject.bytes b/Assets/Resources/AudioData/AudioObject.bytes index 9d84c742c73faaf269bfcde3c2846e53996d1ba0..5199504e49493b25be680c9afb43d5ade9d98d72 100644 GIT binary patch delta 147 zcmexobk>;BV=_ss7=ai9z5tnwY#=t{#Es&U zL;1@<3eWOOOf(ap{D(hGLdYwzATcksI90)~G&d==NWs7eq}pNfLni&rGX$P-$a{h` U1F>K*P+?|q23(;rNF7)q0E)^dZ~y=R delta 80 zcmX?W{LhHdeIsKL>*TY%lAC*2KXEWIFidXXTO!H;0*qi1M0^1<8QCU(!E6ZM#2LtE2C@NG5(9n! diff --git a/Assets/Resources/AudioData/MusicTransition.bytes b/Assets/Resources/AudioData/MusicTransition.bytes index b51331cc5762b82d26b6c0c116f7d7974b85b8ba..3f1fb8234d1279381540dd9c1c59303894852710 100644 GIT binary patch literal 34 ZcmZQ%U|_I!;s=s2&|nW_*)uT0_y8X>0g(Uz literal 34 acmZQ%U|_I!;s=rq_6Y3Yz`zI;0s{a-lmk!z diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index e8da5c0..18489a1 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -807,6 +807,154 @@ Transform: - {fileID: 1667985901} m_Father: {fileID: 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 GameObject: m_ObjectHideFlags: 0 @@ -1933,6 +2081,7 @@ RectTransform: - {fileID: 1987857384} - {fileID: 1490886058} - {fileID: 1394234348} + - {fileID: 647197060} - {fileID: 1161878326} - {fileID: 392790004} - {fileID: 1542973983} @@ -2035,6 +2184,85 @@ MonoBehaviour: m_ChildScaleWidth: 0 m_ChildScaleHeight: 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 GameObject: m_ObjectHideFlags: 0 @@ -2376,7 +2604,7 @@ RectTransform: m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {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} --- !u!114 &1987857385 MonoBehaviour: @@ -2599,6 +2827,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 5ce1f814dd5d46d48bc33c18ba11c44c, type: 3} m_Name: m_EditorClassIdentifier: + startWithMusic: 0 + logLevel: 0 --- !u!4 &2093584671 Transform: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/OCES/Audio/Generated/AudioObjectDefinitions.cs b/Assets/Scripts/OCES/Audio/Generated/AudioObjectDefinitions.cs index 4a5fe3f..8aea295 100644 --- a/Assets/Scripts/OCES/Audio/Generated/AudioObjectDefinitions.cs +++ b/Assets/Scripts/OCES/Audio/Generated/AudioObjectDefinitions.cs @@ -66,6 +66,7 @@ public static class AudioObjectDefinitions { "Chinese Number 08", 49 }, { "Chinese Number 09", 49 }, { "Chinese Number 10", 49 }, + { "Japanese Number 02", 51 }, { "Bar", 52 }, { "Beat", 53 }, { "Grid", 54 }, @@ -104,6 +105,7 @@ public static class AudioObjectDefinitions { "au_sfx_ui_button_corePlay_clear_cloud2", 70 }, { "au_sfx_ui_button_corePlay_clear_cloud3", 70 }, { "sfx_notice_test", 71 }, + { "Spanish Number 03", 73 }, }; public static readonly HashSet AmbiguousNames = new() diff --git a/Assets/Scripts/OCES/Audio/HandWritten/AudioSystem.cs b/Assets/Scripts/OCES/Audio/HandWritten/AudioSystem.cs index 0088bca..207a55d 100644 --- a/Assets/Scripts/OCES/Audio/HandWritten/AudioSystem.cs +++ b/Assets/Scripts/OCES/Audio/HandWritten/AudioSystem.cs @@ -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 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) diff --git a/Assets/Scripts/OCES/Audio/HandWritten/LongAudio/ChannelFader.cs b/Assets/Scripts/OCES/Audio/HandWritten/LongAudio/ChannelFader.cs index aa396c4..14b88aa 100644 --- a/Assets/Scripts/OCES/Audio/HandWritten/LongAudio/ChannelFader.cs +++ b/Assets/Scripts/OCES/Audio/HandWritten/LongAudio/ChannelFader.cs @@ -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; diff --git a/Assets/Scripts/OCES/Audio/HandWritten/SfxSystem.cs b/Assets/Scripts/OCES/Audio/HandWritten/SfxSystem.cs index e850127..f5da89a 100644 --- a/Assets/Scripts/OCES/Audio/HandWritten/SfxSystem.cs +++ b/Assets/Scripts/OCES/Audio/HandWritten/SfxSystem.cs @@ -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 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 remainingPool = isRandom ? new List(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) diff --git a/Assets/Scripts/OCES/Audio/HandWritten/VolumeStepResolver.cs b/Assets/Scripts/OCES/Audio/HandWritten/VolumeStepResolver.cs index ae5e8ad..f350f17 100644 --- a/Assets/Scripts/OCES/Audio/HandWritten/VolumeStepResolver.cs +++ b/Assets/Scripts/OCES/Audio/HandWritten/VolumeStepResolver.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using UnityEngine; -namespace OCES.Audio.HandWritten +namespace OCES.Audio { public class VolumeStepResolver { diff --git a/Assets/Scripts/OCES/PlaySoundBind.cs b/Assets/Scripts/OCES/PlaySoundBind.cs index bade47a..4bb5f8c 100644 --- a/Assets/Scripts/OCES/PlaySoundBind.cs +++ b/Assets/Scripts/OCES/PlaySoundBind.cs @@ -15,6 +15,12 @@ public class PlaySoundBind : MonoBehaviour uint.TryParse(this.inputField.text, out uint audioId); AudioSystem.Instance.Play(audioId); } + + public void StopSound() + { + uint.TryParse(this.inputField.text, out uint audioId); + AudioSystem.Instance.Stop(audioId); + } public void PlaySoundOnTrigger() {