From 189c5016bed285e1d9bbb9e03e902bc9c1ff65df Mon Sep 17 00:00:00 2001 From: zhangzheng Date: Wed, 31 Dec 2025 15:56:05 +0800 Subject: [PATCH] 1111 --- .../Runtime/VideoSystemPanel/VideoPanel.cs | 6 +- .../Runtime/VideoSystemPanel/VideoSystem.cs | 6 +- .../04.AudioCore/RunTime/Abstract/IAudio.cs | 142 ++++++- .../RunTime/Abstract/IResources.cs | 15 + .../RunTime/Abstract/IResources.cs.meta | 3 + .../RunTime/Base/AudioSourcePool.cs | 352 ++++++++++++------ .../RunTime/Base/AudioSourcePool.cs.meta | 12 +- .../RunTime/Base/CoroutineHelper.cs | 56 --- .../RunTime/Base/CoroutineHelper.cs.meta | 11 - .../04.AudioCore/RunTime/Base/MusicPlayer.cs | 182 +++++---- Assets/04.AudioCore/RunTime/Base/SFXPlayer.cs | 104 +++--- .../04.AudioCore/RunTime/Base/VoicePlayer.cs | 193 +++++----- .../RunTime/Use/AudioCoreManager.cs | 98 +++-- .../RunTime/com.audiocore.runtime.asmdef | 4 +- Assets/04.AudioCore/package.json | 5 +- 15 files changed, 694 insertions(+), 495 deletions(-) create mode 100644 Assets/04.AudioCore/RunTime/Abstract/IResources.cs create mode 100644 Assets/04.AudioCore/RunTime/Abstract/IResources.cs.meta delete mode 100644 Assets/04.AudioCore/RunTime/Base/CoroutineHelper.cs delete mode 100644 Assets/04.AudioCore/RunTime/Base/CoroutineHelper.cs.meta diff --git a/Assets/00.StaryEvoTools/Runtime/VideoSystemPanel/VideoPanel.cs b/Assets/00.StaryEvoTools/Runtime/VideoSystemPanel/VideoPanel.cs index 35ceb45..cf43e0a 100644 --- a/Assets/00.StaryEvoTools/Runtime/VideoSystemPanel/VideoPanel.cs +++ b/Assets/00.StaryEvoTools/Runtime/VideoSystemPanel/VideoPanel.cs @@ -164,11 +164,7 @@ public class VideoPanel : BasePanel private void OnStopMove() { Debug.Log("UnityEvo:视频播放完成"); - AudioCoreManager.SetMusicVolume(new AudioData() - { - fadeDuration = 2f, - volume = 1f, - }); + AudioCoreManager.SetMusicVolume(2f, 1f); _animator.Play("vid_maskDefault", 0, 0); if (renderTexture != null) { diff --git a/Assets/00.StaryEvoTools/Runtime/VideoSystemPanel/VideoSystem.cs b/Assets/00.StaryEvoTools/Runtime/VideoSystemPanel/VideoSystem.cs index 1d0df87..49820ec 100644 --- a/Assets/00.StaryEvoTools/Runtime/VideoSystemPanel/VideoSystem.cs +++ b/Assets/00.StaryEvoTools/Runtime/VideoSystemPanel/VideoSystem.cs @@ -25,11 +25,7 @@ public class VideoSystem : AbstractSystem, IVideoSystem public async void PlayVideo(VideoPanel.VideoInfo info) { - AudioCoreManager.SetMusicVolume(new AudioData() - { - fadeDuration = 2f, - volume = 0f, - }); + AudioCoreManager.SetMusicVolume(2f, 0f); await this.GetSystem().PushQueue(parent:AppConfig.GetDefaultMainInstance().transform,packageName:"Main"); this.GetSystem().SendPanelEvent(ModeType.VideoStart,info); } diff --git a/Assets/04.AudioCore/RunTime/Abstract/IAudio.cs b/Assets/04.AudioCore/RunTime/Abstract/IAudio.cs index 574df9a..09aac42 100644 --- a/Assets/04.AudioCore/RunTime/Abstract/IAudio.cs +++ b/Assets/04.AudioCore/RunTime/Abstract/IAudio.cs @@ -1,6 +1,8 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Threading; +using Cysharp.Threading.Tasks; using UnityEngine; namespace Stary.Evo.AudioCore @@ -15,6 +17,16 @@ namespace Stary.Evo.AudioCore /// public float delayTime; + /// + /// 资源包名 + /// + public string packageName; + + /// + /// 资源名 + /// + public string assetName; + /// /// 音频 /// @@ -44,7 +56,7 @@ namespace Stary.Evo.AudioCore /// 是否不打断上一段对话 /// public bool isNotOverVoice; - + /// /// 是否是3D音频 /// @@ -54,41 +66,60 @@ namespace Stary.Evo.AudioCore /// 3D声音位置 /// public Vector3 audio3DPosition; - + /// /// 3D声音最大距离 /// - public float audio3DMaxDistance; - - /// - /// 异步取消令牌 - /// - public CancellationToken cancellationToken; + public float audio3DMaxDistance; } public interface IAudio { + IResources Resources { get; set; } + AudioSourcePool AudioSourcePool { get; set; } + AudioData Initialize(AudioData audioData); void Play(AudioData audioData); - void Stop(AudioData audioData); + UniTask PlayAsync(AudioData audioData); + UniTask FadeInMusic(AudioSource source, float fadeDuration); + UniTask FadeOutMusic(AudioSource source, float fadeDuration); + void Stop(); - + void StopAll(); } + public abstract class AbstractAudio : IAudio { + public IResources Resources { get; set; } + + public AudioSourcePool AudioSourcePool { get; set; } public abstract void Play(AudioData audioData); - public virtual void Stop(AudioData audioData){} + public abstract UniTask PlayAsync(AudioData audioData); - public virtual void Stop(){} + public abstract void StopAll(); + + public virtual void Stop() + { + } + + protected AbstractAudio(IResources resources) + { + Resources = resources; + if (!GetType().Name.Contains("Music")) + { + AudioSourcePool = new AudioSourcePool(GetType().Name, -1); + AudioSourcePool.Creation(); + } + } /// /// 音频数据初始化 /// /// /// - public virtual AudioData AudioDataInitialize(AudioData audioData) + public virtual AudioData Initialize(AudioData audioData) { if (audioData.volume == 0) { @@ -99,7 +130,88 @@ namespace Stary.Evo.AudioCore { audioData.fadeDuration = 1f; } + return audioData; } + + /// + /// 音频淡入 + /// + /// + /// + /// + public async UniTask FadeInMusic(AudioSource source, float fadeDuration) + { + float targetVolume = source.volume; + source.volume = 0; + + while (source.volume < targetVolume) + { + source.volume += targetVolume * Time.deltaTime / fadeDuration; + await UniTask.Yield(); + } + } + + /// + /// 音频淡出 + /// + /// + /// + /// + public async UniTask FadeOutMusic(AudioSource source, float fadeDuration) + { + if (source != null) + { + float startVolume = source.volume; + + while (source.volume > 0) + { + source.volume -= startVolume * Time.deltaTime / fadeDuration; + await UniTask.Yield(); + } + + source.Stop(); + } + } + + /// + /// 播放音效的协程 + /// + /// + /// + /// + /// + public async UniTask PlayAudioAWait(AudioSourceToken audioSourceToken, float delay, System.Action onComplete) + { + await PlayAudioAWait(audioSourceToken, delay); + onComplete?.Invoke(); + } + + /// + /// 播放音效的协程 + /// + /// + /// + /// + /// + public async UniTask PlayAudioAWait(AudioSourceToken audioSourceToken, float delay) + { + Debug.Log($"PlayAudioAWait:{GetType().Name}"); + if (!GetType().Name.Contains("Voice")) + { + await UniTask.Delay(TimeSpan.FromSeconds(audioSourceToken.source.clip.length - 2f), + cancellationToken: audioSourceToken.cancellationToken.Token); + await FadeOutMusic(audioSourceToken.source, 2f); + await UniTask.Delay(TimeSpan.FromSeconds(delay), + cancellationToken: audioSourceToken.cancellationToken.Token); + } + else + { + await UniTask.Delay(TimeSpan.FromSeconds(audioSourceToken.source.clip.length + delay), + cancellationToken: audioSourceToken.cancellationToken.Token); + } + + AudioSourcePool.RecycleSpawn(audioSourceToken); + } } -} +} \ No newline at end of file diff --git a/Assets/04.AudioCore/RunTime/Abstract/IResources.cs b/Assets/04.AudioCore/RunTime/Abstract/IResources.cs new file mode 100644 index 0000000..c8a02c5 --- /dev/null +++ b/Assets/04.AudioCore/RunTime/Abstract/IResources.cs @@ -0,0 +1,15 @@ +using Cysharp.Threading.Tasks; +using UnityEngine; + +namespace Stary.Evo.AudioCore +{ + public interface IResources + { + /// + /// 加载音频资源 + /// + /// + /// + UniTask LoadAssetAsync(string packageName, string assetName) where T : Object; + } +} \ No newline at end of file diff --git a/Assets/04.AudioCore/RunTime/Abstract/IResources.cs.meta b/Assets/04.AudioCore/RunTime/Abstract/IResources.cs.meta new file mode 100644 index 0000000..17f1115 --- /dev/null +++ b/Assets/04.AudioCore/RunTime/Abstract/IResources.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 376ffa6a0d374fe69c2c2e30f0f6d244 +timeCreated: 1767163427 \ No newline at end of file diff --git a/Assets/04.AudioCore/RunTime/Base/AudioSourcePool.cs b/Assets/04.AudioCore/RunTime/Base/AudioSourcePool.cs index 293698c..ca8f939 100644 --- a/Assets/04.AudioCore/RunTime/Base/AudioSourcePool.cs +++ b/Assets/04.AudioCore/RunTime/Base/AudioSourcePool.cs @@ -1,148 +1,274 @@ using System; using UnityEngine; using System.Collections.Generic; +using System.Threading; using UnityEngine.SceneManagement; namespace Stary.Evo.AudioCore { - public class AudioSourcePool: IDisposable + /// + /// 对象池 + /// + public interface IAudioSourcePool { - private Dictionary> poolDict = new Dictionary>(); - private GameObject poolObject; + /// + /// 将对象存进池里 + /// + /// + void Creation(); /// - /// 对象池初始化 + /// 从未激活池里面取对象,并放入激活池 /// - private void PoolAwake() - { - // 检查是否已经存在一个名为"AudioSourcePool"的对象 - SceneManager.sceneUnloaded += OnSceneUnloaded; - poolObject = GameObject.Find("AudioSourcePool"); - if (poolObject == null) - { - // 如果不存在,创建一个新对象 - poolObject = new GameObject("AudioSourcePool"); - } - - // 初始化 Voice 池(只有 1 个) - poolDict["Voice"] = new Queue(); - CreateAudioSource("Voice"); - - // 初始化 Music 池(最多 2 个) - poolDict["Music"] = new Queue(); - for (int i = 0; i < 2; i++) - { - CreateAudioSource("Music"); - } - - // 初始化 SFX 池(初始 1 个,可动态扩展) - poolDict["SFX"] = new Queue(); - CreateAudioSource("SFX"); - } - - /// - /// 创建对象 - /// - /// 定义对象类型 - private void CreateAudioSource(string type) - { - GameObject newObject = new GameObject($"AudioSource_{type}"); - newObject.transform.SetParent(poolObject.transform); // 将新对象作为当前对象的子对象 - newObject.AddComponent().playOnAwake = false; // 添加 AudioSource 组件并禁用自动播放 - if (type == "Music") - { - newObject.GetComponent().loop = true; - } - poolDict[type].Enqueue(newObject); - } - - /// - /// 获取对象 - /// - /// /// - public AudioSource GetAudioSource(string type) - { - if (poolObject == null) - { - PoolAwake(); - } + AudioSourceToken Spawn(); - if (!poolDict.ContainsKey(type)) - { - Debug.LogError($"UnityEvo:对象池中不存在类型: {type}"); - return null; - } - - if (poolDict[type].Count == 0) - { - // 如果池为空,动态创建新的 GameObject(仅限 SFX 与 Voice) - if (type == "SFX" || type == "Voice") - { - CreateAudioSource(type); - } - else - { - Debug.LogWarning($"UnityEvo:对象池 {type} 已用完,无法分配新的 AudioSource"); - return null; - } - - CreateAudioSource(type); - } - - GameObject audioObject = poolDict[type].Dequeue(); - AudioSource audioSource = audioObject.GetComponent(); - return audioSource; - } + /// + /// 从激活池中释放对象到未激活池中 + /// + AudioSourceToken RecycleSpawn(AudioSourceToken source); /// /// 回收对象 /// - /// - /// - public void ReturnAudioSource(string type, GameObject audioObject) + void RecycleAll(); + + /// + /// 清空对象池 + /// + void RemoveAll(); + + int GetPolLength(); + } + + public class AudioSourcePool : IAudioSourcePool + { + private GameObject poolObject; + + /// + /// 回收对象的父物体 + /// + private Transform recycle; + + protected List activepool = new List(); + + protected Queue inactivepool = new Queue(); + + //没有回收的个数 + protected int noRecycleCount; + + /// + /// 对象池物体总数量 + /// + public int poolCount = 0; + + /// + /// 对象池最大数量 + /// + private int poolSize = 0; + + public AudioSourcePool(string poolName, int poolSize) { - if (!poolDict.ContainsKey(type)) + this.poolSize = poolSize; + poolObject = GameObject.Find(poolName); + if (poolObject == null) { - Debug.LogError($"UnityEvo:对象池中不存在类型: {type}"); - return; + // 如果不存在,创建一个新对象 + poolObject = new GameObject(poolName); } - AudioSource audioSource = audioObject.GetComponent(); - audioSource.Stop(); // 停止播放 - audioSource.clip = null; // 清空音频剪辑 - audioSource.volume = 1f; // 音量大小恢复 - poolDict[type].Enqueue(audioObject); // 回收到对象池 + poolObject = GameObject.Find($"recycle_{poolName}"); + if (poolObject == null) + { + // 如果不存在,创建一个新对象 + poolObject = new GameObject($"recycle_{poolName}"); + } } /// - /// 场景销毁时清空对象池 + /// 将对象存进池里 /// - /// - void OnSceneUnloaded(Scene scene) + /// + public void Creation() { - foreach (var pair in poolDict) + AudioSource source = CreatAudioSource(); + if (source == null) { - Queue queue = pair.Value; - while (queue.Count > 0) + Debug.LogErrorFormat("对象池【{0}】已达最大数量【{1}】,无法创建新对象", poolObject.name, poolSize); + return; + } + + // if (type == "Music") + // { + // newObject.GetComponent().loop = true; + // } + inactivepool.Enqueue(new AudioSourceToken(source, new CancellationTokenSource())); + + noRecycleCount++; + } + + + /// + /// 从未激活池里面取对象,并放入激活池 + /// + /// 如果为空是否new出来 + public AudioSourceToken Spawn() + { + AudioSourceToken audioSourceToken = null; + if (noRecycleCount > 0) + { + audioSourceToken = inactivepool.Dequeue(); + audioSourceToken.source.transform.SetParent(poolObject.gameObject.transform); + noRecycleCount--; + if (audioSourceToken.source == null) + Debug.LogErrorFormat("对象池中不存在此对象【{0}】请排查代码", audioSourceToken.source); + audioSourceToken.cancellationToken = new CancellationTokenSource(); + } + else + { + var source = CreatAudioSource(); + if (source == null) { - GameObject obj = queue.Dequeue(); - if (obj != null) + Debug.LogErrorFormat("对象池【{0}】已达最大数量【{1}】,无法创建新对象", poolObject.name, poolSize); + return null; + } + + audioSourceToken = new AudioSourceToken(source, new CancellationTokenSource()); + } + + audioSourceToken.source.transform.localPosition = Vector3.zero; + audioSourceToken.source.transform.localRotation = Quaternion.identity; + audioSourceToken.source.transform.localScale = Vector3.one; + activepool.Add(audioSourceToken); + + + return audioSourceToken; + } + + /// + /// 从激活池中释放对象到未激活池中 + /// + public AudioSourceToken RecycleSpawn(AudioSourceToken audioSourceToken) + { + if (audioSourceToken.source != null && activepool.Contains(audioSourceToken)) + { + activepool.Remove(audioSourceToken); + inactivepool.Enqueue(audioSourceToken); + audioSourceToken.source.transform.parent = recycle; + audioSourceToken.source.Stop(); // 停止播放 + audioSourceToken.source.clip = null; // 清空音频剪辑 + audioSourceToken.source.volume = 1f; // 音量大小恢复 + noRecycleCount++; + // 释放CancellationToken资源 + audioSourceToken.Dispose(); + } + + return null; + } + + /// + /// 回收所有对象 + /// + public void RecycleAll() + { + for (int i = 0; i < activepool.Count; i++) + { + RecycleSpawn(activepool[i]); + } + } + + /// + /// 清空对象池 + /// + public void RemoveAll() + { + noRecycleCount = 0; + poolCount = 0; + GameObject.Destroy(poolObject); + inactivepool.Clear(); + GameObject.Destroy(recycle.gameObject); + for (int i = 0; i < activepool.Count; i++) + { + RecycleSpawn(activepool[i]); + } + + activepool.Clear(); + } + + /// + /// 创建一个新的AudioSource对象 + /// + /// + private AudioSource CreatAudioSource() + { + if (poolCount >= poolSize && poolSize > 0) + return null; + poolCount++; + AudioSource source = new GameObject($"{poolObject.name}_{poolCount}").AddComponent(); + source.gameObject.transform.SetParent(recycle); + source.transform.SetParent(poolObject.transform); // 将新对象作为当前对象的子对象 + source.playOnAwake = false; // 添加 AudioSource 组件并禁用自动播放 + return source; + } + + public int GetPolLength() + { + return inactivepool.Count; + } + } + + public class AudioSourceToken + { + public AudioSource source; + public CancellationTokenSource cancellationToken; + + public AudioSourceToken(AudioSource source, CancellationTokenSource cancellationToken) + { + this.source = source; + this.cancellationToken = cancellationToken; + } + + // 添加释放方法 + public void Dispose() + { + // 使用局部变量避免多线程环境下的竞态条件 + var cts = Interlocked.Exchange(ref cancellationToken, null); + if (cts != null) + { + try + { + // 取消令牌,触发注册的回调 + cts.Cancel(); + cts.Dispose(); + } + catch (ObjectDisposedException) + { + // 已经被释放,无需处理 + } + catch (AggregateException ex) + { + // 处理注册回调抛出的异常 + // 只记录第一个异常,避免日志过于冗长 + Debug.LogError($"CancellationTokenSource.Cancel() 回调抛出异常: {ex.InnerExceptions[0]}"); + // 可以选择不重新抛出,避免中断释放流程 + } + catch (Exception ex) + { + Debug.LogError($"CancellationTokenSource.Cancel() 抛出异常: {ex}"); + } + finally + { + // 确保始终释放资源 + try { - UnityEngine.Object.Destroy(obj); + cts.Dispose(); + } + catch (ObjectDisposedException) + { + // 已经被释放,无需处理 } } } - poolDict.Clear(); - } - - public void Dispose() - { - if (poolObject != null) - { - GameObject.Destroy(poolObject); - } - poolDict.Clear(); } } } \ No newline at end of file diff --git a/Assets/04.AudioCore/RunTime/Base/AudioSourcePool.cs.meta b/Assets/04.AudioCore/RunTime/Base/AudioSourcePool.cs.meta index ac2b599..05cba4c 100644 --- a/Assets/04.AudioCore/RunTime/Base/AudioSourcePool.cs.meta +++ b/Assets/04.AudioCore/RunTime/Base/AudioSourcePool.cs.meta @@ -1,11 +1,3 @@ fileFormatVersion: 2 -guid: a03bd7e1c53a64843bdc29b9bd9c5bb6 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +guid: 1fdac552e09c46afb9a4116d1ce2fb8e +timeCreated: 1767150802 \ No newline at end of file diff --git a/Assets/04.AudioCore/RunTime/Base/CoroutineHelper.cs b/Assets/04.AudioCore/RunTime/Base/CoroutineHelper.cs deleted file mode 100644 index 19dc7c0..0000000 --- a/Assets/04.AudioCore/RunTime/Base/CoroutineHelper.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections; -using UnityEngine; -using UnityEngine.Internal; - -namespace Stary.Evo.AudioCore -{ - // 音频协程启动器 - public class CoroutineHelper : IDisposable - { - private static CoroutineHelper _instance = new CoroutineHelper(); - public static CoroutineHelper Instance => _instance; - private CoroutineRunner _coroutineRunner; - - private CoroutineHelper() - { - GameObject runnerObject = new GameObject("CoroutineRunner"); - _coroutineRunner= runnerObject.AddComponent(); - } - public Coroutine StartCoroutine(IEnumerator coroutine) - { - Coroutine myCoroutine = _coroutineRunner.StartIEnumerator(coroutine); - return myCoroutine; - } - public void StopCoroutine(Coroutine myCoroutine) - { - _coroutineRunner.StopIEnumerator(myCoroutine); - } - public void Dispose() - { - if (_coroutineRunner != null) - { - GameObject.Destroy(_coroutineRunner.gameObject); - _coroutineRunner = null; - } - if (CoroutineHelper.Instance != null) - { - CoroutineHelper._instance = null; - } - } - - public class CoroutineRunner : MonoBehaviour - { - public Coroutine StartIEnumerator(IEnumerator coroutine) - { - Coroutine myCoroutine = StartCoroutine(coroutine); - return myCoroutine; - } - public void StopIEnumerator(Coroutine myCoroutine) - { - StopCoroutine(myCoroutine); - } - } - } -} - diff --git a/Assets/04.AudioCore/RunTime/Base/CoroutineHelper.cs.meta b/Assets/04.AudioCore/RunTime/Base/CoroutineHelper.cs.meta deleted file mode 100644 index 8a7885c..0000000 --- a/Assets/04.AudioCore/RunTime/Base/CoroutineHelper.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6f7491906db8d634a8aa1655c3b5621a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/04.AudioCore/RunTime/Base/MusicPlayer.cs b/Assets/04.AudioCore/RunTime/Base/MusicPlayer.cs index e532468..9dd1f55 100644 --- a/Assets/04.AudioCore/RunTime/Base/MusicPlayer.cs +++ b/Assets/04.AudioCore/RunTime/Base/MusicPlayer.cs @@ -1,19 +1,29 @@ -using System.Collections; +using System; +using Cysharp.Threading.Tasks; using UnityEngine; +using Object = UnityEngine.Object; namespace Stary.Evo.AudioCore { - public class MusicPlayer : AbstractAudio + public class MusicPlayer : AbstractAudio, IDisposable { - private AudioSourcePool audioSourcePool; + private GameObject poolObject; private AudioSource audioSource1; private AudioSource audioSource2; private AudioSource currentAudioSource; private Coroutine myCoroutine; - public MusicPlayer(AudioSourcePool audioSourcePool) + public MusicPlayer(IResources resources) : base(resources) { - this.audioSourcePool = audioSourcePool; + poolObject = GameObject.Find(GetType().Name); + if (poolObject == null) + { + // 如果不存在,创建一个新对象 + poolObject = new GameObject(GetType().Name); + } + + audioSource1 = CreatAudioSource("audioSource1", out AudioSource source1); + audioSource2 = CreatAudioSource("audioSource2", out AudioSource source2); } /// @@ -21,15 +31,40 @@ namespace Stary.Evo.AudioCore /// /// {[clip:音频], [volume:音量], [fadeDuration:自然过渡时间], /// [is3DAudio:是否3D音频], [audio3DPosition:3D音频位置], [audio3DMaxDistance:3D音频最大距离]} - public override void Play(AudioData audioData) + public override async void Play(AudioData audioData) { - audioData = AudioDataInitialize(audioData); + if (Resources == null) + { + Debug.LogError("AudioCoreManager: Resources is null, please initialize it first."); + return; + } + + audioData = Initialize(audioData); + + if (!string.IsNullOrEmpty(audioData.packageName) && !string.IsNullOrEmpty(audioData.packageName)) + { + var clip = await Resources.LoadAssetAsync(audioData.packageName, audioData.assetName); + if (clip == null) + { + Debug.LogErrorFormat($"从资源包【{audioData.packageName}】加载音频片段【{audioData.assetName}】失败"); + } + else + { + audioData.clip = clip; + } + } + + if (audioData.clip == null) + { + Debug.LogError("播放的音效音频片段为空"); + return; + } + if (audioSource1 == null) { - audioSource1 = audioSourcePool.GetAudioSource("Music"); audioSource1.clip = audioData.clip; audioSource1.volume = audioData.volume; - + // 设置2D与3D音频 if (audioData.is3DAudio) { @@ -53,7 +88,7 @@ namespace Stary.Evo.AudioCore audioSource1.minDistance = 1f; audioSource1.maxDistance = 500f; } - + currentAudioSource = audioSource1; currentAudioSource.Play(); FadeMusic(audioSource1, audioData.fadeDuration, audioSource2); @@ -62,10 +97,9 @@ namespace Stary.Evo.AudioCore { if (audioSource2 == null) { - audioSource2 = audioSourcePool.GetAudioSource("Music"); audioSource2.clip = audioData.clip; audioSource2.volume = audioData.volume; - + // 设置2D与3D音频 if (audioData.is3DAudio) { @@ -89,7 +123,7 @@ namespace Stary.Evo.AudioCore audioSource2.minDistance = 1f; audioSource2.maxDistance = 500f; } - + currentAudioSource = audioSource2; currentAudioSource.Play(); FadeMusic(audioSource2, audioData.fadeDuration, audioSource1); @@ -99,59 +133,54 @@ namespace Stary.Evo.AudioCore Debug.LogWarning("UnityEvo:已同时存在两个背景乐在切换"); } } + } + public override UniTask PlayAsync(AudioData audioData) + { + return UniTask.CompletedTask; + } + + public override void StopAll() + { + FadeAllMusic(); } /// /// 关闭背景音乐 /// /// {[fadeDuration:自然过渡时间]} - public override void Stop(AudioData audioData) + public override void Stop() { FadeAllMusic(); } - /// - /// 设置背景音乐音量 - /// - /// - public void SetMusicVolume(AudioData audioData) - { - if(currentAudioSource == null) return; - - if (myCoroutine != null) - { - CoroutineHelper.Instance.StopCoroutine(myCoroutine); - myCoroutine = null; - } - myCoroutine = CoroutineHelper.Instance.StartCoroutine(SetMusicVolume(audioData.fadeDuration, audioData.volume)); - } - + /// /// 设置自然过渡到指定音量 /// /// /// /// - private IEnumerator SetMusicVolume(float fadeDuration, float targetVolume) + public async UniTask SetMusicVolume(float fadeDuration, float targetVolume) { float startVolume = currentAudioSource.volume; - while (currentAudioSource.volume != targetVolume) + while (!Mathf.Approximately(currentAudioSource.volume, targetVolume)) { if (currentAudioSource.volume > targetVolume) { - currentAudioSource.volume -= (startVolume-targetVolume) * Time.deltaTime / fadeDuration; + currentAudioSource.volume -= (startVolume - targetVolume) * Time.deltaTime / fadeDuration; } else { - currentAudioSource.volume += (targetVolume-startVolume) * Time.deltaTime / fadeDuration; + currentAudioSource.volume += (targetVolume - startVolume) * Time.deltaTime / fadeDuration; } if (Mathf.Abs(currentAudioSource.volume - targetVolume) < 0.01f) { currentAudioSource.volume = targetVolume; } - yield return null; + + await UniTask.Yield(); } } @@ -164,78 +193,39 @@ namespace Stary.Evo.AudioCore /// private void FadeMusic(AudioSource source1, float fadeDuration, AudioSource source2 = null) { - CoroutineHelper.Instance.StartCoroutine(FadeInMusic(source1, fadeDuration)); - - if (source2 != null) - { - CoroutineHelper.Instance.StartCoroutine(FadeOutMusic(source2, fadeDuration)); - } - + FadeInMusic(source1, fadeDuration); + FadeOutMusic(source2, fadeDuration); } - /// - /// 关闭音频的协程 - /// - /// - /// - /// - private IEnumerator FadeOutMusic(AudioSource source, float fadeDuration) - { - if (source == null) yield break; - float startVolume = source.volume; - - while (source.volume > 0) - { - source.volume -= startVolume * Time.deltaTime / fadeDuration; - yield return null; - } - - source.Stop(); - if (audioSource1 == source) - { - audioSource1 = null; - } - else if (audioSource2 == source) - { - audioSource2 = null; - } - audioSourcePool.ReturnAudioSource("Music", source.gameObject); - } private void FadeAllMusic() { - if (currentAudioSource != null && currentAudioSource.isPlaying) - { - if (currentAudioSource == audioSource1) - { - audioSource1 = null; - } - else - { - audioSource2 = null; - } - audioSourcePool.ReturnAudioSource("Music", currentAudioSource.gameObject); - currentAudioSource = null; - } - + currentAudioSource = null; + + audioSource1.Stop(); + audioSource2.Stop(); } /// - /// 开启音频的协程 + /// 创建一个新的AudioSource对象 /// - /// - /// - /// - private IEnumerator FadeInMusic(AudioSource source, float fadeDuration) + /// + private AudioSource CreatAudioSource(string name, out AudioSource source) { - float targetVolume = source.volume; - source.volume = 0; + source = new GameObject(name).AddComponent(); + source.gameObject.transform.SetParent(poolObject.transform); + source.transform.SetParent(poolObject.transform); // 将新对象作为当前对象的子对象 + source.playOnAwake = false; // 添加 AudioSource 组件并禁用自动播放 + return source; + } - while (source.volume < targetVolume) - { - source.volume += targetVolume * Time.deltaTime / fadeDuration; - yield return null; - } + public void Dispose() + { + FadeAllMusic(); + Object.Destroy(audioSource1.gameObject); + Object.Destroy(audioSource2.gameObject); + audioSource1 = null; + audioSource2 = null; } } } \ No newline at end of file diff --git a/Assets/04.AudioCore/RunTime/Base/SFXPlayer.cs b/Assets/04.AudioCore/RunTime/Base/SFXPlayer.cs index 883a673..9ca32b2 100644 --- a/Assets/04.AudioCore/RunTime/Base/SFXPlayer.cs +++ b/Assets/04.AudioCore/RunTime/Base/SFXPlayer.cs @@ -1,17 +1,15 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; +using Cysharp.Threading.Tasks; using UnityEngine; namespace Stary.Evo.AudioCore { - public class SFXPlayer : AbstractAudio + public class SFXPlayer : AbstractAudio, IDisposable { - private AudioSourcePool audioSourcePool; - private List activeSources = new List(); // 正在播放的 AudioSource 列表 - - public SFXPlayer(AudioSourcePool audioSourcePool) + public SFXPlayer(IResources resources) : base(resources) { - this.audioSourcePool = audioSourcePool; } /// @@ -20,21 +18,60 @@ namespace Stary.Evo.AudioCore /// {[clip:音频], [volume:音量], /// [onComplete:回调行为], [delayOnCompleteTime:延迟回调执行的时间], /// [is3DAudio:是否3D音频], [audio3DPosition:3D音频位置], [audio3DMaxDistance:3D音频最大距离]} - public override void Play(AudioData audioData) + public override async void Play(AudioData audioData) { - audioData = AudioDataInitialize(audioData); + AudioSourceToken audioSourceToken = await PlayAudio(audioData); + // 使用协程处理延迟和回调 + PlayAudioAWait(audioSourceToken, audioData.delayOnCompleteTime, + audioData.onComplete); + } + + public override async UniTask PlayAsync(AudioData audioData) + { + AudioSourceToken audioSourceToken = await PlayAudio(audioData); + await PlayAudioAWait(audioSourceToken, audioData.delayOnCompleteTime); + } + + + private async UniTask PlayAudio(AudioData audioData) + { + if (Resources == null) + { + Debug.LogError("AudioCoreManager: Resources is null, please initialize it first."); + return null; + } + + audioData = Initialize(audioData); + if (!string.IsNullOrEmpty(audioData.packageName) && !string.IsNullOrEmpty(audioData.packageName)) + { + var clip = await Resources.LoadAssetAsync(audioData.packageName, audioData.assetName); + if (clip == null) + { + Debug.LogErrorFormat($"从资源包【{audioData.packageName}】加载音频片段【{audioData.assetName}】失败"); + } + else + { + audioData.clip = clip; + } + } + if (audioData.clip == null) { Debug.LogError("播放的音效音频片段为空"); - return; + return null; } - - AudioSource source = audioSourcePool.GetAudioSource("SFX"); - if (source == null) return; + AudioSourceToken audioSourceToken = AudioSourcePool.Spawn(); + if (audioSourceToken == null) + { + Debug.LogError("音效池为空,无法播放音效"); + return null; + } + + AudioSource source = audioSourceToken.source; source.clip = audioData.clip; source.volume = audioData.volume; - + // 设置2D与3D音频 if (audioData.is3DAudio) { @@ -58,46 +95,27 @@ namespace Stary.Evo.AudioCore source.minDistance = 1f; source.maxDistance = 500f; } + source.Play(); - - // 将 AudioSource 加入活动列表 - activeSources.Add(source); - - // 使用协程处理延迟和回调 - CoroutineHelper.Instance.StartCoroutine(PlaySFXCoroutine(source, audioData.delayOnCompleteTime, audioData.onComplete)); + return audioSourceToken; } /// /// 停止所有音效 /// - /// {[无可使用变量]} public override void Stop() { - if (activeSources == null) return; - foreach (var source in activeSources) - { - if (source.isPlaying) - { - source.Stop(); - audioSourcePool.ReturnAudioSource("SFX", source.gameObject); - } - } - activeSources.Clear(); + AudioSourcePool.RecycleAll(); } - /// - /// 播放音效的协程 - /// - /// - /// - /// - /// - private IEnumerator PlaySFXCoroutine(AudioSource source, float delay, System.Action onComplete) + public override void StopAll() { - yield return new WaitForSeconds(source.clip.length + delay); - audioSourcePool.ReturnAudioSource("SFX", source.gameObject); - onComplete?.Invoke(); - activeSources.Remove(source); + AudioSourcePool.RecycleAll(); + } + + public void Dispose() + { + AudioSourcePool.RecycleAll(); } } } \ No newline at end of file diff --git a/Assets/04.AudioCore/RunTime/Base/VoicePlayer.cs b/Assets/04.AudioCore/RunTime/Base/VoicePlayer.cs index 8fec832..16c0da8 100644 --- a/Assets/04.AudioCore/RunTime/Base/VoicePlayer.cs +++ b/Assets/04.AudioCore/RunTime/Base/VoicePlayer.cs @@ -2,19 +2,17 @@ using System.Collections; using System.Threading; using System.Threading.Tasks; +using Cysharp.Threading.Tasks; using UnityEngine; namespace Stary.Evo.AudioCore { - public class VoicePlayer : AbstractAudio + public class VoicePlayer : AbstractAudio, IDisposable { - private AudioSourcePool audioSourcePool; - private AudioSource currentSource; - private Coroutine myCoroutine; - private CancellationTokenSource cancelTokenSource; - public VoicePlayer(AudioSourcePool audioSourcePool) + private AudioSourceToken currentSource; + + public VoicePlayer(IResources resources) : base(resources) { - this.audioSourcePool = audioSourcePool; } /// @@ -24,80 +22,12 @@ namespace Stary.Evo.AudioCore /// [onComplete:回调行为], [delayOnCompleteTime:延迟回调执行的时间], /// [is3DAudio:是否3D音频], [audio3DPosition:3D音频位置], /// [audio3DMaxDistance:3D音频最大距离]} - public override void Play(AudioData audioData) + public override async void Play(AudioData audioData) { - // 停止当前正在播放的语音与旧协程,或旧异步 - Stop(); - - audioData = AudioDataInitialize(audioData); - - if (myCoroutine != null) - { - CoroutineHelper.Instance.StopCoroutine(myCoroutine); - myCoroutine = null; - } - currentSource = audioSourcePool.GetAudioSource("Voice"); - if (currentSource == null) return; - - currentSource.clip = audioData.clip; - currentSource.volume = audioData.volume; - - // 设置2D与3D音频 - if (audioData.is3DAudio) - { - currentSource.transform.position = audioData.audio3DPosition; - currentSource.spatialBlend = 1; - currentSource.minDistance = 1f; - if (audioData.audio3DMaxDistance != 0) - { - currentSource.maxDistance = audioData.audio3DMaxDistance; - } - else - { - // 默认3D最大距离为3米 - currentSource.maxDistance = 3f; - } - } - else - { - currentSource.transform.position = Vector3.zero; - currentSource.spatialBlend = 0; - currentSource.minDistance = 1f; - currentSource.maxDistance = 500f; - } - - currentSource.Play(); - + AudioSourceToken audioSourceToken = await PlayAudio(audioData); // 使用协程处理延迟和回调 - myCoroutine = CoroutineHelper.Instance.StartCoroutine(PlayVoiceCoroutine(currentSource, audioData.delayOnCompleteTime, audioData.onComplete)); - } - - /// - /// 停止语音 - /// - /// /// {[无可使用变量]} - public override void Stop() - { - // 停止当前协程 - if (currentSource != null && currentSource.isPlaying) - { - currentSource.Stop(); - audioSourcePool.ReturnAudioSource("Voice", currentSource.gameObject); - currentSource = null; - if (myCoroutine != null) - { - CoroutineHelper.Instance.StopCoroutine(myCoroutine); - myCoroutine = null; - } - } - - // 停掉异步任务 - if (cancelTokenSource != null && cancelTokenSource.IsCancellationRequested) - { - cancelTokenSource .Cancel(); - cancelTokenSource.Dispose(); - cancelTokenSource = null; - } + PlayAudioAWait(audioSourceToken, + audioData.delayOnCompleteTime, audioData.onComplete); } /// @@ -108,59 +38,108 @@ namespace Stary.Evo.AudioCore /// [is3DAudio:是否3D音频], [audio3DPosition:3D音频位置], /// [audio3DMaxDistance:3D音频最大距离], /// [cancellationToken:异步取消令牌]} - public async Task PlayAsync(AudioData audioData) + public override async UniTask PlayAsync(AudioData audioData) { + AudioSourceToken audioSourceToken = await PlayAudio(audioData); + await PlayAudioAWait(audioSourceToken, audioData.delayOnCompleteTime); + } + + + private async UniTask PlayAudio(AudioData audioData) + { + if (Resources == null) + { + Debug.LogError("AudioCoreManager: Resources is null, please initialize it first."); + return null; + } + // 停止当前正在播放的语音与旧协程,或旧异步 Stop(); - audioData = AudioDataInitialize(audioData); - currentSource = audioSourcePool.GetAudioSource("Voice"); - if (currentSource == null) return; + audioData = Initialize(audioData); + if (!string.IsNullOrEmpty(audioData.packageName) && !string.IsNullOrEmpty(audioData.packageName)) + { + var clip = await Resources.LoadAssetAsync(audioData.packageName, audioData.assetName); + if (clip == null) + { + Debug.LogErrorFormat($"从资源包【{audioData.packageName}】加载音频片段【{audioData.assetName}】失败"); + } + else + { + audioData.clip = clip; + } + } - currentSource.clip = audioData.clip; - currentSource.volume = audioData.volume; + if (audioData.clip == null) + { + Debug.LogError("播放的音效音频片段为空"); + return null; + } + + + AudioSourceToken audioSourceToken = AudioSourcePool.Spawn(); + if (audioSourceToken == null) + { + Debug.LogError("音效池为空,无法播放音效"); + return null; + } + + currentSource = audioSourceToken; + + currentSource.source.clip = audioData.clip; + currentSource.source.volume = audioData.volume; // 设置2D与3D音频 if (audioData.is3DAudio) { - currentSource.transform.position = audioData.audio3DPosition; - currentSource.spatialBlend = 1; - currentSource.minDistance = 1f; + currentSource.source.transform.position = audioData.audio3DPosition; + currentSource.source.spatialBlend = 1; + currentSource.source.minDistance = 1f; if (audioData.audio3DMaxDistance != 0) { - currentSource.maxDistance = audioData.audio3DMaxDistance; + currentSource.source.maxDistance = audioData.audio3DMaxDistance; } else { // 默认3D最大距离为3米 - currentSource.maxDistance = 3f; + currentSource.source.maxDistance = 3f; } } else { - currentSource.transform.position = Vector3.zero; - currentSource.spatialBlend = 0; - currentSource.minDistance = 1f; - currentSource.maxDistance = 500f; + currentSource.source.transform.position = Vector3.zero; + currentSource.source.spatialBlend = 0; + currentSource.source.minDistance = 1f; + currentSource.source.maxDistance = 500f; } - currentSource.Play(); - await Task.Delay(TimeSpan.FromSeconds(audioData.clip.length), audioData.cancellationToken); + currentSource.source.Play(); + + return audioSourceToken; } /// - /// 播放语音的协程 + /// 停止语音 /// - /// - /// - /// - /// - private IEnumerator PlayVoiceCoroutine(AudioSource source, float delayOnComplete, System.Action onComplete) + /// /// {[无可使用变量]} + public override void Stop() { - yield return new WaitForSeconds(source.clip.length + delayOnComplete); - audioSourcePool.ReturnAudioSource("Voice", source.gameObject); - onComplete?.Invoke(); - currentSource = null; - myCoroutine = null; + // 停止当前协程 + if (currentSource != null && currentSource.source.isPlaying) + { + currentSource.source.Stop(); + AudioSourcePool.RecycleSpawn(currentSource); + currentSource = null; + } + } + + public override void StopAll() + { + AudioSourcePool.RecycleAll(); + } + + public void Dispose() + { + AudioSourcePool.RecycleAll(); } } } \ No newline at end of file diff --git a/Assets/04.AudioCore/RunTime/Use/AudioCoreManager.cs b/Assets/04.AudioCore/RunTime/Use/AudioCoreManager.cs index ae1d428..4118990 100644 --- a/Assets/04.AudioCore/RunTime/Use/AudioCoreManager.cs +++ b/Assets/04.AudioCore/RunTime/Use/AudioCoreManager.cs @@ -2,25 +2,27 @@ using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; +using Cysharp.Threading.Tasks; namespace Stary.Evo.AudioCore { public static class AudioCoreManager { - private static AudioSourcePool audioSourcePool; - private static VoicePlayer Voice; - private static SFXPlayer SFX; + private static IAudio Voice; + private static IAudio SFX; private static MusicPlayer Music; - - public static void Initialize() + private static IResources Resources; + + public static void Initialize(IResources resources) { - audioSourcePool = new AudioSourcePool(); // 初始化播放器 - Voice = new VoicePlayer(audioSourcePool); - SFX = new SFXPlayer(audioSourcePool); - Music = new MusicPlayer(audioSourcePool); + Resources = resources; + Voice = new VoicePlayer(Resources); + SFX = new SFXPlayer(Resources); + Music = new MusicPlayer(Resources); + } - + #region 语音 /// @@ -32,8 +34,12 @@ namespace Stary.Evo.AudioCore /// [audio3DMaxDistance:3D音频最大距离]} public static void PlayVoice(AudioData audioData) { - if(Voice == null) Initialize(); - Voice.Play(audioData); + if (Voice == null) + { + Debug.LogError("AudioCoreManager: Voice is null, please initialize it first."); + return; + } + Voice?.Play(audioData); } /// @@ -45,9 +51,13 @@ namespace Stary.Evo.AudioCore /// [audio3DMaxDistance:3D音频最大距离], /// [cancellationToken:异步取消令牌]} /// - public static Task PlayAsyncVoice(AudioData audioData) + public static UniTask PlayAsyncVoice(AudioData audioData) { - if(Voice == null) Initialize(); + if (Voice == null) + { + Debug.LogError("AudioCoreManager: Voice is null, please initialize it first."); + return UniTask.CompletedTask; + } return Voice.PlayAsync(audioData); } @@ -56,7 +66,11 @@ namespace Stary.Evo.AudioCore /// public static void StopVoice() { - if(Voice == null) return; + if (Voice == null) + { + Debug.LogError("AudioCoreManager: Voice is null, please initialize it first."); + return; + } Voice.Stop(); } @@ -73,7 +87,11 @@ namespace Stary.Evo.AudioCore /// [audio3DMaxDistance:3D音频最大距离]} public static void PlaySFX(AudioData audioData) { - if(SFX == null) Initialize(); + if (SFX == null) + { + Debug.LogError("AudioCoreManager: SFX is null, please initialize it first."); + return; + } SFX.Play(audioData); } @@ -82,7 +100,11 @@ namespace Stary.Evo.AudioCore /// public static void StopAllSFX() { - if(SFX == null) return; + if (SFX == null) + { + Debug.LogError("AudioCoreManager: SFX is null, please initialize it first."); + return; + } AudioData audioData = new AudioData(); SFX.Stop(); } @@ -99,31 +121,43 @@ namespace Stary.Evo.AudioCore /// [audio3DMaxDistance:3D音频最大距离]} public static void PlayMusic(AudioData audioData) { - if(Music == null) Initialize(); + if (Music == null) + { + Debug.LogError("AudioCoreManager: Music is null, please initialize it first."); + return; + } Music.Play(audioData); } /// - /// 设置背景音乐音量 + /// Bgm设置自然过渡到指定音量 /// - /// {[volume:音量], [fadeDuration:自然过渡时间]} - public static void SetMusicVolume(AudioData audioData) + /// + /// + /// + public static void SetMusicVolume(float fadeDuration, float targetVolume) { - if(Music == null) return; - Music.SetMusicVolume(audioData); + if (Music == null) + { + Debug.LogError("AudioCoreManager: Music is null, please initialize it first."); + return; + } + Music.SetMusicVolume(fadeDuration, targetVolume); } - + /// /// 停止播放背景音乐 /// /// 自然过渡时间 public static void StopMusic(float fadeDuration = 1f) { - if(Music == null) return; - AudioData audioData = new AudioData(); - audioData.fadeDuration = fadeDuration; - Music.Stop(audioData); + if (Music == null) + { + Debug.LogError("AudioCoreManager: Music is null, please initialize it first."); + return; + } + Music.Stop(); } #endregion @@ -132,14 +166,14 @@ namespace Stary.Evo.AudioCore /// 重新初始化 /// /// - public static void Reinitialize() + public static void Dispose() { - if (audioSourcePool == null) Initialize(); - StopVoice(); StopAllSFX(); StopMusic(); + Voice = null; + SFX = null; + Music = null; } - } } \ No newline at end of file diff --git a/Assets/04.AudioCore/RunTime/com.audiocore.runtime.asmdef b/Assets/04.AudioCore/RunTime/com.audiocore.runtime.asmdef index ba7fecc..24b99b8 100644 --- a/Assets/04.AudioCore/RunTime/com.audiocore.runtime.asmdef +++ b/Assets/04.AudioCore/RunTime/com.audiocore.runtime.asmdef @@ -1,7 +1,9 @@ { "name": "com.audiocore.runtime", "rootNamespace": "", - "references": [], + "references": [ + "GUID:f51ebe6a0ceec4240a699833d6309b23" + ], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, diff --git a/Assets/04.AudioCore/package.json b/Assets/04.AudioCore/package.json index 5a8576e..2e6f031 100644 --- a/Assets/04.AudioCore/package.json +++ b/Assets/04.AudioCore/package.json @@ -1,6 +1,6 @@ { "name": "com.staryevo.audiocore", - "version": "1.0.20", + "version": "1.1.0", "displayName": "04.AudioCore", "description": "音频播放工具", "unity": "2021.3", @@ -9,6 +9,9 @@ "unity", "scirpt" ], + "dependencies": { + "com.cysharp.unitask": "2.5.10" + }, "author": { "name": "staryEvo", "url": "https://www.unity3d.com"