利用脚本实现全局音效的控制|Unity2D学习日记(二)

引言

个人学习积累中,如有任何问题与错误,欢迎指出与讨论。

这系列将会记录我在搭建自己的2D平台游戏时遇到的一些问题与解决方案,核心目的均为更好的游戏体验与更棒的代码逻辑结构所有代码基于C#与Unity。

正文

恰到好处的音效能够为游戏提供更好的沉浸感。——鲁迅

音效是游戏创造中的重要一环,恰到好处的音效,能够准确的告诉你,主角在“做什么”,又“遭受了什么”,为玩家提供足够的信息。但是如何管理是个问题。

主角扛着几个大音响与数张“唱片”:受伤、跳跃、跑步、攻击……与另一个扛着大音响和唱片的BOSS相遇。他们开启战斗,打着打着,要开启对应的音响,甚至可能还要根据自己的动作切换唱片。

当然这并不是不行,正式游玩时又不会真的有个大音箱挂在主角身上。但当你调试修改代码时,看着Inspector栏里成堆的组件时,你也许会觉得,这并不是一个好办法。那么,有什么更好的解决方法吗?

使用一个脚本实现全局管理,也许是个可行的方法。

惯例,讲一点点的前置小知识。

Component|组件

游戏对象是 Unity Editor 中包含组件的对象。组件定义了该游戏对象的行为。——Unity手册

组件是Unity中最重要的一块内容,脚本也可以作为组件挂载在物体上。我们需要知道的是,组件,也是可以通过脚本在物体上动态挂载(卸载)的。

  • 加载方式组件类型 组件名 = gameobject.AddComponent<组件类型>();
  • 卸载方式Destroy(组件名);

所以,我们可以通过脚本控制音频,在需要播放的时候生成组件(注:查阅网上资料,也有说动态加载对资源的消耗很大,谨慎使用?),并在音乐播放完毕后删除组件。

枚举类与Switch-case语句的组合

这是我个人非常喜欢使用的一个组合,写出来的条例清晰,让人debug时心情愉悦(并不)。

为什么要使用枚举类?

通过枚举类来限制范围,配合代码自动补全,减少出错概率,同时,也提高代码的可读性(只要你不瞎取名)。另外,枚举类里的每个值,本质上是int,所以传入数组时,是以int类型存放的,也正是利用这个,我们可以实现与Switch-case语句的结合,如下:

switch (Enum)
{
    case Enum.Name_1:
        /* 内容 */
        break;
    case Enum.Name_2:
        /* 内容 */
        break;
    case Enum.Name_3:
        /* 内容 */
        break;       
}
    

另外,由于为int值,还可以作为数组等的下标来处理,这方面就留给各位自行研究了。

AudioManager|全局音乐管理类

接下来写我们的脚本吧。为了方便其他脚本快速的调用该类里的内容,我们要使用静态(static)变量,并在一开始就赋值。

/* 无特殊说明,代码都在AudioManger类中 */
public static AudioManager instance;

private void Awake()
{
    // 保证只有一个,丢弃后产生的
    if (instance != null)
    {
        Destroy(this);
        return;
    }
    instance = this;
    DontDestroyOnLoad(gameObject);	// 避免在场景切换时摧毁该脚本所挂载的物体
}

另外,我们还需要准备好唱片(AudioClip)。

/* 简单意思几个,节省篇幅~ */
/* Header("在Inspector里的显示内容"),相当于注释;[SerializeField]用于在Inspector里可视化私有变量,方便赋值 */
[Header("背景音乐")]
[SerializeField] private AudioClip musicClip;	

[Header("玩家音效")]
[SerializeField] private AudioClip runClip_King;

在放歌前,我们还需要做好记录准备,不然局部变量一下子就跑不见了,再找就麻烦了。

private List<AudioGroup> audioSource_Background = new List<AudioGroup>(); 
private List<AudioGroup> audioSource_King = new List<AudioGroup>();

接着,我们要提供一个一键万能按钮。调用它后,会自动生成组件(音响,AudioSource)并播放音效,结束后,自动卸载组件。

/* MusicType为我们的枚举类,target表明对应的物体 */
public void PlayMusic(MusicType musicType, GameObject target)
{
    AudioSource tempS;
    AudioGroup tempAG;
    switch (musicType)
    {
        case MusicType.Background:
            tempS = gameObject.AddComponent<AudioSource>();
            tempS.clip = musicClip;
            tempS.Play();
            tempS.loop = true;			// 背景音乐要循环播放
            tempS.volume = 0.2f;
            tempAG = new AudioGroup(tempS, target);
            audioSource_Background.Add(tempAG);
            /* 背景音乐不需要卸载,一直存在 */
            break;
        case MusicType.Run_King:
            tempS = gameObject.AddComponent<AudioSource>();	// 生成组件
            tempS.clip = runClip_King;	// 确定唱片
            tempS.volume = 0.7f;		// 调整音量
            tempS.Play();				// 播放
            tempAG = new AudioGroup(tempS, target);
            audioSource_King.Add(tempAG);
            StartCoroutine(DeleteAudioAfterPlay(tempAG, audioSource_King));	// 协程,具体见下
            break;
    }
}

/* 等待音效播放完后自动卸载 */
IEnumerator DeleteAudioAfterPlay(AudioGroup ag, List<AudioGroup> agList)
{
    yield return new WaitForSeconds(ag.audioSource.clip.length);	// length获取音频长度,WaitForSeconds(等待时间)
    agList.Remove(ag);
    Destroy(ag.audioSource);	// 卸载组件
}

等等,这里是不是出现了什么奇怪的东西?AudioGroup是什么?

这是我自己定义的一个类(不太喜欢用结构),主要考虑到这样的情况:有多个敌人开着音响,而根据已有的内容无法将敌人与音效一一对应(因为都绑定在AudioManager的物体上)。具体内容见下:(之后有需要,我们也可以扩充这个类的变量)

/* 在AudioManager类之外 */
public class AudioGroup
{
    public AudioSource audioSource;		// 音响
    public GameObject target;			// 对应的物体
    
    /* 构造函数,用于赋值 */
    public AudioGroup()
    {
    }

    public AudioGroup(AudioSource audioS, GameObject t)
    {
        audioSource = audioS;
        target = t;
    }
}

最后,我们只要在合适的地方按这个万能按钮就行了~至于怎么调用,就看你们自己的想法了,写在对应执行的地方或者作为事件放在动画里都是可以的。

/* 在AudioManager类之外,额外写个函数是方便作为事件放在动画里。 */
void RunAudio()
{
    AudioManager.instance.PlayMusic(MusicType.Run_King, gameObject);
}

当然,这种管理方式不仅限于音频管理,各位大可修改后用作其他方式的处理。

后记

这种全局管理的结构,个人相信应该不是最优解,也许在之后学习了更多知识后,会有进一步的优化。这篇文章,就当是提供一种思路吧。另外,我在学习本文相关内容时,借鉴了不少帖子、视频,包括但不限于:

Unity 2D教程:从独立游戏学习开发12: 音效控制(Audio Manager)——M_Studio