不管是拼装积木、搭建故事还是做游戏甚至做建筑,前期很重要的一环就是搭建框架。
快速搭建可以运作的框架,然后一点点地往里面填充干货,不断地调整每一根螺丝钉的位置,以及往这个框架中填充更多的事物,这会大幅提升效率,尤其在重点事项上进展神速,让人叹为观止。把游戏框架搭建好了,那后面的流程就像搭积木一样快乐又高效!
对于游戏开发人员而言游戏框架就像是数学中的公式,它的好处不言而喻:能让开发者更加从容的面对复杂多变的需求,让代码的结构更加清晰,具有更好的延展性和交接性……
但“越是通用的代码越是无用的代码”,框架之上,更重要的也许是设计理念。如何在代码的通用性和针对性中找到一个平衡,这是很多程序开发者面临的问题。
在本期的技术分享会上,炽天工作室的程序猿洋哥带来了《游戏框架简析》,手把手教你如何在框架的通用性和针对性中找到平衡,帮助大家不断优化工作方法、提升工作效率,搭建自己的游戏框架!
Part1. 框架的概念
1.什么是框架
框架的第一含义是一个骨架,它封装了某领域内处理流程的控制逻辑,所以我们经常说框架是一个半成品的应用。由于领域的种类之多,所以框架必须具有针对性,比如专门用于解决底层通信的框架,或专门用于某种游戏类型的框架。
框架中也包含了很多元素,但是这些元素之间关系的紧密程度要远远大于类库中元素之间的关系。框架中的所有元素都为了实现一个共同目标而相互协作。
没有一个万能的框架可以应用于所有种类的领域和应用,框架的目标性非常强,它专注于解决某一特定领域的问题,并致力于这一特定领域提供通用的解决方案。
2.框架和类库的区别
1)从结构上说,框架是高内聚的,而类库内部则是相对松散的;
2)框架封装了处理流程的控制逻辑,而类库几乎不涉及任何处理流程和控制逻辑;
3)框架专注于特定领域,而类库却是通用的;
4)框架通常建立在众多类库的基础之上,而类库一般不依赖于某框架。
3.框架设计
框架使得我们开发应用的速度更快、质量更高、成本更低,这些好处是不言而喻的。
框架源于应用,却又高于应用。框架往往是在我们拥有了开发某种类型应用的大量经验(俗称代码量),总结这种类型应用中共性的东西,将其提炼到一个“高层次”中,以备复用。这个“高层次”的东西便是框架的原型。随着经验的不断积累,框架也会随之不断地完善和发展。
所以框架是一个实践的产物,而不是理论研究出来的。设计好一个框架最好的方法就是从一个具体的应用开始,以提供同一类型应用的通用解决方案为目标,不断地从具体应用中提炼和萃取框架,然后反哺该类型的应用。
有一点需要特别注意,框架的设计的要点在于权衡,正如前面提到,框架在为应用提供一个骨架的同时,也给应用圈定了一个框框,我们只能在这有限的天地内来发挥。
所以一个好的框架设计应当采用一个非常恰当的权衡决策,以使框架在为应用提供强大支持的同时,又对应用作更少的限制、权衡。框架的设计从来就不是一件简单的事情,但是有很多框架设计的经验可以供我们参考。
4.框架的设计经验和原则
5.什么是优秀的框架
Part2. 框架的应用
1.设计最初的思考
1)kiss原则:
Keep it Simple and Stupid,源于《UNIX编程艺术》中总结的。简单地理解这句话,就是要把一个产品做的连白痴都会用,因而也被称为“懒人原则”。换句话来说,“简单就是美”。
简单是软件设计的目标,简单的代码,占用时间少,漏洞少,并且易于修改。其核心要素是:
拆分:把复杂的逻辑拆分为一个个单一的执行单元;
简洁:只允许串型依赖的调用关系。串型依赖:一个依赖一个的形式,例如人依赖车,车依赖零件,零件依赖原料,原料依赖产地;
简单:单个执行单元代码量一定要尽可能少。
如何在工作中践行kiss原则:
2) 单一的update:
在Unity内部,Unity会跟踪感兴趣列表中的对象回调(例如Update、FixedUpdate、LateUpdate等)。这些列表以侵入式链接列表的形式进行维护,从而确保在固定事件进行列表更新。在启用或者禁用MonoBehaviour时分别会在这些列表中添加/删除MonoBehaviour。
虽然直接将适当的回调添加到需要它们的MonoBehaviour十分方便,但随着回调数量的增加,这种方式将变得越来越低效。从原生代码调用托管代码回调有一个很小但很明显的开销。这会导致在调用大量每帧都执行的方法时延长帧时间,而且在实例化包含大量MonoBehaviour的预制件时延长实例化时间。(注意:实例化成本归因于调用预制件中每个组件上的Awake和OnEnable回调时产生的性能开销)
当具有每帧回调的MonoBehaviour数量增长到数百或者数千时,删除这些回调并将MonoBehaviour(甚至标准C#对象)连接到全局管理器单例可以优化性能。然后,全局管理器单例可以将Update、LateUpdate和其他回调分发给感兴趣的对象。这种方式的另一个好处是允许代码在回调没有操作的情况下巧妙地将回调取消订阅,从而减少每帧必须调用的大量函数。
注:参考文档https://docs.unity.cn/cn/current/Manual/BestPracticeUnderstandingPerformanceInUnity8.html
并且,Unity自带的Update的执行顺序是按照Mono的加载顺序执行的,虽然可以通过Script Execution Order来控制每个脚本的执行顺序,但在Mono过多时通过执行顺序来控制Mono的顺序显得过于混乱繁琐,当同一时刻创建多个Mono的时候无法很有效的对Update的执行顺序进行管理。
单一的Update不仅可以有效的减少性能和空间的开销,还能更方便的控制时序和游戏调速。
3. 网络通信
1)流程
2)发送消息
4. 界面
1)生命周期
2)工具
通过UI工具可以快速生成重复的代码以及相对应的配置文件。减少开发人员的重复劳动力,让其有更多的精力在业务逻辑开发上。
3)绑定
界面在初始化完成会根据对应的ViewModel的DB定义初始化自身的Observer。
Observer可以对界面元素进行快速绑定,这种快速绑定方式会在界面销毁的时候自动取消全部绑定,可以让开发者无需关心绑定的生命周期。
5. EC模式
根据ECS的设计理念,简化其中的S变形成EC模式。Entity:即实体负责保存数据,没有具体的逻辑更新。
Component:即组件,获取Entity上自身所关心的数据,并对数据进行绑定,在数据变化时进行具体的逻辑操作。
该模式减少了Mono创建时的开销,可以优先对数据进行操作,无需等待资源加载完成,淡化异步回调概念。
6. 逻辑图
1)好处
将游戏中操作分解成可独立执行节点,随后通过组合模式将节点串联起来,实现对应的功能逻辑。
在一定程度上减少了策划信息传递过程中的损失,减少程序在从一个想法到落地的时间成本,可以更加快捷、便利的实现某一游戏逻辑。
2)执行流程
Part3. 总结和展望
1. 总结
· 整体框架依然采用传统的三层模式
· 最底层的数据访问层分为DB,ShareDB和CacheDB三块
· 逻辑层大量采用组合模式,使元素简单化,使得客户端可以像处理简单元素一样处理复杂元素,并使其解耦
· 显示层使用MVVM的思想进行设计,将业务逻辑和属于开发与页面设计分离
· 单一的Update可以更好的控制时序
2. 展望
· 使用间接依赖的方式是逻辑图行为和数据之间解耦
· 将组件进行中间件化,让其更加独立,并可以进行热插拔
· LogicUpdate和Update分离,将逻辑更新和渲染更新分离
本次的分享会就到此结束啦,感谢炽天的小伙伴在百忙之中的分享!
如果你还有什么想看、想了解的内容,请后台留言给我们,我们拥有一群怀抱理想、热爱学习与进步,乐于分享的小伙伴们,不论是涉及到游戏研发、运营还是协作管理等,我们统统都能火速安排!