帧同步游戏小记

马上就春节了,闲下来 想做个同步的网路游戏
虽然市面上有一些同步的网路框架,入门级的有Mirror,大众性的Photon ,偏商业化的ET等等,但是有什么能比自己做个网络框架更令人激动的呢。PS 如果各位看官想直接用一个帧同步的框架 在此推荐我参与编写的GDNet,里面含括了 TCP UDP,KCP,UDX,WebSocket等等协议,也包含了一个简单的帧同步示例项目,GDNet仍处于优化阶段,为了让更多的小白更易上手制作属于自己的网络游戏。

言归正传,网络游戏可大致分为 状态同步和帧同步,在此不细说两者的区别了,我立项是做一个moba或者act类型的网路同步游戏,所以选择的是帧同步方案。
既然是帧同步方案,那么先说下帧同步是什么: 即相同的输入 计算得到相同的结果
它的难点如下
1.保证客户端独自计算的正确,即一致性 导致不一致的主要原因是浮点数在不同平台下的精确度不一致
2.网络的可靠性 可靠性包括延迟和丢包率
3.逻辑和显示的分离 因为帧同步 同步的是输入 即逻辑数据(逻辑数据一致即可 显示层不一致 并不影响游戏逻辑走下去)
下面一一来解答如何消灭上面的难点
1.关于一致性 ,photon有个math库,Truesync提供了完整的 定点数的数学库,或者自己写一个 基于整数的定点数(难度不大 只是全部重写常用数学操作比较繁琐)
2.网络的可靠性 因为主要目标平台是移动端,所以可以看做是4G或者5G网络环境,因为游戏的逻辑是基于服务器下发的关键帧数据来驱动的,所以需要保证关键帧数据的准确性和顺序,准确性可以用MD5或者其它一些算法来保证,顺序性,最简单的就是直接用TCP,因为它是保证有序的,但是TCP的RTO时间是*2的,当网络不好的时候,可能延迟较大影响玩家体验,而且TCP的包头要比UDP的大,较耗费流量,所以优先选择UDP,但是UDP有乱序和丢包问题,所以需要在UDP基础上封装下,确保UDP的顺序和准确性,常用的有Enet和KCP,据测试Enet的稳定性和KCP差不多,但是KCP的延迟要比Enet低一些,所以我选择KCP,在Git上很容易能下载到KCP的各种版本案例
3.逻辑和显示分离,这个很好理解 一切的逻辑是根据 逻辑帧走,而不依赖于本地的显示走,当没收到服务器下发的逻辑帧时 本地的逻辑是完全停止的,虽然可能玩家还在播放走路 攻击等等的动画,但是本地的逻辑是停止的
譬如 按下了A键 攻击 顺序是 客户端发给服务器 服务器定时下发操作给各个客户端,客户端收到了关键帧才执行攻击(逻辑层去检测 打到了哪些人,显示层去播放攻击动画),在unity里常用的是当动画播放到某一时刻才去检测,在帧同步里就不能这样做了,因为不能依赖于本地的 动画片段的时间(要延时攻击怎么办呢 只能根据当前技能的延时时间,例如 1s(一秒)是30逻辑帧 想在攻击的0.5s(半秒)后才去真正的释放技能 ,那么就只能写成在 延迟15逻辑帧后才去释放技能)

关于帧同步的服务器:标准的帧同步,是要求客户端定时发送指令,服务器必须等待收集到所有客户端的包之后再下发,这个等待帧信息的过程称为帧锁定。如果一个客户端网络卡顿,那么所有客户端都要等待这个客户端的帧同步信息。早起的同步游戏 红警就是如此,一个玩家卡 那么全部都卡,大家都在等他

乐观锁就是无论是否收集到客户端的信息,都将目前为止收集到的指令封装为帧并下发,这样所有客户端不会因为某个客户端的卡顿而卡顿。现在大多数的帧同步都是如此,因为你卡是你的事情 大家不卡或者(土豪不卡),那你别去影响大家的游戏体验,当你网络好的时候 你逻辑帧跑快点(加速)追上大家的游戏进度就好了 比较出名的Dota LoL都是如此 当你卡的时候 你的画面是禁止的,网络恢复畅通的时候 会发现人物是加速跑的(这时候就是在追赶大家的游戏进度)

因为准备用的游戏引擎是Unity,那么就说一下帧同步里Unity里的API 哪些是能用的哪些是不能用的吧
几乎都不能用,
生命周期函数:不能用,因为它的生命周期函数不能保证顺序,所以要自己模仿Unity的生命周期函数 重写一个自己的
碰撞系统:不能用,因为它是基于float的,但是float不能保证不同平台下的一致性,所以要实现自己的可靠的碰撞系统,具体的算法有 四叉树 八叉树 GJK SAT等等,我所用到的是 四叉树 和GJK,四叉树是基于AABB碰撞检测,先检测出大概碰撞到的物体,再用GJK精确检测碰撞,当然也可以用一些成熟的碰撞检测库,因为我这2种算法只能检测凸面体的碰撞
射线:不能用,原因同上 自己写射线检测可以参考https://blog.csdn.net/tom_221x/article/details/51861129?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522161264759116780261940504%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=161264759116780261940504&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-7-51861129.pc_search_result_no_baidu_js&utm_term=%25E5%25B0%2584%25E7%25BA%25BF%25E6%25A3%2580%25E6%25B5%258B%25E7%25A2%25B0%25E6%2592%259E%25E7%25AE%2597%25E6%25B3%2595

寻路系统,不能用,原因同上,只能用 寻路算法自己写个 或者找个现成的库

关于上面提到的几种算法,我会在项目完工后附到文章里

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Protobuf是一种高效的序列化协议,可以用于数据交换和数据存储。它的主要优势是大小小,速度快,可扩展性强。下面是使用Protobuf的一些小记: 1. 定义消息格式 首先,需要定义消息格式,以便Protobuf可以将数据序列化和反序列化。消息格式定义在.proto文件中,使用protobuf语言编写。例如,下面是一个简单的消息格式定义: ``` syntax = "proto3"; message Person { string name = 1; int32 age = 2; } ``` 这个消息格式定义了一个名为Person的消息,包含两个字段:name和age。 2. 生成代码 一旦消息格式定义好,就可以使用Protobuf编译器生成代码。编译器将根据消息格式定义生成相应的代码,包括消息类、序列化和反序列化方法等。可以使用以下命令生成代码: ``` protoc --java_out=. message.proto ``` 这将生成一个名为message.pb.java的Java类,该类包含Person消息的定义以及相关方法。 3. 序列化和反序列化 一旦生成了代码,就可以使用Protobuf序列化和反序列化数据。例如,下面是一个示例代码,将一个Person对象序列化为字节数组,并将其反序列化为另一个Person对象: ``` Person person = Person.newBuilder() .setName("Alice") .setAge(25) .build(); byte[] bytes = person.toByteArray(); Person deserializedPerson = Person.parseFrom(bytes); ``` 这个示例代码创建了一个Person对象,将其序列化为字节数组,然后将其反序列化为另一个Person对象。在这个过程中,Protobuf使用生成的代码执行序列化和反序列化操作。 以上是使用Protobuf的一些基本步骤和注意事项,希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值