回顾之前做的几个Demo,做点总结。
一、做法:
为了更清晰,NRatel将 一个游戏对象类 拆分为 两个类,如下:
1、定义“纯粹的逻辑类”。
基本职责:对游戏对象的“逻辑值”进行定义、存储和计算。
关键要素:只关心逻辑数据,不考虑表现问题。(如同在没有表现能力的服务端写代码)。
不用继承Monobehaviour(Unity中)、不在任何系统自带的组件上存取逻辑值。
可在逻辑运算的特定的位置抛出数据变更事件。
或在数据操作完成后回调到 Controller。
举例说明:如定义、存储和计算 “变换相关的坐标、旋转、缩放”、“战斗属性相关的“生命、法力”等。
2、定义“纯粹的表现类”。
基本职责:根据逻辑类中的数据,对物体进行实际显示更新。
关键要素:持有 实际的游戏物体(GameObject)。(获得游戏物体的对象引用)
持有或可访问对应的逻辑类。(可获得对应逻辑类的数据内容)
可监听逻辑类中的数据变更事件,并以此对要表现的游戏对象及其子对象做显示更新(观察模式单向弱依赖)。
或由 Controller 类控制,“可选地” 对实际物体进行操作。
如:逻辑数据上的位移变化较粗糙,但修改实际物体时,可对Transform组件上的值进行直接赋值或平滑插值。
实际开发中,这两个类其实是可以合并为一个类的,只是要注意保持这种单向弱依赖的处理的方式。
二、意义(为什么要将逻辑与表现分离)
1、清晰高效的交互模型。
可以认为:所有的逻辑类共同组成了一个 在客户端内部的、独立完整的 “个人数据服务器”。对外,负责与真正的服务器同步(交换更新)数据;对内,通过事件驱动表现显示。
逻辑与表现的分离,可以让彼此有不同的 “心跳频率”,通常,为了减少客户端与服务器之间收发消息的次数。“逻辑类中的数据同步频率” 要远低于 “表现刷新频率”。表现时,通过在逻辑值之间插值,仍然可以让显示平滑,可做到让玩家毫无察觉。
2、安全可靠的数据模型。
所有数据都在逻辑类中显式定义,不会存储在任何系统组件上。
所以,能够避免“意想不到的被修改”(比如被一些tween类插件意外修改)。
能够避免使用浮点数,这也是帧同步实现的重点之一。
3、可拆卸的表现逻辑。
始终保持 “表现” 单向弱依赖于 “逻辑”,所以,
能够随意销毁任何显示中的游戏物体(GameObject)而不影响逻辑运算(逻辑类一直存在,只是干掉了类似观察者的东西)。对于“按视野显示游戏物体,分质量(高低模、不同图片压缩品质)显示游戏物体” 这样的性能优化需求,可以处理的得心应手。