需求产生:
团队开发过程中,需要给预制节点绑定音效,但音效团队和开发团队之间的沟通协作存在延迟,很容易导致多方编辑同一个预制节点从而产生冲突。因此需要设计并实现一个让音频数据从预制体中独立出来的方案,使得两方工作不会相互干扰。
设计思路:
使用额外的文件专门存储音频配置,并与预制节点一一对应;运行时读取音频配置,给配置中列出的节点挂载音频控制组件,触发对应的操作后,根据组件保存的内容播放相应的音频。由于GameObject.Instantiate拷贝节点时,会同样拷贝其挂载的组件,所以不用担心运行过程中新拷贝的节点丢失音频的问题。
实现:
分为音频编辑器和运行时读取两个部分:
音频编辑器:本文使用Lua脚本作为音频配置文件,通过配置名称与预制节点同名来进行映射(人为规定不要对不同预制体使用相同名称);将操作事件【单击、拖拽、勾选等】对应的脚本名称与音频名称作为LuaTable的k,v值存储在LuaTable中,作为事件列表;将根节点到需要绑定事件的节点路径上的全称【方便调用Find方法】和事件列表打包,以序号为k值保存到LuaTable,最后加上return,确保在require时直接返回该节点的配置。
运行时读取:根据预制体的名称,从指定路径下读取同名lua配置文件,遍历获取到的LuaTable,使用Find查找到绑定了事件的节点,遍历该节点的操作事件列表,K值即需要绑定的控件名称,V值传入该控件作为对应的音频。控件在对应操作事件触发的回调内,添加播放当前控件保存的音频的代码。
具体实现中的技术点:
1.通过反射获取到项目支持的所有操作控件名称
首先,将所有操作控件放到同一个命名空间下,此处为namespace Extension.TouchEvent
以下代码从运行的程序域中查找所有处于指定namespace下的类型,并返回其Type对象
var ass = (
from assembly in AppDomain.CurrentDomain.GetAssemblies ()
where!(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder)
from type in assembly.GetExportedTypes ()
where type.Namespace != null && type.Namespace.CompareTo ("Extension.TouchEvent") == 0
select type
);
随后遍历ass,读取每个Type的name即可。【获取名称接口详见:C# Type类型参考手册】
2.在自定义程序集中通过反射执行默认程序集Assembly-CSharp中的某个函数
需要注意的是,自定义程序集无法直接访问默认程序集,会报找不到的错误,此外,也无法直接通过在Unity中修改当前程序集的Assembly Definition Referances的办法增加对默认程序集的支持(因为不支持默认程序集的添加)
仍然是通过反射的办法,从当前运行的程序域内找到默认程序集Assembly-CSharp,然后在该程序集下拿到函数所在的类。
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies ()) {
if (asm.GetName ().Name == "Assembly-CSharp") {
foreach (var type in asm.ExportedTypes) {
if (type.Name.CompareTo ("xxx_my_class") == 0) {
return type;
}
}
}
}
上文提到了Type的参考手册,其中有一个GetMethod的方法可以通过字符串的方法名称获取对应的方法xxFunction,然后调用xxFunction.Invoke(params),传入该方法所需的参数params,即可执行该方法。
本文通过此方法获取默认程序集中的音频管理类,并调用其音频播放代码播放播放音频。