前言
早期为了实现对服务器的快速设计和实现,忽略了游戏架构上的设计。使用传统的面向对象的方式对业务需求进行实现,导致了项目在中期的研发和扩展中遇到了各种数据对接不恰当的瓶颈。如果要强制实现会使系统之间的交叉絮乱。这样开发下去后果可想而知。于是笔者在遇到了此问题后,细想就后怕。决定重构之。
介于笔者一直在用 Unity,对 ECS(实体组件系统) 一些思想也非常认可,而且坚信未来一定是 面向数据编程 的。所以毫不犹豫的选择了用 ECS 来重构之。
什么是 ECS 呢?
ECS - 实体组件系统
E :Entity 实体 也就是游戏世界中的单位 (他可以拥有各种组件)
C : Component 组件 也就是实体上的很多个部件 (比如一个英雄 会存在:属性组件 / 技能组件 / buff组件 / 皮肤组件 / 等等)
S : System 系统 也就是处理对应组件的一个中心 (比如:有 属性组件 就会存在对应的 属性系统 / 有 技能组件 就会 存在技能系统 一一对应)
ECS 基础的内容大概就是以上。很简单就能理解。
同步机制
如果单纯以上的方式来考虑ECS的话其实是不够的。
因为ECS的核心应该是在面向数据的编程方式上,起码在游戏的应用中是这样的。因为所有存在交互的 游戏实体 都是存在数据变化的。
所以在我们决定用ECS的方式来重构的时候,就决定了我们的服务器又或是客户端一定是面向数据进行编程的。而不是消息,不是指令。
以此我们就能推敲出一套通用的底层机制。通过这套底层机制来进行游戏数据层的完全同步。而客户端就面向本地的数据层进行逻辑处理。
于是有了如下图所示的代码:
Golang (服务器):
C# (客户端):
Proto :
如上图所示 服务器 / 客户端 代码都是通过解析 proto 文件 而 生成的 component 代码组件。
这里的同步机制主要同步 实体 / 组件 的 添加 / 删除 / 更新
增量更新
我们在生成的时候 处理了 服务器组件 进行 增量更新操作 因为这样能节省网络流量
也处理了 客户端组件 进行 增量更新的操作
数据的处理
严格意义上来讲 ECS 的 Component 上是不会存在任何逻辑操作的。他只是数据层。
而所有的数据逻辑操作都在对应的 System 中。
所以我们这里的 Component 只有数据查询的 API
我们要实现的这套同步机制就是 无论什么情况 都让客户端先行同步 所有 Entity 的 Component 然后再通过 System 来处理 Component。 实现客户端真正意义上的面向数据编程。这样客户端不用再去管缓存数据 或者 是传递数据的事务。
我们通过 System 将对应业务的代码强内聚。这样从排错上来说也非常高效。因为只需要找到对应的 System 去排错
如图:
又因为 System 与 任何 Component 产生关联 都能通过 Entity 交互。这样又很好的剥离了 System 之间的强关联。
客户端 Entity 部分代码:
Entity 的 Actor
因为我们的 Components 只是数据层。可能很多数据比如我们压缩的数据,或者需要换算一次才能使用的数据。对于这种操作 我们将会对 Entity 引入 Actor的方式来处理。
这样我们的Actor就会存在很多的原子函数。可以提供给 System 操作, 同时还能保证底层的安全性。
逻辑帧消息
逻辑帧上我们将消息分为单帧下发,也就是一帧只有一个消息下发的方式,而其中组件消息的部分:
因为我们的 Proto 还是老版本 还不支持 Map 或 any 的操作 所以这里略显蛋疼。
不过办法总是有的 我们的消息同步这块也通过生成器来生成代码。就省去了每次都需要去添加人工写一次了。
如图:
总结
总结下ECS就是 ECS其实本身并不复杂,但是在使用它解决业务场景的时候 需要严谨的定义业务中的规则,剥离组件的方式。
如何去定义ECS在面向业务的时候的场景很重要。如果用好了,那就是利器。否则很可能导致数据层泪崩。
另外 ECS 这种架构 在 配合上 代码的自动化生成 真的能达到事倍功半的效果。所以在以后要多考虑让机器帮我们制造。而不是靠手撸~
以上大致为本次我们用ECS方式重构的一些核心要素了 草草结尾~ ^_。