前言
众所周知, Unity自带音效播放没有回调,不能自动播放clip列表; 所以简单实现一个带自动播放功能的接口,用以实现音乐列表的逐个播放.
一. 功能分析
- 首先要求切换场景时音乐不停,只在需要时播放
- 其次即传入音乐名和播放次数,即可将该音乐循环播放指定次数
- 可以直接传入一个包含音乐名和播放次数的列表, 将所有列表中的音乐全部播放指定次数
- 第一点很好解决,只要将挂载
AudioListener
和AudioSource
的节点设置为切换场景不销毁即可 - 第二个功能则需要通过开启协程,通过协程来判断音乐的播放状态, 再传入回调函数, 递归调用播放音乐函数
- 第三个功能在第二个功能的基础上实现,通过传入回调函数并递归实现
二.实现
- 初始化
这里的选择是创建一个AudioSystem
节点挂在Listener
,下面挂着两个节点分别挂载音乐节点和音效节点.
将父节点设置为不因场景跳转销毁的模式.
注意在这里我将Loop
属性关闭, 因为我是通过音乐是否在播放来实现协程检测的.
public class AudioSystem : MonoBehaviour
{
// 单例
static AudioSystem audioSys;
// 音乐播放
static AudioSource musicSource;
// 音效播放
static AudioSource soundSource;
void Awake()
{
DontDestroyOnLoad(gameObject);
}
public static AudioSystem Instance
{
get
{
if(audioSys == null)
{
audioSys = new GameObject("AudioSystem").AddComponent<AudioSystem>();
audioSys.gameObject.AddComponent<AudioListener>();
audioSys.Init();
}
return audioSys;
}
}
void Init()
{
musicSource = new GameObject("musicSource").AddComponent<AudioSource>();
musicSource.transform.SetParent(audioSys.transform);
musicSource.playOnAwake = false;
musicSource.loop = false;
soundSource = new GameObject("soundSource").AddComponent<AudioSource>();
soundSource.transform.SetParent(audioSys.transform);
soundSource.playOnAwake = false;
soundSource.loop = false;
}
}
- 音乐播放
函数传入name
音乐名,loopTime
循环次数,action
回调函数
public void PlayMusic(string name, int loopTime, Action action = null)
通过协程实际上很好实现,AudioSource
中有个属性为IsPlaying
,通过这个属性我们可以随时知道当前是否有音频在播放; 所以直接开一个协程等待即可.
- 主要分为几步:
- 判断是否正在播放该音频,如果是,那么重启一个协程即可,
- 如果不是当前正在播放的音频,则加载需要播放的资源并播放.
- 开启协程时,将循环次数减少,代表还剩多少遍播放.
协程中:
- 先等待音乐播放完
- 播完后判断是否需要循环
- 循环的话就调用
PlayMusic
- 不循环则执行回调
static string nowName = null;
static Coroutine coroutine;
// 播放完成执行acion
public void PlayMusic(string name, int loopTime, Action action = null)
{
if (coroutine != null)
StopCoroutine(coroutine);
if(nowName == name)
{
//正在播放当前资源
if(!musicSource.isPlaying)
musicSource.Play();
}
else
{
//加载资源
AudioClip clip = Resources.Load<AudioClip>(PATH + fname + ".wav");
if(clip == null)
return;
if (musicSource.isPlaying)
musicSource.Stop();
musicSource.clip = clip;
nowName = name;
musicSource.Play();
}
// 开启协程,并将循环次数 - 1
coroutine = StartCoroutine(MusicLoop(name, loopTime - 1, action));
}
IEnumerator MusicLoop(string path, int loopTime, Action action = null)
{
//等待播放完毕
while (musicSource.isPlaying) yield return null;
//播放次数不为0就继续循环
if (loopTime != 0)
PlayMusic(path, loopTime, action);
//播放完毕且有回调则执行
else if(action != null)
action();
}
- 播放列表
这里只要利用回调函数来移动下标就可以了
//播放列表音乐
public void PlayMusicList(List<KeyValuePair<string, int>> list, int index = 0)
{
if (list == null || index >= list.Count) return;
PlayMusic(list[index].Key, list[index].Value, () =>
{
PlayMusicList(list, index + 1);
});
}
三. 完整代码
- 注意销毁协程
public class AudioSystem : MonoBehaviour
{
// 单例
static AudioSystem audioSys;
// 音乐播放
static AudioSource musicSource;
// 音效播放
static AudioSource soundSource;
// 当前音乐名
static string nowName = null;
void Awake()
{
DontDestroyOnLoad(gameObject);
}
// 单例
public static AudioSystem Instance
{
get
{
if(audioSys == null)
{
audioSys = new GameObject("AudioSystem").AddComponent<AudioSystem>();
audioSys.gameObject.AddComponent<AudioListener>();
audioSys.Init();
}
return audioSys;
}
}
// 创建两个子节点
void Init()
{
musicSource = new GameObject("musicSource").AddComponent<AudioSource>();
musicSource.transform.SetParent(audioSys.transform);
musicSource.playOnAwake = false;
musicSource.loop = false;
soundSource = new GameObject("soundSource").AddComponent<AudioSource>();
soundSource.transform.SetParent(audioSys.transform);
soundSource.playOnAwake = false;
soundSource.loop = false;
}
//播放列表音乐
public void PlayMusicList(List<KeyValuePair<string, int>> list, int index = 0)
{
if (list == null || index >= list.Count) return;
PlayMusic(list[index].Key, list[index].Value, () =>
{
PlayMusicList(list, index + 1);
});
}
static Coroutine coroutine;
// 播放完成执行acion
public void PlayMusic(string name, int loopTime, Action action = null)
{
if (coroutine != null)
StopCoroutine(coroutine);
if(nowName == name)
{
//正在播放当前资源
if(!musicSource.isPlaying)
musicSource.Play();
}
else
{
//加载资源
AudioClip clip = Resources.Load<AudioClip>(PATH + fname + ".wav");
if(clip == null)
return;
if (musicSource.isPlaying)
musicSource.Stop();
musicSource.clip = clip;
nowName = name;
musicSource.Play();
}
// 开启协程,并将循环次数 - 1
coroutine = StartCoroutine(MusicLoop(name, loopTime - 1, action));
}
IEnumerator MusicLoop(string path, int loopTime, Action action = null)
{
//等待播放完毕
while (musicSource.isPlaying) yield return null;
//播放次数不为0就继续循环
if (loopTime != 0)
PlayMusic(path, loopTime, action);
//播放完毕且有回调则执行
else if(action != null)
action();
}
public void SetMusicVolume(float volume)
{
musicSource.volume = Mathf.Clamp(volume, 0, 1);
}
public void PlayOneShot(string name)
{
}
private void OnDestroy()
{
if (coroutine != null)
StopCoroutine(coroutine);
}
}