unity使用ogg音乐制作loop循环点

文: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位置开始循环