文:721(www.d7gd.com)
ogg格式音乐和mp3音乐相比,一个重要的功能就是音乐循环时并不是从开始位置循环,而是中间有2个循环点,会在播放到第二个循环点时,跳转到第一个循环点播放,降低了循环播放时的割裂感,通常商业游戏都会用这种方法播放BGM,而不是从头开始循环
由于chatgpt产生幻觉告诉我unity能读ogg的循环点(实际上不可以),我研究了半天编辑ogg文件,结果发现并不是我编辑的不对,而是unity读不了,还是要靠代码解决,网上查到相关内容不多,自己写一篇
首先要看你获取的音乐里面有没有内置循环点LOOPSTART,如果是专门素材网站下载的,或者其他游戏(比如RPGMaker)拆包获取的,可能会有,mp3文件肯定没有,这里提供两种方法
1.foobar2000安装vgmstream插件后,右击曲目,选择properties可以查看文件的标签信息
其中LOOPSTART就是循环开始点,LOOPLENGTH为循环长度,LOOPEND(不一定有,数值为前两者相加)为循环结束点,通常为曲目的结尾
2.使用音乐编辑软件Audacity,打开文件后,编辑-元数据编辑器(Edit Metadata),里面也有LOOPSTART和LOOPLENGTH信息
LOOPSTART和LOOPLENGTH的单位是采样点(Sample Position),虽然unity不能直接读ogg文件里的这个标签,但是认采样点,数值通常是6-8位数的数字
如果文件本身没有内置循环点,或者是mp3文件,就要自己听,设置循环点
音乐编辑软件Audacity打开文件,视图-工具栏-选择工具栏(Selection Toolbar),这一项勾选,右下角会显示你当前选区的时间等信息,右击这一块,将显示内容改为采样(Sample) ,这里显示的数值就变成了你选区的采样点,点击你想要循环的地方,记录下这两个LOOPSTART和LOOPEND的采样数值,LOOPLENGTH就是两者的差值,
LOOPEND后面的音乐可以删掉,节省容量,由于我们并不需要unity读ogg信息,所以不转换成ogg,直接用mp3也行,但是ogg可能效果会好一点(chatgpt说mp3循环时容易有卡顿)
unity中代码,播放部分
BGM为音乐的ogg或mp3文件,BGMLoopStart是循环开始点,如果你想从头循环,就填0,把上面获取的数值填进去,BGMLoopEnd是结束点,如果文件里没有,就是LOOPSTART+LOOPLENGTH的值,
我这里还有个音乐淡出淡入的效果,BGMvolume为音量上限,数值在0-1之间,如果不需要淡出淡入,就直接bgmSource.clip = BGM[ID]; bgmSource.Play();
public AudioSource bgmSource;
public AudioClip[] BGM;
public int[] BGMLoopStart,BGMLoopEnd;
float BGMvolume
private int currentBGMID = -1,loopStart=0,loopEnd=0;
private Coroutine currentBGMCoroutine;
public void BGMPlay(int ID){
//如果正在播放当前音乐,则不重新播放
if (bgmSource.clip == BGM[ID] && bgmSource.isPlaying){ return; }
// 如果有正在播放的协程,停止它
if (currentBGMCoroutine != null){
StopCoroutine(currentBGMCoroutine);}
// 开始新的背景音乐播放协程
currentBGMCoroutine = StartCoroutine(PlayBGMWithFade(ID));
}
private IEnumerator PlayBGMWithFade(int ID){
float fadeOutTime = 1.0f; // 淡出时间
float fadeInTime = 1.0f; // 淡入时间
// 如果当前有音乐在播放,先淡出
if (bgmSource.isPlaying)
{
for (float t = 0; t < fadeOutTime; t += Time.unscaledDeltaTime)
{
bgmSource.volume = Mathf.Lerp(BGMvolume, 0.0f, t / fadeOutTime);
yield return null;
}
bgmSource.Stop(); // 停止当前音乐
}
// 设置新的音乐并淡入
bgmSource.clip = BGM[ID];
bgmSource.Play();
for (float t = 0; t < fadeInTime; t += Time.unscaledDeltaTime)
{
bgmSource.volume = Mathf.Lerp(0.0f, BGMvolume, t / fadeInTime);
yield return null;
}
if(BGMLoopStart[ID]>0){
bgmSource.loop = false;
}else{
bgmSource.loop = true;
}
currentBGMID = ID;
loopStart = BGMLoopStart[ID];
loopEnd = BGMLoopEnd[ID];
}
循环部分,BGMLoop()放到Update()中
void BGMLoop(){
// 如果当前有 Loop 信息
if ( currentBGMID > 0 && BGMLoopStart[currentBGMID] > 0)
{
if (bgmSource.isPlaying){
if (bgmSource.timeSamples >= loopEnd||bgmSource.timeSamples >= bgmSource.clip.samples)
{
bgmSource.timeSamples = loopStart;
}
}else{
bgmSource.Play();
bgmSource.timeSamples = loopStart;
}
}
}
如果是用代码控制循环点的音乐,我这里把unity自身的bgmSource.loop关了
因为如果音乐播放完毕,unity会自动从头开始循环,这个优先级高于我的代码
这样写,无论是先到达loopEnd的循环点,还是音乐播放完毕,都会到loopStart位置开始循环