GameFramework解析:声音 (Sound)
前言
虽然现在稍微像样点的游戏项目都接上了像Wwise这类音频引擎,大概率用不上这类声音模块,不过GF的声音模块还是非常值得没有游戏音效管理经验的同学学习,声音模块也属于GF中较为轻量的一个模块,本文将简单讲解。
先抛出几个问题:
- 对于同一类型的音效,我希望在设置面板上做统一的音量大小调整,例如游戏设置中常有的BGM、UI、队友语音、场景音效等音量调节,但开发过程中不断有新的音效加入,如何做同一类型音效的音量的统一管理?
- 像RTS这类单位非常多,攻击频率也很高的游戏,如果每次攻击/受击都播放音效,那游戏整体声音将会十分混乱,如何在框架层面控制同个类型的声音的最大同时播放数量?
- 在问题2的基础上,如果我们只播放有限数量的音效,如何加入优先级控制,实现在已达到播放上限时,继续尝试播放音效,如果新播放的音效优先级比当前正在播放的音效要高,那么就顶替掉正在播放的音效?
结构
SoundAgent
SoundAgent是声音代理,在Unity中我们一般使用AudioSource来播放声音,在GF的声音管理下,我们不再自行创建AudioSource来播放声音,而是使用SoundAgent来播放。SoundAgent有一个ISoundAgentHelper接口的字段,这个接口在UGF层上有具体实现类,实现这个接口的类是一个挂载了AudioSource组件的Mono类,它持有了自身GameObject上的AudioSource的引用,并在ISoundAgentHelper接口的方法实现中去调用AudioSource的接口。
当然,游戏业务不会直接访问SoundAgent,而是通过SoundManager直接播放声音,而SoundManager会调用SoundGroup的接口,然后由SoundGroup取得SoundAgent去播放声音。
SoundGroup
1 | /// <summary> |
SoundGroup是本文前言中抛出的3个问题的解决方案的核心实现,上面是SoundGroup中PlaySound的实现代码。
问题1
每个声音播放的时候都会指定一个SoundGroup,SoundGroup有Mute、Volume两个方法来控制这个SoundGroup下每个Agent的静音设置和音量系数。所以我们只需要把不同类型的声音分到不到同组,我们就可以统一控制每个组的整体音量。
问题2
SoundGroup内部以List的形式来储存多个SoundAgent,每次播放声音都会取出一个SoundAgent(把agent的IsPlaying标记置为true),待播放完毕时,才会放回去(把agent的IsPlaying标记置为false),如果该SoundGroup中所有的SoundAgent都在播放中,那么这次播放就有可能会失败,这就解决了问题2中,同一类型(同一个SoundGroup)限制最大同时播放数量的问题。
上面是指有可能播放失败,是因为如果当SoundGroup中所有的SoundAgent都在播放中时,还会比较优先级,这个就是问题3要探讨的内容。
问题3
当播放声音时,SoundGroup会遍历内部的SoundAgent,若当前迭代中的SoundAgent没在播放状态中,则直接使用该SoundAgent来播放,如果处于播放状态,则会对比优先级等内容:
- 若新播放的音效的优先级比该SoundAgent当前播放的音效的优先级要低,则跳过,检测下一个SoundAgent。
- 若新播放的音效的优先级比该SoundAgent当前播放的音效的优先级要高,则把这个SoundAgent作为候选的Agent,后续有可能会用这个SoundAgent来播放新的音效而取代这个SoundAgent的当前音效,具体详见第三点。
- 若新播放的音效的优先级比该SoundAgent当前播放的音效的优先级要相等,这种情况GF提供了m_AvoidBeingReplacedBySamePriority字段,意味避免同优先级取代。
- 当这个字段为true时,那相同优先级的新音效将无法取代正在播放的音效,只能检测下一个SoundAgent。
- 当这个字段为false时,若当前候选SoundAgent是空或者当前候选SoundAgent的开始播放时间点晚于当前遍历的这个SoundAgent的开始播放时间,则把候选SoundAgent更新为当前遍历的SoundAgent,也就是若允许同优先级取代时,会取播放时间最早的SoundAgent来作为最后用来播放新音效的SoundAgent。
注意上述流程若遍历中检测到没有在播放中的SoundAgent时,会直接作为最终播放SoundAgent,中断遍历流程,而检测到SoundAgent正在播放音效时,就算作为候选SoundAgent也不会直接中断遍历流程,而是逐一对比取正在播放的SoundAgent中播放时间最早的一个作为最终播放SoundAgent。
PlaySoundParams
播放参数,对于同一个声音资源,每次播放可以通过传入不同的播放参数,以达到不同的播放效果,参数包括有音量、优先级、静音、播放开始时间、以及一系列音效(Sound Effect)设置等。
SoundManager
外部访问声音模块的入口。
- 对外提供HasSoundGroup、GetSoundGroup、GetAllSoundGroups、AddSoundGroup、AddSoundAgentHelper等对SoundGroup进行查询、操作等接口。
- 对外提供PlaySound、StopSound、StopAllLoadedSounds、PauseSound、ResumeSound等控制声音播放的接口。
关于SoundManager中的资源管理
SoundManager的资源加载卸载与GF的UI模块大同小异(不同的是由于音效资源不需要多个实例,所以SoundManager内部不需要对象池来维护),可以参考本系列的UI解析文章,本文不再赘述,本文开头的UML图中也对此部分进行了简化。
最后
GameFramework解析 系列目录:GameFramework解析:开篇
个人原创,未经授权,谢绝转载!