【Unity】如何简单制作一个可跨场景,方便调用的音频播放管理器

前言

​ 在游戏制作中,我们为了提升游戏的试听效果需要搭建起一个差不多的音频管理器。对于老鸟来说这简直手拿把掐,但是对于新手来说可能就有些头大了——对unity的组件了解有限不说,太复杂的学不来,太简陋的又不方便用。因此笔者在这里分享一下在最近gamejam比赛项目中使用到的一个可跨场景,且方便调用,可拓展性强还不复杂的音频播放管理系统(适用于中小型项目)。

配置准备

混音器

​ 在正式开始制作之前,我们得先配置一下当前所需且音频系统不可或缺的东西——混音器

​ 混音器可以帮我们控制不同音频的输出轨道,简单来说就是方便调整BGM(背景音乐)和FX(特效)之间的声音大小关系,至于其他混音效果就交给音乐去做吧——不如说专业的事就得让专业的人干,作为程序给音乐配置了混音器已经是仁至义尽了……

​ 首先我们(win系统的我们)在上方工具栏中依次选中【Window】→【Audio】→【Audio Mixer】调出混音器窗口,然后创建主混音器,这里取名为MainMixer。再在Groups下的Master通道下再根据自己需求创建分支混音组(一般的需求创建BGM和FX也够用了),也可以把BGM和FX的输出音量改一下。

Audio Listener,Audio Clip和Audio Source

在配置完混音器后,我们再来简单了解一下unity中音频播放绕不开的这三位

  • Audio Listener

顾名思义,是用来监听音频的组件,但不需要我们手动添加,因为我们的摄像机上默认挂载了Audio Listener,不用动就行。如果同时挂载了多个摄像机的话可以会有同一场景中有多个Listener的报错

  • Audio Clip

音频片段,是我们需要播放的音频。我们的音频资源导入后就会被unity归类到这个类型里

  • Audio Source

可以理解为播放器,这里有很多播放相关的设置,比如挂载需要播放的音频片段或者是否组件激活就播放的设置之类

开始制作

音频类型(AudioType

我们虽然可以直接播放我们的音频片段,但是这不方便我们管理和调用,所以我们先创建一个音频类型的类。

创建脚本取名为AudioType,代码如下:

using UnityEngine;
using UnityEngine.Audio;

[System.Serializable]	//在Inspector窗口中可见
public class AudioType  //音频类型
{
    [HideInInspector]
    public AudioSource Source;  //音频源(在Inspector中隐藏)
    public AudioClip Clip;  //音频片段
    public AudioMixerGroup MixerGroup;  //音频混音组
    
    public string Name;  //音频名称
    
    [Range(0f, 1f)]
    public float Volume;    //音量(滑动条)
    [Range(0.1f, 5f)]
    public float Pitch;    //音调(滑动条)
    public bool Loop;    //是否循环播放
    public bool PlayOnAwake;    //是否在Awake时自动播放
}

代码解释

这里使用了音频相关的字段,因此需要引用一下UnityEngine.Audio

这里为音频类型提供了音频源(播放器),音频片段,输出混音组,音频名称的自定义设置以及音量,音高,是否循环播放或者在Awake时播放的选项。

不难理解,但看着就觉得可拓展性强。

音频管理器(Audio Manager

我们先创建一个脚本,取名为AudioManager,并使用单例模式

代码如下:

using UnityEngine;
using UnityEngine.Audio;

public class AudioManager : MonoBehaviour
{
    /// <summary>
    /// 获得AudioManager的单例
    /// </summary>
    public static AudioManager instance;
    
    [Header("音频类型")]
    public AudioType[] AudioTypes;  // 音频类型数组,存放需要播放的音频
    [Header("音频设置")]
    public AudioMixer mixer;    // 音频混合器

    private void Awake()
    {
        if (instance != null)
            Destroy(instance.gameObject);
        else
            instance = this;
        
        DontDestroyOnLoad(gameObject);  // 保证切换场景时不被销毁
    }

    private void OnEnable()
    {
        foreach (var type in AudioTypes)    // 遍历AudioTypes数组进行初始化
        {
            type.Source = gameObject.AddComponent<AudioSource>();    // 在调用了AudioManager的脚本的GameObject上添加AudioSource(喇叭)组件
            
            type.Source.name = type.Name;    // 设置AudioSource的名字
            type.Source.volume = type.Volume;    // 音量
            type.Source.pitch = type.Pitch;    // 音调
            type.Source.loop = type.Loop;    // 是否循环播放
            type.Source.playOnAwake = type.PlayOnAwake;    // 是否在Awake时自动播放

            if (type.MixerGroup != null)      // 如果有设置AudioMixerGroup
            {
                type.Source.outputAudioMixerGroup = type.MixerGroup;    // 设置AudioMixerGroup
            }
        }
    }

这部分代码就是AudioManger的核心了。但我们还没播放,暂停和停止的功能。我这里给几个功能模板,了解原理后可以根据自己的需求高度自定义,代码如下:

public void PlayBGM(string name)    // 播放音频时调用
    {
        
        foreach (AudioType type in AudioTypes)    // 遍历AudioTypes数组
        {
            type.Source.Stop();    // 停止所有音频
        }

        foreach (AudioType type in AudioTypes) // 遍历AudioTypes数组
        {
            if (type.Name == name) // 如果找到名字name对应的音频
            {
                type.Source.clip = type.Clip; // 设置音频Clip
                type.Source.Play(); // 播放音频
                return;
            }
        }

        Debug.LogError("没有找到"+name+"音频");    // 没找到音频时输出错误信息
    }
    
    public void PlayBGM(AudioClip clip)    // 播放音频时调用
    {
        
        foreach (AudioType type in AudioTypes)    // 遍历AudioTypes数组
        {
            type.Source.Stop();    // 停止所有音频
        }

        foreach (AudioType type in AudioTypes) // 遍历AudioTypes数组
        {
            if (type.Clip == clip) // 如果找到名字name对应的音频
            {
                type.Source.clip = type.Clip; // 设置音频Clip
                type.Source.Play(); // 播放音频
                return;
            }
        }

        Debug.LogError("没有找到"+clip+"音频");    // 没找到音频时输出错误信息
    }

    public void PlayFX(string name) // 播放音效时调用
    {
        foreach (AudioType type in AudioTypes)    // 遍历AudioTypes数组
        {
            if (type.Name == name)    // 如果找到名字name对应的音频
            {
                type.Source.PlayOneShot(type.Clip);    // 播放音效
                return;
            }
        }
        
        Debug.LogError("没有找到"+name+"音效");    // 没找到音效时输出错误信息
    }
    
    public void Pause(string name)    // 暂停音频时调用
    {
        foreach (AudioType type in AudioTypes)    // 遍历AudioTypes数组
        {
            if (type.Name == name)  
            {
                type.Source.Pause();    // 暂停音频
                return;
            }
        }
        
        Debug.LogError("没有找到"+name+"音频");
    }
    
    public void Stop(string name)    // 停止音频时调用
    {
        foreach (AudioType type in AudioTypes)    // 遍历AudioTypes数组
        {
            if (type.Name == name)
            {
                type.Source.Stop();    // 停止音频
                return;
            }
        }
        
        Debug.LogError("没有找到"+name+"音频");
    }

	public void StopAll()    // 停止所有音频时调用
    {
        foreach (AudioType type in AudioTypes)    // 遍历AudioTypes数组
        {
            type.Source.Stop();    // 停止音频   
        }
        
        Debug.LogError("没有找到"+name+"音频");
    }

    public bool IsPlaying(string name) // 判断音频是否正在播放
    {
        foreach (AudioType type in AudioTypes)
        {
            if (type.Name == name)
            {
                return type.Source.isPlaying;
            }
        }
        
        Debug.LogError("没有找到"+name+"音频");
        return false;
    }

代码解释

我们使用单例模式方便在每个地方随时调用

public声明一个AudioType的数组方便我们在Unity的Inspector窗口下设置,public一个AudioMixer来索引混音器

Awake()里做完声明单例的仪式后,在OnEnable()里遍历AudioTypes数组进行初始化

给每一个音频都添加一个播放器(不知道会不会很吃性能,如果你不想的话可以想想其他办法),然后把他们的基本信息比如这个音频的名字,音量,音高啥的赋值给播放器上的对应属性。但这时不能直接赋值音频片段(clip),不然可能会启用就群魔乱舞般地播放所有音频

功能方法对音频的索引主要靠自己取的名字,也就是Name。BGM是个特例,因为我的场景类型直接挂载了音频片段,所以加载场景时可以直接使用音频片段播放。可以直接在你想播放音频的地方播放,一般情况下的调用是这个样子的:

AudioManager.instance.PlayBGM("主题曲BGM");

我这里的播放方法是BGM和FX分开创建的,因为调用一首BGM时,一般也不可能和另一首BGM共存,所以干脆直接先停掉其他BGM再播放需要的BGM。但音效不一样,同一场景同一时间内,可能会有多个音效存在,所以不必停止其他FX播放。而切换音乐和停止音乐我懒得调试所以没做淡入淡出,有想法的小伙伴可以自己尝试下。

回到Unity

我们回到Unity,在场景中创建一个空物体,取名为Audio Manager,挂载上我们刚写完的AudioManager脚本,对其内容进行设置

都设置好之后就算做好了

结语

这个音频播放系统虽然简陋,但是拓展性很高,我后续也在里面加了几个广播和监听事件来实现场景切换时,切换对应切换场景的背景音乐等功能,也可以用这个系统做一个进入碰撞体后自动播放对应音频的物体。希望这篇文章能帮助到你!

posted @ 2024-12-03 14:34  StaDark  阅读(658)  评论(0)    收藏  举报