基于ET的模块化管理及其声音模块
前言
毕设中的demo需要用到声音模块,但是对于一些个人小项目来说Wwise的接入显然是不现实的。因此也根据了另外一个成熟框架-GameFramework中的声音框架进行了对ET的部分适配,对于GF中部分的OOP思路进行了ECS化(组件代替继承,分发代替虚函数),其中底层仍用到了Unity自带的AudioClip、AudioSource等组件。
本文参考了花花的GameFramework解析:声音,并进行了一定的修改。
模块化管理组件(ModuleComponent)
在以前制作demo的过程中我们通常会把不同的全局模块采用统一的单例类管理,方便进行模块的插入、删除、使用、交互,而在ET中我同样采取了这样的思路,并且把这个单例管理类作为Entity插入到Game.Scene内名为”Game”的zoneScene中,其在客户端中仅存在一个,而在服务器中可以管理多个,同时我将一些传统的显示模块,如CameraModule(相机模块)、EffectModule(特效模块)、SceneTreeModule(场景四叉树管理模块)、SoundModule(声音模块)等都加入到这个模块中,方便进行模块的增加、删除、调用。
以下是ModuleComponent的UML图:
- 添加新模块时实现AModuleEventHandler接口并打上ModuleAttribute(Type type)属性,OnLoad(BaseModule module)中对module添加相应的模块Entity,同时在OnRelease(BaseModule module)中移除相应的模块Entity。之后通过在ModuleComponent中调用Register()通过具体类型分发对应的AModuleEventHandler接口实现注入,本质上还是通过接口函数分发的方式来实现多态。
- 在moduleDic中存储已生成模块的Type对应BaseModule,在Get()中通过具体泛型或类型Type获取BaseModule后通过GetComponent<>()的方式获取到对应的实际模块Entity(如CameraModule等)
- 对于BaseModule中具有相应状态枚举来管理当前模块状态,同时在不需要时可以直接移除对应Entity,实现热插拔
模块管理设计方面相对简单,因为模块基本都是同步载入且为热插拔的方式,所以在RegisterMode中没有过多的状态区分。
声音管理模块
主体逻辑部分:
声音管理模块的设计参考了GF中的SoundComponent,通过SoundAgent实现最终的声音播放,同时在SoundModule中将不同的SoundAgent划分在不同的SoundGroup中进行统一管理(如所有背景音乐、效果音乐可以划分在不同SoundGroup中进行分别管理),与此同时,通过代理模式SoundHelper、SoundAgentHelper、SoundGroupHelper实现了对Unity中AudioSource、AudioMixer等的调用解耦,SoundAgent无需得知与Unity音频组件的交互手段,而将其托付给对应Helper实现。
先放上设计过后的UML图:
- 对于SoundGroup、SoundAgent、SoundModule来说实现了Entity和System的分离,同时通过ISoundGroup和ISoundAgent对Entity类的属性接口进行封装
- 在SoundModule中存储SoundGroup以及在SoundGroup中存储SoundAgent时,都通过AddChild<>的方式先将被存储的Entity以child的形式添加到存储Entity中,随后进行赋值,方便管理同时可在父类Entity调用Dispose时即将其存储的所有child Dispose掉并返回Entity引用池中
- 通过PlaySoundParams中具体参数可以在播放时调节播放的时间、位置、绑定的GameObject、声调等
- SoundAgent、SoundGroup具体与Audio组件的交互通过其对应的Helper进行
具体实现部分:
上面封装好了一套规范化的音频调用流程后,我们就需要对SoundGroup、SoundAgent、SoundModule写好对应的Helper,其对应的功能如下:
- ISoundAgentHelper:获取当前是否正在播放、获取声音长度、获取或设置播放位置、静音等,其对应的是AudioSource的调用,实现这个接口的类本质上是一个挂在AudioSource组件的Mono类。
- ISoundGroupHelper:获取当前的音频混合组,对应的是AudioMixerGroup的调用
- ISoundHelper:管理音频资源,进行动态同步、异步加载和释放,其对应的是不同加载情况下的AudioClip的资源管理
具体的UML图如下:
- 将ISoundAgentHelper和ISoundGroupHelper放在Mono层,作为与Unity组件的交互Helper
- 将ISoundHelper放在ET层,内部调用ResourceComponent与Resource类分别实现对AB包音频以及Resource目录下音频的动态加载与释放,其在SoundModule中采用函数分发的方式调用
具体使用:
我们在切换战斗场景结束的回调中测试战斗背景音乐的播放,先设置好对应的场景音乐的参数后调用
设置好预设的AudioMixer,方便通过AudioMixer对所有的处在同一Group的AudioSource进行调节
Play运行,可以看到已经自动放入了对应的AudioMixer的Group中
且在Hierarchy窗口中进行了分层管理:
一些小Tips:
- 在GF框架内部的SoundGroup对其内部所有SoundAgent的管理实际用到AudioMixer的其实基本没有,包括Mute、Volume的设置,都是通过代码内遍历soundAgent对其单独调节实现的,而并非借助AudioMixer暴露接口的方式调节,降低了与AudioMixer间的耦合性。猜测应该是想将AudioMixer的调节尽量放在运行前调节而非运行时。