整理笔记 2025.6.20
This commit is contained in:
148
3Resources/游戏开发/帧同步总结.md
Normal file
148
3Resources/游戏开发/帧同步总结.md
Normal file
@@ -0,0 +1,148 @@
|
||||
## 一、逻辑与表现分离
|
||||
|
||||
①、逻辑层仅处理数据,准备好用于渲染的数据。
|
||||
|
||||
②、表现层对已有的数据,进行插值表现,且不会改动数据。
|
||||
|
||||
③、两者的帧率可以不同。
|
||||
|
||||
## 二、时间相关
|
||||
|
||||
①、客户端通过Ping包方式预测服务器的战斗时间。
|
||||
|
||||
②、RTT计算方式,发一个Ping包,包含发送时的本地时间戳,服务器接收到以后,返回这个时间,客户端接收到后,用最新的本地时间戳减去这个时间,就可以得到RTT。
|
||||
|
||||
③、RTT计算时,服务器还会返回一个服务器的战斗时间,取最低RTT的一半,加上服务器返回的战斗时间,即可以估算为战斗时间。
|
||||
|
||||
④、多次模拟,越来越接近服务器的战斗时间。
|
||||
|
||||
## 三、预输入
|
||||
|
||||
①、非锁步同步中,客户端先于服务器运行,比如快个3帧。
|
||||
|
||||
②、理想状态下,服务器因为有收包缓冲在,每帧都能收到每个客户端的输入。
|
||||
|
||||
③、但网络延迟高时,客户端发送的输入,可能超过了服务器的收包缓冲。
|
||||
|
||||
④、所以基于RTT的计算,在延迟高时:
|
||||
|
||||
客户端会增大预输入的帧数,比如原本发送超过服务器3帧的输入,改成6帧。
|
||||
|
||||
服务器也会增大收包缓冲,在收到最新的同帧号输入时,会替换之前的输入。
|
||||
|
||||
缓冲可以动态调整。
|
||||
|
||||
⑤、一般预输入只处理预测失败概率低的操作,比如持续移动。
|
||||
|
||||
⑥、客户端冗余发送
|
||||
|
||||
## 四、预测
|
||||
|
||||
①、客户端先行于服务器,所以需要预测其他客户端的行为。
|
||||
|
||||
②、一般基于连续输入原则,比如连续的移动输入等。
|
||||
|
||||
③、对于高敏感操作,一般不进行预测。
|
||||
|
||||
## 五、回滚
|
||||
|
||||
①、在本地帧输入数据与服务器发送过来的帧输入不一致时,发生回滚。
|
||||
|
||||
②、找到最近可用的快照,进行推进。
|
||||
|
||||
③、逻辑层回滚技巧:
|
||||
|
||||
基于帧索引的可序列化数据,比如随机数状态,属性等。
|
||||
|
||||
针对于大型的,序列化成本较高的数据,可记录每帧变动的指令,在回滚时,逐帧逐条回滚这些指令。
|
||||
|
||||
④、表现层回滚技巧:
|
||||
|
||||
表现层一般设计为可插值,而非多帧状态累加。
|
||||
|
||||
有一些效果,可等到逻辑帧确认后再表现。
|
||||
|
||||
插值回滚,而不是瞬间表现到位。
|
||||
|
||||
⑤、ECS回滚参考:
|
||||
|
||||
Entity/Component不增删时,拷贝整个Entity/Component的数据块。
|
||||
|
||||
Entity删除时,可标记为禁用,等确定不回滚后再删除,增加时,操作记录回退。
|
||||
|
||||
Component增删时,操作记录回退。
|
||||
|
||||
## 六、追帧
|
||||
|
||||
①、落后太多时,可选择最近目标帧的快照进行处理。
|
||||
|
||||
②、一般会设置当前帧可用于追帧的时间,避免追帧卡顿。
|
||||
|
||||
## 七、快照
|
||||
|
||||
①、较大的间隔生成全量快照,比如1秒。
|
||||
|
||||
②、较小的间隔生成增量快照,记录基于全量快照的变动。
|
||||
|
||||
③、OOP中,一般通过标签,或者内建数据结构类实现序列化、反序列化、数据变动记录。
|
||||
|
||||
④、ECS中,一般关注Entity数量、Component数量和数据即可。
|
||||
|
||||
⑤、序列化一般使用自定义二进制,且数据一般为整数,如果对大小有要求,可以参考整数压缩方案。
|
||||
|
||||
## 八、日志
|
||||
|
||||
①、常规日志,比如输入,行为,随机数,属性等。
|
||||
|
||||
②、自动日志,大致实现思路:
|
||||
|
||||
遍历所有涉及逻辑的代码文件,通过正则表达式,查找到所有的有值类型参数的方法。
|
||||
|
||||
生成记录代码,包括宏开关,记录方法ID(方法名经过映射后的ID),参数数量,每个参数大小的代码和记录值的泛型接口代码。
|
||||
|
||||
③、哈希相关:
|
||||
|
||||
每次记录可算哈希,每帧可算哈希,多帧哈希可叠加。
|
||||
|
||||
哈希计算是可能会有冲突的,尽可能的降低计算成本与哈希冲突。
|
||||
|
||||
④、不一致定位,定位哈希不同的帧和定位同帧下的不同步点。
|
||||
|
||||
## 九、不同步
|
||||
|
||||
①、基于日志定位。
|
||||
|
||||
②、可能的原因:
|
||||
|
||||
使用了浮点数相关的模块,比如内置数学API、物理模拟、动作、自定义库等。
|
||||
|
||||
使用了字典,遍历顺序不一致。
|
||||
|
||||
多线程操作数据先后,执行结果处理先后。
|
||||
|
||||
“我”的开发角度,使得逻辑倾向于其中一方。
|
||||
|
||||
工具类、单例类有未纳入序列化的数据。
|
||||
|
||||
## 十、定点数
|
||||
①、选择合适精度,这涉及计算性能与内存开销。
|
||||
|
||||
②、开方,快速近似查表代替循环。
|
||||
|
||||
③、反三角函数,查表代替泰勒展开(循环)。
|
||||
|
||||
## 十一、多世界
|
||||
|
||||
①、尽量不要使用有状态(数据)的单例类。
|
||||
|
||||
②、可以存在无状态(数据)的静态类,工具类,辅助类等。
|
||||
|
||||
## 十二、其他
|
||||
|
||||
①、帧同步中,没有秒的概念,只有帧的概念,秒的相关数据都需要转换成帧。
|
||||
|
||||
②、逻辑帧中的延迟模块,一定是由逻辑帧去轮询,比如自定义协程、延迟模块等。
|
||||
|
||||
③、客户端和服务器都有帧缓冲,客户端快照也会有缓冲,以减少内存使用。
|
||||
|
||||
④、预测帧数高时,预输入帧数可对应降低。
|
||||
Reference in New Issue
Block a user