用ECS架构完美实现帧同步

      很早之前写过一篇关于网络游戏同步方案的文章:网络游戏网络同步方案的选择,里面谈到帧同步,这里就谈一下帧同步的一种实现方式吧,主要还是以收集资料为主。

      首先说一下帧同步最简单的一种实现,很简单就是服务器对客户端上行的所有指令不做任何计算直接转发给所有相关的客户端,客户端在自己本地根据服务器转发的指令做计算展现游戏数据和画面,只要所有的客户端的计算逻辑是一样的就不会出现大家看到不同的结果,但是这样会带来一个很不好的副作用那就是计算依赖严重客户端,会有很多外挂,moba类的游戏(外挂相对少)还凑合,但是对于fps游戏几乎是灾难性的。

     稍微复杂一点,服务器保存有游戏中的状态数据,客户端每次上行指令时服务器会根据游戏状态做一些基本合法检测,但是并不做实际的游戏运算,然后下发指令,这种实现在一定程度上可以解决外挂问题,不过因为计算在客户端还是会出现外挂和多个玩家之间不同步的问题。

     以上两种实现方式从技术角度上来说没有什么难度,只要做到了客户端的一致,没有其他的技术难题。

     最后说,最复杂的实现就是服务器要做游戏逻辑的计算了,服务器需要对上行的逻辑做计算然后给客户端同步状态,说到同步状态有人会说这不又变成状态同步了吗,实际上不是的,所谓帧同步,关键在于帧,帧同步中有一个所谓的锁帧概念,在状态同步中逻辑处理是这样的,客户端在玩家有指令的时候都会上行操作指令,极限情况下你同时操作100次就会上行100次,服务器会对每一个上行的消息进行及时处理并切回相应的消息包给客户端,而帧同步中是一时间来计算的,比如客户端和服务约定的逻辑帧是60帧。每帧就是16毫秒,客户端会保存一个指令缓存,客户端会按照每16毫秒一次的速度把本地的缓存的指令上行服务器,服务器以同样的频率下行一帧内收集到的所有客户端的指令并附加帧索引。 当然客户端不用严格按照制定的帧率上行数据,但是服务器必须按照这个帧率下行,因为客户端是由服务器下行的数据驱动的。所以可以看到两者的区别,当客户单产生新的指令时不会立刻上行而是缓存本地,以固定的频率上传,服务器也是同样的道理,产生数据时不会立下行数据二十缓存起来,这就是帧同步的关键。

     但是如果只是简单的用这种方式会和状态同步一样有很大的延迟,如果要开发一款快速响应(responsive)的网络对战动作游戏,如果每个操作都要等服务器回包的话,就不可能有高响应性了,这种快速响应的需求在moba,fps类的游戏中尤为重要,因此需要额外的手段去fix,其一就是客户端对玩家的操作做预测(predict,也可以说是预表现),不需要等服务器回包玩家操作结束后立刻做游戏画面的渲染,从而达到流畅的体验,注意这里只是预测,最终的结果会等服务器计算结束后做仲裁,如果服务器计算结束同步状态的时候仲裁失败,客户端会被服务器拉回服务器的计算状态。这种预测说起来很简单,但是实际上由于网络的延时会产生很多问题,由于网络的延时客户端总是先行与服务器,如果处理不好仲裁成功的概率很低客户端就会频繁的被服务器拉回另外一个状态,比延时更严重(王者荣耀在实现过程中就放弃了客户端预测因为最终的效果没有想象中那么好,会造成玩家玩起来很飘 王者荣耀在技术架构与网络同步方案),因此有时候更好的做法是根据客户端的延时来灵活的决定是否需要做预测或者说根据延时高低做部分预测,比如适时舍弃弹道预测和碰撞预测,命中预测等等。

      因此如果需要完美的实现最后一种帧同步有很多的细节需要处理,并且在实现中途加入,回放和观战等功能的时候又会带来新的问题。最后这里还有最关键的地方,就是服务器要做计算来仲裁客户端的预测,如何保证客户端和服务器的计算逻辑是一致的,最好的方法就是客户端和服务器走完全一样的代码逻辑,但是客户端和服务器不一样,客户单需要做游戏中玩家相关的动作和特效等一系列画面的渲染,而与一些服务器的逻辑是客户端不需要的,如何用一种方式来处理网络同步和帧同步中复杂的逻辑,有没有一种架构来简化这种帧同步实现的复杂度,这就是最后我们要提到的ECS架构,下面就是一些收集的概念性质的东西。

ECS(Entity Component System) 

        Entity:代表游戏中的实体,是 Component 的容器。本身并无数据和逻辑。
        Component:代表实体“有什么”,一个或多个 Component 组成了游戏中的逻辑实体。只有数据,不涉及逻辑。
        System:对 Component 集中进行逻辑操作的部分。一个 System 可以操作一类或多类 Component。同一个 Component 在不同的 System 中,可以关联不同的逻辑。
      采用 ECS 的范式进行开发,思路上跟传统的开发模式有较大的差别:

      1)Entity 是个抽象的概念,并不直接映射为具体的事物:比如可以不存在 Player 类,而是由多个相关 Component 所组成的 Entity 代表了 Player。如 Entity { MovementComponent, RenderComponent, StateMachineComponent, ... } 等等。

      2)行为通过对 Component 实施操作来表达。比如简单重力系统的实现,可以遍历所有 Movement Component 实施位移,而不是遍历所有 玩家、怪物、场景物件,或者它们统一的基类。

     3)剥离数据和行为,数据存储于 Component 中,而 Component 的相关行为,和涉及多个 Component 的交互和耦合,由 System 进行实施。
ECS 框架,至少有以下优点:

   模式简单:如果还是觉得复杂,推荐看看 GoF 的《设计模式》。
   概念统一:不再需要庞大臃肿的 OOP 继承体系和大量中间抽象,有助于迅速把握系统全貌。
   结构清晰:Component 即数据,System 即行为。Component 扁平的表达有助于实现 Component 间的正交。而封装数据和行为的做法,不仔细设计就会导致 Component 越来越臃肿。
   容易组合,高度复用:Component 具有高度可插拔、可复用的特性。而 System 主要关心的是 Component 而不是 Entity,通过 Component 来组装新的 Entity,对 System 来说是无痛的。
   扩展性强:增加 Component 和 System,不需要对原有代码框架进行改动。
利于实现面向数据编程(DOP)。对于游戏开发领域来说,面向数据编程是个很重要的思路。天然亲和数据驱动的开发模式,有助于实现以编辑器为核心的工作流程。
   性能更好优化:接上条,相比 OOP 来说,DOP 有更大的性能优化空间。

   当然这种所谓的ECS架构在部分场景下并不是最合适的选择,如果你的游戏并不涉及多人在线实时对战的场景这种方式并不适合。

   最后给一些收集的相关资料吧,非常值得看。

   守望先锋架构设计与网络同步 ,守望先锋如何用ECS实现帧同步,里面有很多技术实现的细节。

   守望先锋回放技术-阵亡镜头、全场最佳和亮眼表现,仍然是守望先锋工程师的分享,

    有关守望先锋的分享之前有一个视频纯英文的,现在找不到了,只有一些文章。

    云风的相关分享

    https://github.com/alecthomas/entityx  github上ECS的一个实现(c++)

    知乎对ecs的讨论

    ECS方式实现帧同步

    帧同步战斗

你可能感兴趣的:(linux)