Unity开发尽量避免使用MonoBehavior
链接:https://www.zhihu.com/question/31163311/answer/557136453
“尽量避免使用MonoBehavior”,这显然是不对的。
本科阶段我们就学习过设计模式,即良好的程序要尽可能提高聚合性,降低耦合性。而巨量的MonoBehavior有大概率是会大大拉低聚合性,并使逻辑缠绕在一起提高耦合性的。耦合度高的代码不仅难以维护,还会造成巨量性能消耗。
举个栗子吧,比如你需要实现这么一个需求:做一个Left 4 Dead中的打僵尸的FPS射击实现,现在这个需求有以下几个技术点:
物理表现方面:
- 僵尸要有布偶效果和动画互动,不停的奔跑并追杀主角。
- 人物要携带角色控制器,简单的跑跳蹲趴走都要有。
- 主角有枪能发射子弹,主角有刀能砍(与场景互动)。
渲染方面:
- 僵尸数量巨大,需要在SRP中实现一套动画实例化的渲染管线,并靠脚本提交绘制指令。
- 子弹击中僵尸会喷血,击中墙壁会有火花,考虑到Unity现有的粒子效果比较挫,需要自己实现一套基于GPGPU的粒子。
- 僵尸身上喷出的血会溅到墙上,需要实现贴花。
逻辑方面:
- 僵尸状态控制:攻击状态,血量等。
- 人物状态控制:血量,体力等。
这里提供两种实现方案:
第一种:每把枪开一个Component拖到模型上,我们称之为GunController,在主角身上放一个Component负责移动控制,我们称之为PlayerController,每个僵尸身上拖一个Component,自动获取Mesh种类并提交给SRP进行绘制,并且存储着怪物的状态,我们称之为MonsterController, 粒子效果的脚本我们称之为Particle,贴花脚本称之为Decal。
那么这个调用状态将会是这样:GunController触发射线事件 -> 获取MonsterController,计算伤害等 -> 实例化粒子,实例化贴花。而僵尸攻击主角的过程则是MonsterController -> Collider -> PlayerController
与此同时,每帧MonsterController,Particle, Decal等都要依靠Update函数向SRP提交绘制指令, 这套系统还需要提供接口给其他组件,比如UI。
第二种:只开3个Component,一个管枪,一个管主角,剩下一个管所有僵尸。而实现则全部以struct和static class的形式,比如子弹计算,就直接使用静态函数计算射线碰撞等,并且用控制所有僵尸的脚本直接在字典中计算伤害,僵尸的状态则直接依靠脚本单例进行遍历,攻击时也直接向主角脚本发送请求。绘制指令全部统一提交,切枪,换枪时也只是切换枪的模型,动画,射击参数等。
对比这两种办法就能看到第二种只用了三个MonoBehavior, 而第一种则需要用满屏幕的Component,起码光僵尸身上人手一个Component数都数不过来了。而第二种方法从各个方面来讲都比第一种要好不少。首先,开发效率上,第一种需要不停的开脚本不停的拖,而且很难做模块化,因为每一部分都是与其他部分互相依赖的,所以必须在写代码之前祷告一翻,保佑自己没有一部分会出问题,这样一下子就能跑起来了,而第二种方法大多数都是静态的没有依赖的方法,本身可以直接进行单元测试和排查。维护效率上讲,这么一套链式调用过程基本一个月以后就是能跑的起来全看天了,更别提迭代和加功能了,而第二种将数据和逻辑进行模组化控制,版本迭代和需求变更也要容易得多。再说运行性能。MonoBehavior的Update等函数,是需要经过函数指针的,调用效率远不如普通函数,尤其是当数量巨大时这一点表现的更加明显,同时MonoBehavior脚本本身也会产生不菲的GC,最后的结果就是游戏各种GC卡顿,帧数大受影响。相反,反观后者则基本不会在运行时产生新的对象,可以说是0GC压力,甚至多线程并行运算也没问题, 比如僵尸的骨骼动画和状态因为使用统一的脚本控制,是可以直接使用Job System进行多线程优化的,运行性能比前者不知道高到哪里去了。
以前没有开发经验的时候,自己做游戏常常用前一种方法,拖的满屏幕各种Component,结果就是半个月过去望着满屏幕的Component一脸懵B,而现在有了一定的开发经验,在开发时就会刻意注意做好层级分割,到最后最上层的调用层总共剩不下几个MonoBehavior。
总结一下,尽可能少的使用Unity component,开发效率高,多人合作容易,可维护性可扩展性高,更容易做优化且运行效率更高。
赞同 39收起评论
分享
收藏喜欢收起
7 条评论
切换为时间排序
- 2019-09-22
如果僵尸的类型有很多种,是增加component呢,还是在处理僵尸的component里增加逻辑分支?
1回复踩 举报
- (作者) 回复2019-09-22
增加component,但是注意component最好只存序列化数据,逻辑调用应该全局统一控制
1回复踩 举报
知乎用户02-21
第二种方式这样做出来的东西也脱离引擎本身,甚至很容易迁移到其他引擎而不影响逻辑,这种定然是最好的开发方式。然而unity官方的demo很多都是第一种,看着都想吐
赞回复踩 举报
2019-03-04
哎啊这不就是ECS么