You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity创建AudioSource循环歌曲,首次循环后出现停顿问题求助

音乐循环切换的首次停顿问题排查与修复建议

问题背景

我需要实现多首歌曲的平滑循环与切换功能:

  • 为每首歌创建独立的AudioSource,所有曲目同时播放,通过调整音量切换当前可听歌曲。
  • 每首歌在57.3秒时创建新的AudioSource实现“重启”,旧AudioSource逐渐降低音量直至淡出(因曲目末尾存在超过57.3秒的淡出段)。

遇到的问题

  • 首次循环结束时,歌曲会略早于预期结束,出现明显停顿,但后续所有循环均正常运行。
  • 若将AudioSource的playOnAwake设为true,而非在createNewAudioSource方法中调用Play(),则完全没有声音,不确定该现象是否与首循环停顿问题相关。

实现代码

using Unity.VisualScripting;
using UnityEngine;

public class MusicManager : MonoBehaviour
{
    [SerializeField] AudioClip[] music;
    // 循环间隔时间,设置为57.3秒
    [SerializeField] float timeToSwitchTracks;

    AudioSource[] musicSources;
    AudioSource[] lastMusicSources;
    // 当前选中的可听歌曲索引
    int selectedMusic = 0;
    float timeSinceLastLoop = 0;


    private void Awake()
    {
        musicSources = new AudioSource[music.Length];
        lastMusicSources = new AudioSource[music.Length];

        if(timeToSwitchTracks <= 0)
        {
            Debug.Log("MusicManager中timeToSwitchTracks设置无效");
        }

        createNewAudioSources();
    }


    private void Update()
    {
        updateVolumes();

        timeSinceLastLoop += Time.deltaTime;

        // 达到循环间隔时间,创建新的AudioSource
        if (timeSinceLastLoop > timeToSwitchTracks)
        {
            createNewAudioSources();
            // 让新AudioSource的音量与旧的保持一致
            setMusicVolume(lastMusicSources[selectedMusic].volume);
        }
    }


    void updateVolumes()
    {
        int sign = 1;

        for(int i = 0; i < musicSources.Length; i++)
        {
            // 选中曲目音量增加,其余曲目音量减少
            sign = i == selectedMusic ? 1 : -1;

            musicSources[i].volume = musicSources[i].volume + (Time.deltaTime * sign);
            // 旧AudioSource逐渐静音
            if (lastMusicSources[i] != null)
            {
                lastMusicSources[i].volume = lastMusicSources[i].volume - Time.deltaTime;
            }
        }
    }


    // 创建并播放新的AudioSource,将旧的存入lastMusicSources用于淡出
    void createNewAudioSources()
    {
        timeSinceLastLoop = 0;

        for (int i = 0; i < musicSources.Length; i++)
        {
            lastMusicSources[i] = musicSources[i];

            musicSources[i] = transform.AddComponent<AudioSource>();
            musicSources[i].clip = music[i];
            musicSources[i].volume = 0;

            musicSources[i].Play();

            // 曲目最长不超过65秒,到时销毁AudioSource
            Destroy(musicSources[i], 65f);
        }
    }


    public void setSelectedMusic(int x)
    {
        selectedMusic = x;
    }


    public void setMusicVolume(float volume)
    {
        musicSources[selectedMusic].volume = volume;
    }
}

我的疑问

我怀疑问题出在timeSinceLastLoop变量与首次AudioSource播放的不同步上,但日志显示循环计时是正确的。我不知道如何检查AudioSource是否在timeSinceLastLoop开始计数前就已运行,也不清楚具体的修复方案。


问题分析与修复方案

1. 首次循环停顿的根源

首次调用createNewAudioSources()是在Awake()中,此时timeSinceLastLoop被设为0,但AudioSource调用Play()后存在微小的启动延迟,而Update()中的计时从Awake()完成后开始累加,导致首次循环时,新AudioSource的播放时机比旧AudioSource的57.3秒节点略晚,旧音频已开始淡出但新音频未及时衔接,出现停顿。

2. 具体修复步骤

(1)对齐首次播放的计时

在createNewAudioSources()中,播放新AudioSource后同步计时,确保与音频播放进度一致:

void createNewAudioSources()
{
    timeSinceLastLoop = 0;

    for (int i = 0; i < musicSources.Length; i++)
    {
        lastMusicSources[i] = musicSources[i];

        musicSources[i] = transform.AddComponent<AudioSource>();
        musicSources[i].clip = music[i];
        musicSources[i].volume = 0;

        musicSources[i].Play();
        // 首次创建时,同步timeSinceLastLoop与音频播放时间
        if (lastMusicSources[i] == null)
        {
            timeSinceLastLoop = musicSources[i].time;
        }

        Destroy(musicSources[i], 65f);
    }
}

(2)优化音量过渡逻辑

原逻辑中音量直接加减Time.deltaTime可能超出0-1范围,需限制区间,同时在旧音频音量为0时直接销毁:

void updateVolumes()
{
    int sign = 1;

    for(int i = 0; i < musicSources.Length; i++)
    {
        sign = i == selectedMusic ? 1 : -1;

        // 限制音量在0-1之间
        float newVolume = Mathf.Clamp(musicSources[i].volume + (Time.deltaTime * sign), 0f, 1f);
        musicSources[i].volume = newVolume;

        if (lastMusicSources[i] != null)
        {
            float newLastVolume = Mathf.Clamp(lastMusicSources[i].volume - Time.deltaTime, 0f, 1f);
            lastMusicSources[i].volume = newLastVolume;
            // 音量为0时销毁旧AudioSource
            if (newLastVolume <= 0f)
            {
                Destroy(lastMusicSources[i]);
                lastMusicSources[i] = null;
            }
        }
    }
}

(3)解决playOnAwake无声音问题

设置playOnAwake = true时,音频会在组件添加时立即播放,但初始音量为0,需在创建后手动触发一次音量调整:

musicSources[i] = transform.AddComponent<AudioSource>();
musicSources[i].clip = music[i];
musicSources[i].volume = 0;
musicSources[i].playOnAwake = true;
// 针对选中曲目,初始化时赋予微小音量
if (i == selectedMusic)
{
    musicSources[i].volume = Mathf.Clamp(Time.deltaTime, 0f, 1f);
}

3. 额外优化建议

  • 用Mathf.Lerp实现平滑音量过渡,替代直接加减,让切换更自然:
    float targetVolume = i == selectedMusic ? 1f : 0f;
    musicSources[i].volume = Mathf.Lerp(musicSources[i].volume, targetVolume, Time.deltaTime * 2f);
    
  • 改用协程处理音量过渡和循环逻辑,减少Update()中的频繁遍历,提升性能。

内容的提问来源于stack exchange,提问作者parakeet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 07:54:53