7.音效模块

7.音效模块


7.1 知识点

音效模块的作用

在游戏开发中,我们经常需要播放各种各样的音乐和音效:

  • 背景音乐:营造游戏氛围,增强沉浸感
  • 打击音效:攻击、命中时的音效反馈
  • UI音效:按钮点击、界面切换的音效
  • 特效音效:技能释放、爆炸等特效的音效
  • 环境音效:脚步声、环境氛围音等

这些音效会在不同的模块中被调用:技能释放、怪物受伤、角色受伤、副本通关、奖励发放等。

核心问题:

  • 不同模块中播放音效时产生大量冗余代码
  • 音效管理分散,整体结构杂乱
  • 缺乏统一的音效管理机制

解决方案:
通过独立实现一个音效管理模块,专门用于管理和控制游戏中的所有音乐相关功能,提供给外部统一使用。

音效模块的基本原理

设计理念:

  • 单例管理:使用单例模式统一管理音乐和音效
  • 资源加载:通过资源管理器异步加载音频资源
  • 生命周期管理:自动检测音效播放完毕并清理资源
  • 音量控制:独立控制背景音乐和音效的音量
  • 播放状态管理:支持播放、暂停、停止等操作

工作流程:

  1. 背景音乐:创建独立的AudioSource组件,使用DontDestroyOnLoad保证场景切换时不销毁
  2. 音效管理:使用List记录所有正在播放的音效组件
  3. 自动清理:通过FixedUpdate检测音效播放完毕,自动移除和销毁
  4. 音量控制:统一管理背景音乐和音效的音量大小

背景音乐实现

主要功能

背景音乐部分需要实现以下功能:

  1. 播放背景音乐:根据音乐名称异步加载并播放
  2. 停止背景音乐:停止当前播放的背景音乐
  3. 暂停背景音乐:暂停当前播放的背景音乐
  4. 设置背景音乐音量:动态调整背景音乐的音量大小

源码实现:MusicMgr.cs (背景音乐功能)

using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 音乐音效管理器(初步版本 - 仅支持背景音乐)
/// </summary>
public class MusicMgr : Singleton<MusicMgr>
{
    //背景音乐播放组件
    private AudioSource bkMusic = null;

    //背景音乐大小
    private float bkMusicValue = 0.1f;

    private MusicMgr()
    {
    }

    /// <summary>
    /// 播放背景音乐
    /// </summary>
    /// <param name="name">音乐名称</param>
    public void PlayBKMusic(string name)
    {
        //动态创建播放背景音乐的组件 并且 不会过场景移除 
        //保证背景音乐在过场景时也能播放
        if (bkMusic == null)
        {
            GameObject obj = new GameObject();
            obj.name = "BKMusic";
            GameObject.DontDestroyOnLoad(obj);
            bkMusic = obj.AddComponent<AudioSource>();
        }

        //根据传入的背景音乐名字 来播放背景音乐
        ABResMgr.Instance.LoadResAsync<AudioClip>("music", name, (clip) =>
        {
            bkMusic.clip = clip;
            bkMusic.loop = true;
            bkMusic.volume = bkMusicValue;
            bkMusic.Play();
        });
    }

    /// <summary>
    /// 停止背景音乐
    /// </summary>
    public void StopBKMusic()
    {
        if (bkMusic == null)
            return;
        bkMusic.Stop();
    }

    /// <summary>
    /// 暂停背景音乐
    /// </summary>
    public void PauseBKMusic()
    {
        if (bkMusic == null)
            return;
        bkMusic.Pause();
    }

    /// <summary>
    /// 设置背景音乐大小
    /// </summary>
    /// <param name="v">音量值(0-1)</param>
    public void ChangeBKMusicValue(float v)
    {
        bkMusicValue = v;
        if (bkMusic == null)
            return;
        bkMusic.volume = bkMusicValue;
    }
}

音效基础实现

主要功能

音效部分需要实现以下功能:

  1. 播放音效:根据音效名称异步加载并播放
  2. 自动移除播放完成的音效:通过FixedUpdate检测音效播放完毕
  3. 停止指定音效:手动停止正在播放的音效
  4. 设置音效音量:动态调整所有音效的音量大小
  5. 暂停或继续播放所有音效:统一控制所有音效的播放状态

音效和背景音乐的不同

音效和背景音乐不同,需要特别考虑:

  1. 音效数量多:需要管理多个同时播放的音效
  2. 播放状态检测:需要检测音效是否播放完毕
  3. 容器管理:使用容器记录所有正在播放的音效组件

音效类型说明

音效分为循环和非循环两种:

  • 非循环音效:需要自动检测播放完毕并清理
  • 循环音效:需要外部手动管理停止

源码实现:MusicMgr.cs (支持音效管理)

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 音乐音效管理器(支持音效管理 - 存在频繁创建删除问题)
/// </summary>
public class MusicMgr : Singleton<MusicMgr>
{
    //背景音乐播放组件
    private AudioSource bkMusic = null;

    //背景音乐大小
    private float bkMusicValue = 0.1f;

    //用于音效组件依附的对象
    private GameObject soundObj = null;
    //管理正在播放的音效
    private List<AudioSource> soundList = new List<AudioSource>();
    //音效音量大小
    private float soundValue = 0.1f;
    //音效是否在播放
    private bool soundIsPlay = true;

    private MusicMgr()
    {
        MonoMgr.Instance.AddFixedUpdateListener(Update);
    }

    /// <summary>
    /// 音效管理更新
    /// 检测音效是否播放完毕并自动清理
    /// </summary>
    private void Update()
    {
        if (!soundIsPlay)
            return;

        //不停的遍历容器 检测有没有音效播放完毕 播放完了 就移除销毁它
        //为了避免边遍历边移除出问题 我们采用逆向遍历
        for (int i = soundList.Count - 1; i >= 0; --i)
        {
            if (!soundList[i].isPlaying)
            {
                //音效播放完毕,销毁音效组件
                GameObject.Destroy(soundList[i]);
                soundList.RemoveAt(i);
            }
        }
    }

    //播放背景音乐相关方法保持不变...
    /// <summary>
    /// 播放音效
    /// </summary>
    /// <param name="name">音效名字</param>
    /// <param name="isLoop">是否循环</param>
    /// <param name="isSync">是否同步加载</param>
    /// <param name="callBack">加载结束后的回调</param>
    public void PlaySound(string name, bool isLoop = false, bool isSync = false, UnityAction<AudioSource> callBack = null)
    {
        if (soundObj == null)
        {
            //音效依附的对象 一般过场景音效都需要停止 所以我们可以不处理它过场景不移除
            soundObj = new GameObject("soundObj");
        }

        //加载音效资源 进行播放
        ABResMgr.Instance.LoadResAsync<AudioClip>("sound", name, (clip) =>
        {
            AudioSource source = soundObj.AddComponent<AudioSource>();
            source.clip = clip;
            source.loop = isLoop;
            source.volume = soundValue;
            source.Play();
            //存储容器 用于记录 方便之后判断是否停止
            soundList.Add(source);
            //传递给外部使用
            callBack?.Invoke(source);
        }, isSync);
    }

    /// <summary>
    /// 停止播放音效
    /// </summary>
    /// <param name="source">音效组件对象</param>
    public void StopSound(AudioSource source)
    {
        if (soundList.Contains(source))
        {
            //停止播放
            source.Stop();
            //从容器中移除
            soundList.Remove(source);
            //从依附对象上移除
            GameObject.Destroy(source);
        }
    }

    /// <summary>
    /// 改变音效大小
    /// </summary>
    /// <param name="v">音量值(0-1)</param>
    public void ChangeSoundValue(float v)
    {
        soundValue = v;
        for (int i = 0; i < soundList.Count; i++)
        {
            soundList[i].volume = v;
        }
    }

    /// <summary>
    /// 继续播放或者暂停所有音效
    /// </summary>
    /// <param name="isPlay">是否是继续播放 true为播放 false为暂停</param>
    public void PlayOrPauseSound(bool isPlay)
    {
        if (isPlay)
        {
            soundIsPlay = true;
            for (int i = 0; i < soundList.Count; i++)
                soundList[i].Play();
        }
        else
        {
            soundIsPlay = false;
            for (int i = 0; i < soundList.Count; i++)
                soundList[i].Pause();
        }
    }

    //播放背景音乐相关方法保持不变...
}

音效模块优化

之前存在什么问题

通过上面的实现,我们解决了音效管理的基本功能,但发现了一个性能问题:音效组件会频繁的创建和删除,产生大量的内存垃圾,并且频繁创建对象也会带来性能消耗

问题分析:

  1. 内存垃圾:频繁创建和销毁AudioSource组件产生大量垃圾
  2. 性能开销:每次播放音效都需要AddComponent操作
  3. GC压力:大量临时对象增加垃圾回收压力
  4. 过场景:过场景需要清除所有音效的方法,应该提供方法

如何解决频繁创建删除问题

解决方案:
利用对象池(缓存池)对音效组件进行复用,避免频繁的创建和删除。

优化后的源码实现:MusicMgr.cs (使用对象池优化)

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 音乐音效管理器(使用对象池优化)
/// </summary>
public class MusicMgr : Singleton<MusicMgr>
{
    //背景音乐播放组件
    private AudioSource bkMusic = null;

    //背景音乐大小
    private float bkMusicValue = 0.1f;

    //管理正在播放的音效
    private List<AudioSource> soundList = new List<AudioSource>();
    //音效音量大小
    private float soundValue = 0.1f;
    //音效是否在播放
    private bool soundIsPlay = true;

    private MusicMgr()
    {
        MonoMgr.Instance.AddFixedUpdateListener(Update);
    }

    /// <summary>
    /// 音效管理更新
    /// 检测音效是否播放完毕并自动归还到对象池
    /// </summary>
    private void Update()
    {
        if (!soundIsPlay)
            return;

        //不停的遍历容器 检测有没有音效播放完毕 播放完了 就移除销毁它
        //为了避免边遍历边移除出问题 我们采用逆向遍历
        for (int i = soundList.Count - 1; i >= 0; --i)
        {
            if (!soundList[i].isPlaying)
            {
                //音效播放完毕了 不再使用了 我们将这个音效切片置空
                soundList[i].clip = null;
                //归还到对象池
                PoolMgr.Instance.ReturnGameObj(soundList[i].gameObject);
                soundList.RemoveAt(i);
            }
        }
    }

    //播放背景音乐相关方法保持不变...

    /// <summary>
    /// 播放音效
    /// </summary>
    /// <param name="name">音效名字</param>
    /// <param name="isLoop">是否循环</param>
    /// <param name="isSync">是否同步加载</param>
    /// <param name="callBack">加载结束后的回调</param>
    public void PlaySound(string name, bool isLoop = false, bool isSync = false, UnityAction<AudioSource> callBack = null)
    {
        //加载音效资源 进行播放
        ABResMgr.Instance.LoadResAsync<AudioClip>("sound", name, (clip) =>
        {
            //从缓存池中取出音效对象得到对应组件
            AudioSource source = PoolMgr.Instance.GetGameObj("Sound/soundObj").GetComponent<AudioSource>();
            //如果取出来的音效是之前正在使用的 我们先停止它
            source.Stop();

            source.clip = clip;
            source.loop = isLoop;
            source.volume = soundValue;
            source.Play();
            //存储容器 用于记录 方便之后判断是否停止
            //由于从缓存池中取出对象 有可能取出一个之前正在使用的(超上限时)
            //所以我们需要判断 容器中没有记录再去记录 不要重复去添加即可
            if (!soundList.Contains(source))
                soundList.Add(source);
            //传递给外部使用
            callBack?.Invoke(source);
        }, isSync);
    }

    /// <summary>
    /// 停止播放音效
    /// </summary>
    /// <param name="source">音效组件对象</param>
    public void StopSound(AudioSource source)
    {
        if (soundList.Contains(source))
        {
            //停止播放
            source.Stop();
            //从容器中移除
            soundList.Remove(source);
            //不用了 清空切片 避免占用
            source.clip = null;
            //放入缓存池
            PoolMgr.Instance.ReturnGameObj(source.gameObject);
        }
    }

    //改变音效大小和暂停方法保持不变...

3D音效支持

优化思路:
由于我们使用了对象池,因此AudioSource依附的是一个个独立的游戏对象。3D音效主要考虑:

  1. 位置跟随:音效需要依附在对象上并跟随对象移动
  2. 3D参数设置:AudioSource的3D空间音频参数设置

实现方式:

//获取音效组件和它依附的对象
AudioSource source = MusicMgr.Instance.PlaySound("explosion", false, false, (audio) =>
{
    //设置音效的父对象和位置,音效便可以跟随对象移动
    audio.transform.SetParent(targetObject.transform);
    audio.transform.localPosition = Vector3.zero;
    
    //设置3D音效相关参数
    audio.spatialBlend = 1.0f; //3D音效
    audio.rolloffMode = AudioRolloffMode.Logarithmic;
    audio.minDistance = 1f;
    audio.maxDistance = 50f;
});

提供清除所有音效的方法

目前过场景时,音效相关对象会被自动的删除
但是音效管理器中我们的容器还占着引用,我们应该提供方法清空容器

/// <summary>
/// 清空音效相关记录 过场景时在清空缓存池之前去调用它
/// 重要的事情说三遍!!!
/// 过场景时在清空缓存池之前去调用它
/// 过场景时在清空缓存池之前去调用它
/// 过场景时在清空缓存池之前去调用它
/// </summary>
public void ClearSound()
{
    for (int i = 0; i < soundList.Count; i++)
    {
        soundList[i].Stop();
        soundList[i].clip = null;
        PoolMgr.Instance.ReturnGameObj(soundList[i].gameObject);
    }

    //清空音效列表
    soundList.Clear();
}

重要提示: 在场景切换时,必须在清空缓存池之前调用ClearSound()方法清空音效列表,避免引用已被销毁的对象。


7.2 知识点代码

MusicMgr.cs(音效管理器)

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 音乐音效管理器
/// 提供背景音乐和音效的播放、停止、暂停等功能
/// 使用对象池优化音效组件的创建和销毁
/// </summary>
public class MusicMgr : Singleton<MusicMgr>
{
    #region 背景音乐相关字段

    /// <summary>
    /// 背景音乐播放组件
    /// </summary>
    private AudioSource bkMusic = null;

    /// <summary>
    /// 背景音乐大小
    /// </summary>
    private float bkMusicValue = 0.1f;

    #endregion

    #region 音效相关字段

    /// <summary>
    /// 管理正在播放的音效
    /// </summary>
    private List<AudioSource> soundList = new List<AudioSource>();

    /// <summary>
    /// 音效音量大小
    /// </summary>
    private float soundValue = 0.1f;

    /// <summary>
    /// 音效是否在播放
    /// </summary>
    private bool soundIsPlay = true;

    #endregion

    #region 构造函数

    private MusicMgr()
    {
        MonoMgr.Instance.AddFixedUpdateListener(Update);
    }

    #endregion

    #region Unity更新函数

    /// <summary>
    /// 音效管理更新
    /// 检测音效是否播放完毕并自动归还到对象池
    /// </summary>
    private void Update()
    {
        if (!soundIsPlay)
            return;

        //不停的遍历容器 检测有没有音效播放完毕 播放完了 就移除销毁它
        //为了避免边遍历边移除出问题 我们采用逆向遍历
        for (int i = soundList.Count - 1; i >= 0; --i)
        {
            if (!soundList[i].isPlaying)
            {
                //音效播放完毕了 不再使用了 我们将这个音效切片置空
                soundList[i].clip = null;
                //归还到对象池
                PoolMgr.Instance.ReturnGameObj(soundList[i].gameObject);
                soundList.RemoveAt(i);
            }
        }
    }

    #endregion

    #region 背景音乐相关方法

    /// <summary>
    /// 播放背景音乐
    /// </summary>
    /// <param name="name">音乐名称</param>
    public void PlayBKMusic(string name)
    {
        //动态创建播放背景音乐的组件 并且 不会过场景移除 
        //保证背景音乐在过场景时也能播放
        if (bkMusic == null)
        {
            GameObject obj = new GameObject();
            obj.name = "BKMusic";
            GameObject.DontDestroyOnLoad(obj);
            bkMusic = obj.AddComponent<AudioSource>();
        }

        //根据传入的背景音乐名字 来播放背景音乐
        ABResMgr.Instance.LoadResAsync<AudioClip>("music", name, (clip) =>
        {
            bkMusic.clip = clip;
            bkMusic.loop = true;
            bkMusic.volume = bkMusicValue;
            bkMusic.Play();
        });
    }

    /// <summary>
    /// 停止背景音乐
    /// </summary>
    public void StopBKMusic()
    {
        if (bkMusic == null)
            return;
        bkMusic.Stop();
    }

    /// <summary>
    /// 暂停背景音乐
    /// </summary>
    public void PauseBKMusic()
    {
        if (bkMusic == null)
            return;
        bkMusic.Pause();
    }

    /// <summary>
    /// 设置背景音乐大小
    /// </summary>
    /// <param name="v">音量值(0-1)</param>
    public void ChangeBKMusicValue(float v)
    {
        bkMusicValue = v;
        if (bkMusic == null)
            return;
        bkMusic.volume = bkMusicValue;
    }

    #endregion

    #region 音效相关方法

    /// <summary>
    /// 播放音效
    /// </summary>
    /// <param name="name">音效名字</param>
    /// <param name="isLoop">是否循环</param>
    /// <param name="isSync">是否同步加载</param>
    /// <param name="callBack">加载结束后的回调</param>
    public void PlaySound(string name, bool isLoop = false, bool isSync = false, UnityAction<AudioSource> callBack = null)
    {
        //加载音效资源 进行播放
        ABResMgr.Instance.LoadResAsync<AudioClip>("sound", name, (clip) =>
        {
            //从缓存池中取出音效对象得到对应组件
            AudioSource source = PoolMgr.Instance.GetGameObj("Sound/soundObj").GetComponent<AudioSource>();
            //如果取出来的音效是之前正在使用的 我们先停止它
            source.Stop();

            source.clip = clip;
            source.loop = isLoop;
            source.volume = soundValue;
            source.Play();
            //存储容器 用于记录 方便之后判断是否停止
            //由于从缓存池中取出对象 有可能取出一个之前正在使用的(超上限时)
            //所以我们需要判断 容器中没有记录再去记录 不要重复去添加即可
            if (!soundList.Contains(source))
                soundList.Add(source);
            //传递给外部使用
            callBack?.Invoke(source);
        }, isSync);
    }

    /// <summary>
    /// 停止播放音效
    /// </summary>
    /// <param name="source">音效组件对象</param>
    public void StopSound(AudioSource source)
    {
        if (soundList.Contains(source))
        {
            //停止播放
            source.Stop();
            //从容器中移除
            soundList.Remove(source);
            //不用了 清空切片 避免占用
            source.clip = null;
            //放入缓存池
            PoolMgr.Instance.ReturnGameObj(source.gameObject);
        }
    }

    /// <summary>
    /// 改变音效大小
    /// </summary>
    /// <param name="v">音量值(0-1)</param>
    public void ChangeSoundValue(float v)
    {
        soundValue = v;
        for (int i = 0; i < soundList.Count; i++)
        {
            soundList[i].volume = v;
        }
    }

    /// <summary>
    /// 继续播放或者暂停所有音效
    /// </summary>
    /// <param name="isPlay">是否是继续播放 true为播放 false为暂停</param>
    public void PlayOrPauseSound(bool isPlay)
    {
        if (isPlay)
        {
            soundIsPlay = true;
            for (int i = 0; i < soundList.Count; i++)
                soundList[i].Play();
        }
        else
        {
            soundIsPlay = false;
            for (int i = 0; i < soundList.Count; i++)
                soundList[i].Pause();
        }
    }

    /// <summary>
    /// 清空音效相关记录 过场景时在清空缓存池之前去调用它
    /// 重要的事情说三遍!!!
    /// 过场景时在清空缓存池之前去调用它
    /// 过场景时在清空缓存池之前去调用它
    /// 过场景时在清空缓存池之前去调用它
    /// </summary>
    public void ClearSound()
    {
        for (int i = 0; i < soundList.Count; i++)
        {
            soundList[i].Stop();
            soundList[i].clip = null;
            PoolMgr.Instance.ReturnGameObj(soundList[i].gameObject);
        }

        //清空音效列表
        soundList.Clear();
    }

    #endregion
}

MusicManagerUsageTest.cs(音效管理器使用测试)

using UnityEngine;

/// <summary>
/// 音效管理器使用测试
/// 演示音效管理器的完整功能
/// </summary>
public class MusicManagerUsageTest : MonoBehaviour
{
    AudioSource testSoundSource;

    void Start()
    {
        Debug.Log("音效管理器使用测试:开始");

        // 1. 测试背景音乐
        TestBackgroundMusic();

        // 2. 测试音效播放
        Invoke("TestSoundEffects", 2f);

        // 3. 测试音效控制
        Invoke("TestSoundControl", 5f);
    }

    /// <summary>
    /// 测试背景音乐功能
    /// </summary>
    private void TestBackgroundMusic()
    {
        Debug.Log("测试背景音乐");
        
        // 播放背景音乐
        MusicMgr.Instance.PlayBKMusic("bgm1");
        // 输出:背景音乐开始播放

        // 延迟测试暂停和恢复
        Invoke("PauseAndResumeBGM", 3f);

        // 延迟测试音量调整
        Invoke("AdjustBGMVolume", 6f);
    }

    private void PauseAndResumeBGM()
    {
        Debug.Log("暂停背景音乐");
        MusicMgr.Instance.PauseBKMusic();
        // 输出:背景音乐已暂停

        Invoke("ResumeBGM", 2f);
    }

    private void ResumeBGM()
    {
        Debug.Log("恢复背景音乐");
        MusicMgr.Instance.PlayBKMusic("bgm1");
        // 输出:背景音乐恢复播放
    }

    private void AdjustBGMVolume()
    {
        Debug.Log("调整背景音乐音量:0.5");
        MusicMgr.Instance.ChangeBKMusicValue(0.5f);
        // 输出:背景音乐音量已设置为0.5
    }

    /// <summary>
    /// 测试音效播放功能
    /// </summary>
    private void TestSoundEffects()
    {
        Debug.Log("测试音效播放");

        // 播放一次性的音效
        MusicMgr.Instance.PlaySound("explosion", false, false, (source) =>
        {
            Debug.Log("爆炸音效播放完成");
            // 输出:爆炸音效播放完成
        });

        // 播放循环音效
        MusicMgr.Instance.PlaySound("footstep", true, false, (source) =>
        {
            Debug.Log("脚步声开始循环播放");
            testSoundSource = source;
            // 输出:脚步声开始循环播放
        });

        // 播放3D音效
        Invoke("Play3DSound", 1f);
    }

    private void Play3DSound()
    {
        Debug.Log("播放3D音效");
        MusicMgr.Instance.PlaySound("3d_sound", false, false, (source) =>
        {
            // 设置3D音效参数
            source.spatialBlend = 1.0f;
            source.rolloffMode = AudioRolloffMode.Logarithmic;
            source.minDistance = 1f;
            source.maxDistance = 50f;
            
            // 设置音效位置(跟随某个对象)
            source.transform.position = new Vector3(5, 0, 5);
            
            Debug.Log("3D音效已设置并开始播放");
            // 输出:3D音效已设置并开始播放
        });
    }

    /// <summary>
    /// 测试音效控制功能
    /// </summary>
    private void TestSoundControl()
    {
        Debug.Log("测试音效控制");

        // 暂停所有音效
        MusicMgr.Instance.PlayOrPauseSound(false);
        Debug.Log("所有音效已暂停");
        // 输出:所有音效已暂停

        Invoke("ResumeAllSounds", 2f);
        Invoke("StopTestSound", 4f);
        Invoke("TestVolumeControl", 6f);
    }

    private void ResumeAllSounds()
    {
        Debug.Log("恢复所有音效");
        MusicMgr.Instance.PlayOrPauseSound(true);
        // 输出:所有音效已恢复播放
    }

    private void StopTestSound()
    {
        if (testSoundSource != null)
        {
            Debug.Log("停止循环音效");
            MusicMgr.Instance.StopSound(testSoundSource);
            // 输出:循环音效已停止
        }
    }

    private void TestVolumeControl()
    {
        Debug.Log("调整音效音量:0.3");
        MusicMgr.Instance.ChangeSoundValue(0.3f);
        // 输出:所有音效音量已设置为0.3
    }

    void OnDestroy()
    {
        // 场景切换前清空音效
        MusicMgr.Instance.ClearSound();
        Debug.Log("场景切换:已清空所有音效");
    }
}

SceneTransitionTest.cs(场景切换测试)

using UnityEngine;

/// <summary>
/// 场景切换测试
/// 演示场景切换时如何正确处理音效
/// </summary>
public class SceneTransitionTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("场景切换测试:开始");

        // 播放一些音效
        for (int i = 0; i < 5; i++)
        {
            MusicMgr.Instance.PlaySound($"sound{i}", false);
        }
        Debug.Log("已播放5个音效");

        // 延迟切换场景
        Invoke("SwitchScene", 3f);
    }

    private void SwitchScene()
    {
        Debug.Log("准备切换场景");

        // 【重要】必须在清空缓存池之前清空音效列表
        MusicMgr.Instance.ClearSound();
        Debug.Log("已清空音效列表");

        // 然后可以清空缓存池
        // PoolMgr.Instance.ClearPool();

        Debug.Log("场景切换完成");
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏