帧同步

2.1k words

简介

客户端,相同的状态、相同的输入、相同的计算方式=相同的下一帧状态
服务器,以固定间隔整理所有的客户端输入,整理成一个输入帧,通过网络传输给所有的客户端

需要实现的功能

可靠的UDP

KCP

数据的同步

客户端向服务器

  1. 在非回放的状态下,Update中,将当前的输入(horizontal、vertical、fire1、mouseDown、useSkill)保存到静态变量(GameManager.CurGameInput)中
  2. GameManager 中 Update 调用 SendInput ,将当前帧和 curGameInput 发送给服务器
  3. 接收到服务器发送来的数据,GameManager.PushFrameInput(msg.input) ,将帧和操作数据存入本地(frame 中)
  4. Update 中,根据 frame 中拿到当前帧的操作,并赋予给 inputAgent ,inputAgent 根据 数据进行位置的移动、角度的旋转

服务器向客户端

  1. 接受客户端发送来的数据,并在等待间隔后,转发给其他客户端

确定性的数学、物理运算库

存在的问题

  • 十进制浮点数无法精准转换为二进制
  • 浮点数运算对位
  • 随机数不一致

思路

通过扩大倍数,把小鼠部分按照特定的精度变成整数,后续都基于整数来进行运算
物理引擎使用定点数计算,最后虽然将定点数转换为 Unity Transform 的相关浮点数显示物体位置,但是最终在决定物理碰撞、游戏事件、物理移动中都是定点数化后的整数运算,做到物理引擎迭代的确定性,不同机器上跑物理引擎模拟,结果一致,在乘法和除法时容易溢出,可以考虑用两个int来存储值,一个存整数,一个存小数

unity-deterministic-physics 方案

unity-deterministic-physics
Unity DOTS物理版本0.6.0-preview.3的修改版本,它支持使用软浮点的跨平台确定性物理模拟。

数学库、重构的碰撞体之类的没看懂,用法大概是这样,先用着有空再深挖

修改速度

1
2
3
4
5
6

EntityManager.SetComponentData(entity, new PhysicsVelocity()
{
Linear = physicsParams.startingLinearVelocity,
Angular = angularVelocityLocal
});

修改阻尼

1
2
3
4
5
EntityManager.SetComponentData(entity, new PhysicsDamping()
{
Linear = physicsParams.linearDamping,
Angular = physicsParams.angularDamping
});

运算

加减乘除
Image text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 定点数的位数
INTEGER_BITS = 8
FRACTIONAL_BITS = 8

# 缩放因子
SCALE_FACTOR = 2 ** FRACTIONAL_BITS

# 定点数相加
def fixed_point_add(a, b):
# 将整数部分和小数部分分离
a_int = a >> FRACTIONAL_BITS
a_frac = a & (SCALE_FACTOR - 1)
b_int = b >> FRACTIONAL_BITS
b_frac = b & (SCALE_FACTOR - 1)

# 整数部分相加
result_int = a_int + b_int

# 小数部分相加
result_frac = a_frac + b_frac
if result_frac >= SCALE_FACTOR:
result_frac -= SCALE_FACTOR
result_int += 1

# 合并整数部分和小数部分
result = (result_int << FRACTIONAL_BITS) + result_frac

return result

# 示例运算
a = 10.5
b = 3.75

# 将浮点数转换为定点数
a_fixed = int(a * SCALE_FACTOR)
b_fixed = int(b * SCALE_FACTOR)

# 定点数相加
result_fixed = fixed_point_add(a_fixed, b_fixed)

# 将定点数转换回浮点数
result = result_fixed / SCALE_FACTOR

print("结果:", result) # 输出: 14.25

固定的执行顺序

对于协程等不确定性的方式不能再使用

使用自定义的协程

C#实现类似Unity的协程 - 喊我小怪兽啊的文章 - 知乎

解决方案

  • 取整计算法
  • 容许小概率误差
  • 逻辑表现分离,(表现层可以使用浮点数)

断线重连

通过追帧的方式重新演算到当前帧的游戏状态

比赛回放

  • 服务器记录关键帧

  • 下发客户端进行重放

  • 开发时记录在本地

数据在游戏结束时,序列化成字节,自定义的数据类型需要实现相关序列化和反序列化操作
回放时,读取字节反序列化成原先数据

反作弊

  • 重演
  • 仲裁

避免等待