美文网首页前端开发那些事儿
基于ECS的大地图同步方案(下)

基于ECS的大地图同步方案(下)

作者: 李相赫的乐芙兰 | 来源:发表于2021-06-07 22:35 被阅读0次

    前面两节讲述了ECS框架以及大地图同步的实现方案,本篇最为这个系列的最后一篇,主题是“没有银弹”,讨论一下基于ECS同步的局限性。
    1.难以对复杂数据类型(比如vector,map或者自定义的class)进行同步
    a.复杂类型的变化不容易描述
    上节中所述,同步的重点在于如何判断数据发生了变化。在当前的实现方案中,是采用变化标记+版本号的方式来管理看到一个实体时到底需要同步哪些数据的。对于基础类型来说(比如int、string),当值发生了变化,那就直接将变化标记置为true,等需要同步时将整个变量的值写进同步包里即可。而对于复杂类型,如果只变化了一部分,就需要同步整个变量,那显然是一件效率非常低的事,而描述到底哪一部分发生了变化,逻辑由过于复杂(试想一下该怎么描述一个map的各种变化情况)
    b.复杂类型的序列化难以扩展
    同步数据包需要对变化的变量进行序列化和反序列化的支持,而如果要序列化自定义类型,则每加一种类型就需要修改一次序列化的接口。由于客户端需也需要调用对应的反序列化的dll,每加一种类型就得更新客户端的dll(对手机包来说就得整包更新)这显然也是无法接受的

    2.一个实体上相同的组件只能有一份
    ECS的设计理念中,同一个Entity中不允许存在两个相同的Component。比如一个建筑实体,只能有一个ComponentBuilding,一个ComponentPosition,这在大多数情况下是满足需求的。但是假如一个建筑拥有多个技能,此时不能在实体上附加多个ComponentSkill。
    为了应对这种情况,项目中采用为每一个技能创建一个技能实体,在技能实体上附加ComponentSkill。然后在建筑实体上附加一个ComponentSkillMgr,在这个组件中记录所有技能实体的id,用来管理建筑的所有技能。

    3.同步的延迟性
    游戏世界中实体的变化是随时都可能发生的,而数据同步是按固定的同步帧,对视野内的实体进行标记检测来判断需要哪些数据的。如果一个实体的某个数据,在一帧中变化了多次,检测时只知道这个数据发生了变化以及最后的值,但无法知道变化的过程。
    可能有人会说为什么不在发生变化的时候立即同步呢?这就回到第一节中讲到的视野同步方案选择的问题了。因为SLG大地图沙盘无法想MMORPG一样有“视野对”的概念,当一个实体发生了变化,此时它时很难知道到底应该同步给哪些玩家的(也许可以但计算量会非常巨大)
    由于客户端获取到的实体变化时延迟的(甚至是不全的),就需要在逻辑上做一些额外的处理。

    4.链表
    由于ECS支持一个实体自由的增加和删除组件,所以在存储时大量采用链表的结构
    a.system在管理同一个组件的所有实例时采用链表的结构
    这就要求在每帧Update遍历组件时必须一次性全部遍历,而无法拆到多个帧中分批Update(因为到了下一帧,链表可能已经发生了变化,无法维护上一帧到底遍历到哪里接着往后遍历)。如果服务器的实体非常多,每帧全部遍历会导致卡顿的化则处理起来比较棘手。
    b.一个实体内部管理所拥有的组件时采用链表结构
    如果一类实体上挂的组件非常多(比如一个部队可能挂10几个组件),在获取某个组件的开销的时间常数将会比较大,而获取组件是一个调用频率非常之高的操作,这可能会是一个隐患

    最后总结一些在实际项目中对于上面这些问题的“妥协”做法:

    1.只对大地图可见(需要进行视野同步)的逻辑物使用ECS方式管理和同步,其他与视野同步无关的对象,采用传统的OOP和protobuf消息的方式同步
    2.组件中需要同步的变量禁止使用复杂类型
    3.如果一个父对象实体包含多种相同的子对象,则将每个子对象实例化成独立的实体,然后在父对象实体中记录这些子对象实体的id
    4.如果在一个同步帧期间一个变量发生了多次变化,并且这些变化的过程都需要同步,则与客户端约定使用字符串的方式记录变化并同步这个字符串

    相关文章

      网友评论

        本文标题:基于ECS的大地图同步方案(下)

        本文链接:https://www.haomeiwen.com/subject/iwuqeltx.html