This commit is contained in:
zhangzheng
2025-12-31 15:56:05 +08:00
parent f154e98620
commit 189c5016be
15 changed files with 694 additions and 495 deletions

View File

@@ -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)
{

View File

@@ -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<IPanelSystem>().PushQueue<VideoPanel>(parent:AppConfig.GetDefaultMainInstance().transform,packageName:"Main");
this.GetSystem<IPanelSystem>().SendPanelEvent(ModeType.VideoStart,info);
}

View File

@@ -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
/// </summary>
public float delayTime;
/// <summary>
/// 资源包名
/// </summary>
public string packageName;
/// <summary>
/// 资源名
/// </summary>
public string assetName;
/// <summary>
/// 音频
/// </summary>
@@ -58,37 +70,56 @@ namespace Stary.Evo.AudioCore
/// <summary>
/// 3D声音最大距离
/// </summary>
public float audio3DMaxDistance;
/// <summary>
/// 异步取消令牌
/// </summary>
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();
}
}
/// <summary>
/// 音频数据初始化
/// </summary>
/// <param name="audioData"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 音频淡入
/// </summary>
/// <param name="source"></param>
/// <param name="fadeDuration"></param>
/// <returns></returns>
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();
}
}
/// <summary>
/// 音频淡出
/// </summary>
/// <param name="source"></param>
/// <param name="fadeDuration"></param>
/// <returns></returns>
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();
}
}
/// <summary>
/// 播放音效的协程
/// </summary>
/// <param name="source"></param>
/// <param name="delay"></param>
/// <param name="onComplete"></param>
/// <returns></returns>
public async UniTask PlayAudioAWait(AudioSourceToken audioSourceToken, float delay, System.Action onComplete)
{
await PlayAudioAWait(audioSourceToken, delay);
onComplete?.Invoke();
}
/// <summary>
/// 播放音效的协程
/// </summary>
/// <param name="source"></param>
/// <param name="delay"></param>
/// <param name="onComplete"></param>
/// <returns></returns>
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);
}
}
}

View File

@@ -0,0 +1,15 @@
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace Stary.Evo.AudioCore
{
public interface IResources
{
/// <summary>
/// 加载音频资源
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
UniTask<T> LoadAssetAsync<T>(string packageName, string assetName) where T : Object;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 376ffa6a0d374fe69c2c2e30f0f6d244
timeCreated: 1767163427

View File

@@ -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
/// <summary>
/// 对象池
/// </summary>
public interface IAudioSourcePool
{
private Dictionary<string, Queue<GameObject>> poolDict = new Dictionary<string, Queue<GameObject>>();
private GameObject poolObject;
/// <summary>
/// 将对象存进池里
/// </summary>
/// <param name="obj"></param>
void Creation();
/// <summary>
/// 对象池初始化
/// 从未激活池里面取对象,并放入激活池
/// </summary>
private void PoolAwake()
{
// 检查是否已经存在一个名为"AudioSourcePool"的对象
SceneManager.sceneUnloaded += OnSceneUnloaded;
poolObject = GameObject.Find("AudioSourcePool");
if (poolObject == null)
{
// 如果不存在,创建一个新对象
poolObject = new GameObject("AudioSourcePool");
}
// 初始化 Voice 池(只有 1 个)
poolDict["Voice"] = new Queue<GameObject>();
CreateAudioSource("Voice");
// 初始化 Music 池(最多 2 个)
poolDict["Music"] = new Queue<GameObject>();
for (int i = 0; i < 2; i++)
{
CreateAudioSource("Music");
}
// 初始化 SFX 池(初始 1 个,可动态扩展)
poolDict["SFX"] = new Queue<GameObject>();
CreateAudioSource("SFX");
}
/// <summary>
/// 创建对象
/// </summary>
/// <param name="type">定义对象类型</param>
private void CreateAudioSource(string type)
{
GameObject newObject = new GameObject($"AudioSource_{type}");
newObject.transform.SetParent(poolObject.transform); // 将新对象作为当前对象的子对象
newObject.AddComponent<AudioSource>().playOnAwake = false; // 添加 AudioSource 组件并禁用自动播放
if (type == "Music")
{
newObject.GetComponent<AudioSource>().loop = true;
}
poolDict[type].Enqueue(newObject);
}
/// <summary>
/// 获取对象
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
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<AudioSource>();
return audioSource;
}
/// <summary>
/// 从激活池中释放对象到未激活池中
/// </summary>
AudioSourceToken RecycleSpawn(AudioSourceToken source);
/// <summary>
/// 回收对象
/// </summary>
/// <param name="type"></param>
/// <param name="audioObject"></param>
public void ReturnAudioSource(string type, GameObject audioObject)
void RecycleAll();
/// <summary>
/// 清空对象池
/// </summary>
void RemoveAll();
int GetPolLength();
}
public class AudioSourcePool : IAudioSourcePool
{
private GameObject poolObject;
/// <summary>
/// 回收对象的父物体
/// </summary>
private Transform recycle;
protected List<AudioSourceToken> activepool = new List<AudioSourceToken>();
protected Queue<AudioSourceToken> inactivepool = new Queue<AudioSourceToken>();
//没有回收的个数
protected int noRecycleCount;
/// <summary>
/// 对象池物体总数量
/// </summary>
public int poolCount = 0;
/// <summary>
/// 对象池最大数量
/// </summary>
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>();
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}");
}
}
/// <summary>
/// 场景销毁时清空对象池
/// 将对象存进池里
/// </summary>
/// <param name="scene"></param>
void OnSceneUnloaded(Scene scene)
/// <param name="obj"></param>
public void Creation()
{
foreach (var pair in poolDict)
AudioSource source = CreatAudioSource();
if (source == null)
{
Queue<GameObject> queue = pair.Value;
while (queue.Count > 0)
Debug.LogErrorFormat("对象池【{0}】已达最大数量【{1}】,无法创建新对象", poolObject.name, poolSize);
return;
}
// if (type == "Music")
// {
// newObject.GetComponent<AudioSource>().loop = true;
// }
inactivepool.Enqueue(new AudioSourceToken(source, new CancellationTokenSource()));
noRecycleCount++;
}
/// <summary>
/// 从未激活池里面取对象,并放入激活池
/// </summary>
/// <param name="createIfPoolEmpty">如果为空是否new出来</param>
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;
}
/// <summary>
/// 从激活池中释放对象到未激活池中
/// </summary>
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;
}
/// <summary>
/// 回收所有对象
/// </summary>
public void RecycleAll()
{
for (int i = 0; i < activepool.Count; i++)
{
RecycleSpawn(activepool[i]);
}
}
/// <summary>
/// 清空对象池
/// </summary>
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();
}
/// <summary>
/// 创建一个新的AudioSource对象
/// </summary>
/// <param name="obj"></param>
private AudioSource CreatAudioSource()
{
if (poolCount >= poolSize && poolSize > 0)
return null;
poolCount++;
AudioSource source = new GameObject($"{poolObject.name}_{poolCount}").AddComponent<AudioSource>();
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();
}
}
}

View File

@@ -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

View File

@@ -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<CoroutineRunner>();
}
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);
}
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 6f7491906db8d634a8aa1655c3b5621a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
/// <summary>
@@ -21,12 +31,37 @@ namespace Stary.Evo.AudioCore
/// </summary>
/// <param name="audioData">{[clip:音频], [volume:音量], [fadeDuration:自然过渡时间],
/// [is3DAudio:是否3D音频], [audio3DPosition:3D音频位置], [audio3DMaxDistance:3D音频最大距离]}</param>
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<AudioClip>(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;
@@ -62,7 +97,6 @@ namespace Stary.Evo.AudioCore
{
if (audioSource2 == null)
{
audioSource2 = audioSourcePool.GetAudioSource("Music");
audioSource2.clip = audioData.clip;
audioSource2.volume = audioData.volume;
@@ -99,33 +133,27 @@ namespace Stary.Evo.AudioCore
Debug.LogWarning("UnityEvo:已同时存在两个背景乐在切换");
}
}
}
public override UniTask PlayAsync(AudioData audioData)
{
return UniTask.CompletedTask;
}
public override void StopAll()
{
FadeAllMusic();
}
/// <summary>
/// 关闭背景音乐
/// </summary>
/// <param name="audioData">{[fadeDuration:自然过渡时间]}</param>
public override void Stop(AudioData audioData)
public override void Stop()
{
FadeAllMusic();
}
/// <summary>
/// 设置背景音乐音量
/// </summary>
/// <param name="audioData"></param>
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));
}
/// <summary>
/// 设置自然过渡到指定音量
@@ -133,25 +161,26 @@ namespace Stary.Evo.AudioCore
/// <param name="fadeDuration"></param>
/// <param name="targetVolume"></param>
/// <returns></returns>
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
/// <returns></returns>
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);
}
/// <summary>
/// 关闭音频的协程
/// </summary>
/// <param name="source"></param>
/// <param name="fadeDuration"></param>
/// <returns></returns>
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();
}
/// <summary>
/// 开启音频的协程
/// 创建一个新的AudioSource对象
/// </summary>
/// <param name="source"></param>
/// <param name="fadeDuration"></param>
/// <returns></returns>
private IEnumerator FadeInMusic(AudioSource source, float fadeDuration)
/// <param name="obj"></param>
private AudioSource CreatAudioSource(string name, out AudioSource source)
{
float targetVolume = source.volume;
source.volume = 0;
source = new GameObject(name).AddComponent<AudioSource>();
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;
}
}
}

View File

@@ -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<AudioSource> activeSources = new List<AudioSource>(); // 正在播放的 AudioSource 列表
public SFXPlayer(AudioSourcePool audioSourcePool)
public SFXPlayer(IResources resources) : base(resources)
{
this.audioSourcePool = audioSourcePool;
}
/// <summary>
@@ -20,18 +18,57 @@ namespace Stary.Evo.AudioCore
/// <param name="audioData">{[clip:音频], [volume:音量],
/// [onComplete:回调行为], [delayOnCompleteTime:延迟回调执行的时间],
/// [is3DAudio:是否3D音频], [audio3DPosition:3D音频位置], [audio3DMaxDistance:3D音频最大距离]}</param>
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<AudioSourceToken> 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<AudioClip>(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;
@@ -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;
}
/// <summary>
/// 停止所有音效
/// </summary>
/// <param name="audioData">{[无可使用变量]}</param>
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();
}
/// <summary>
/// 播放音效的协程
/// </summary>
/// <param name="source"></param>
/// <param name="delay"></param>
/// <param name="onComplete"></param>
/// <returns></returns>
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();
}
}
}

View File

@@ -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;
}
/// <summary>
@@ -24,80 +22,12 @@ namespace Stary.Evo.AudioCore
/// [onComplete:回调行为], [delayOnCompleteTime:延迟回调执行的时间],
/// [is3DAudio:是否3D音频], [audio3DPosition:3D音频位置],
/// [audio3DMaxDistance:3D音频最大距离]}</param>
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));
}
/// <summary>
/// 停止语音
/// </summary>
/// /// <param name="audioData">{[无可使用变量]}</param>
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);
}
/// <summary>
@@ -108,59 +38,108 @@ namespace Stary.Evo.AudioCore
/// [is3DAudio:是否3D音频], [audio3DPosition:3D音频位置],
/// [audio3DMaxDistance:3D音频最大距离],
/// [cancellationToken:异步取消令牌]}</param>
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<AudioSourceToken> 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<AudioClip>(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;
}
/// <summary>
/// 播放语音的协程
/// 停止语音
/// </summary>
/// <param name="source"></param>
/// <param name="delayOnComplete"></param>
/// <param name="onComplete"></param>
/// <returns></returns>
private IEnumerator PlayVoiceCoroutine(AudioSource source, float delayOnComplete, System.Action onComplete)
/// /// <param name="audioData">{[无可使用变量]}</param>
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();
}
}
}

View File

@@ -2,23 +2,25 @@
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;
private static IResources Resources;
public static void Initialize()
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音频最大距离]}</param>
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);
}
/// <summary>
@@ -45,9 +51,13 @@ namespace Stary.Evo.AudioCore
/// [audio3DMaxDistance:3D音频最大距离],
/// [cancellationToken:异步取消令牌]}</param>
/// <returns></returns>
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
/// </summary>
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音频最大距离]}</param>
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
/// </summary>
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,18 +121,28 @@ namespace Stary.Evo.AudioCore
/// [audio3DMaxDistance:3D音频最大距离]}</param>
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);
}
/// <summary>
/// 设置背景音乐音量
/// Bgm设置自然过渡到指定音量
/// </summary>
/// <param name="audioData">{[volume:音量], [fadeDuration:自然过渡时间]}</param>
public static void SetMusicVolume(AudioData audioData)
/// <param name="fadeDuration"></param>
/// <param name="targetVolume"></param>
/// <returns></returns>
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);
}
@@ -120,10 +152,12 @@ namespace Stary.Evo.AudioCore
/// <param name="fadeDuration">自然过渡时间</param>
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
/// 重新初始化
/// </summary>
/// <returns></returns>
public static void Reinitialize()
public static void Dispose()
{
if (audioSourcePool == null) Initialize();
StopVoice();
StopAllSFX();
StopMusic();
Voice = null;
SFX = null;
Music = null;
}
}
}

View File

@@ -1,7 +1,9 @@
{
"name": "com.audiocore.runtime",
"rootNamespace": "",
"references": [],
"references": [
"GUID:f51ebe6a0ceec4240a699833d6309b23"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,

View File

@@ -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"