简介
客户端,相同的状态、相同的输入、相同的计算方式=相同的下一帧状态
服务器,以固定间隔整理所有的客户端输入,整理成一个输入帧,通过网络传输给所有的客户端
需要实现的功能
可靠的UDP
KCP
link
本质:ARQ协议(自动重发机制);
特性:
1: 超时计算 RTO(重传时间间隔)翻倍vs不翻倍,x1.5
2: 选择性重传vs全部重传
3: 快速重传:发送端发送了1,2,3,4,5几个包,然后收到远端的ACK: 1, 3, 4, 5,当收到ACK3时,KCP知道2被跳过1次,收到ACK4时,知道2被跳过了2次,此时可以认为2号丢失,不用等超时,直接重传2号包,大大改善了丢包时的传输速度。
延迟ackvs非延迟ack:TCP为了充分利用带宽,延迟发送ACK(NODELAY都没用),这样超时计算会算出较大RTT时间,延长了丢包时的判断过程。KCP的ACK是否延迟发送可以调节。
使用原理:以一个消息的收发为例:
® 使用socket接口搭建udp底层通信,例如C#的Socket API。
® 发送数据前,调用kcp的send接口,发送到kcp,进行数据包拆分、封装。
® 设置kcp的output回调接口,数据包处理完成后,自动回调output接口。在这里通过socket发送到服务器
® 服务器部分运行流程与客户端相同,这里略过。消息处理完成后,服务器发包到客户端。
® 客户端的socket接口收到数据,调用kcp的input接口,kcp对数据包进行合并、解包。再通过回调接口,把真实的数据包返回给客户端进行逻辑处理。
数据的同步
客户端向服务器
- 在非回放的状态下,Update中,将当前的输入(horizontal、vertical、fire1、mouseDown、useSkill)保存到静态变量(GameManager.CurGameInput)中
- GameManager 中 Update 调用 SendInput ,将当前帧和 curGameInput 发送给服务器
- 接收到服务器发送来的数据,GameManager.PushFrameInput(msg.input) ,将帧和操作数据存入本地(frame 中)
- Update 中,根据 frame 中拿到当前帧的操作,并赋予给 inputAgent ,inputAgent 根据 数据进行位置的移动、角度的旋转
服务器向客户端
- 接受客户端发送来的数据,并在等待间隔后,转发给其他客户端
确定性的数学、物理运算库
存在的问题
- 十进制浮点数无法精准转换为二进制
- 浮点数运算对位
- 随机数不一致
思路
通过扩大倍数,把小鼠部分按照特定的精度变成整数,后续都基于整数来进行运算
物理引擎使用定点数计算,最后虽然将定点数转换为 Unity Transform 的相关浮点数显示物体位置,但是最终在决定物理碰撞、游戏事件、物理移动中都是定点数化后的整数运算,做到物理引擎迭代的确定性,不同机器上跑物理引擎模拟,结果一致,在乘法和除法时容易溢出,可以考虑用两个int来存储值,一个存整数,一个存小数
unity-deterministic-physics 方案
unity-deterministic-physics
Unity DOTS物理版本0.6.0-preview.3的修改版本,它支持使用软浮点的跨平台确定性物理模拟。
数学库、重构的碰撞体之类的没看懂,用法大概是这样,先用着有空再深挖
修改速度
1 |
|
修改阻尼
1 | EntityManager.SetComponentData(entity, new PhysicsDamping() |
运算
加减乘除
1 | # 定点数的位数 |
固定的执行顺序
对于协程等不确定性的方式不能再使用
使用自定义的协程
C#实现类似Unity的协程 - 喊我小怪兽啊的文章 - 知乎
解决方案
- 取整计算法
- 容许小概率误差
- 逻辑表现分离,(表现层可以使用浮点数)
断线重连
通过追帧的方式重新演算到当前帧的游戏状态
比赛回放
服务器记录关键帧
下发客户端进行重放
开发时记录在本地
数据在游戏结束时,序列化成字节,自定义的数据类型需要实现相关序列化和反序列化操作
回放时,读取字节反序列化成原先数据
反作弊
- 重演
- 仲裁