物理同步ABC

相比物理模拟,物理同步应该属于小众话题了,一般游戏也鲜有涉及,但物理同步本身仍然是一个值得研究的话题,其中关联到的技术还是比较有趣的,本文简述了几类物理同步的实现方法,以期做些小小的总结.

说到物理模拟我们总是能想到各类物理引擎,譬如 Box2D,PhysX 等等,但这些引擎大部分都仅实现了物理模拟的机制,很少有涉及物理同步的内容,总的来说,物理模拟已经非常困难了,再加一层物理同步则是难上加难,一是游戏设计上或多或少都希望避免这类复杂问题;二是物理同步实现不仅涉及物理模拟,还要包括网络同步,框架整合等内容,很大程度上已经超脱了传统物理模拟的范畴,一般的通用物理引擎往往很难在这块上有很深入的支持,目前我看到的在物理同步这块走的比较远的是 Unreal 的 Chaos,但总的来说仍然任重而道远.

简单来说,物理同步的实现大抵可以分为两类:

  • 输入同步
  • 状态同步

此处我们默认的同步架构指的都是 客户端 + 服务器 的网络结构,其中 服务器 会作为物理模拟的权威方来处理(从工程角度讲,自然还有许多将 输入同步 与 状态同步 相结合的方式(甚至有些游戏会直接将 客户端 作为物理模拟的权威方来进行处理),在此就不进一步展开发散了,仅就传统实现方法来进行讨论):

输入同步

所谓的输入同步,更时髦一些的说法可能是 帧同步,即客户端从相同的初始状态开始,每一物理(逻辑)帧都获取服务器转发过来的各类物理输入,然后本地执行物理模拟,以期得到一致的物理输出(各个客户端之间),同一般的帧同步一样,这个方案有好几座大山需要克服:

  • 浮点精度问题

众所周知,浮点数计算存在精度问题,各个硬件平台间也存在精度差异,如果想要获得完全一致的计算结果(各个平台间),我们往往需要借助定点数,但这么做又会出现 模拟效率低下, 扩展维护困难 等问题,需要我们一一权衡 …

  • 随机数据问题

随机数也是造成不一致的一大原因,这就需要我们统一随机种子,或者干脆规避随机过程.

  • 模拟顺序问题(碰撞检测,约束求解 等等)

物理模拟中涉及的 碰撞检测, 约束求解 等等,也会受到模拟顺序的影响,譬如我们迭代求解碰撞对,不同的求解顺序就可能造成不同的求解结果,这就要求我们能够固定模拟的求解顺序,但这又势必会造成一些效率性,扩展性等方面的妥协 …

  • 模拟效率问题
  • 扩展维护问题

正如上面所说,一致性问题的解决往往会造成 模拟效率低下, 扩展维护困难 等问题的出现,需要我们仔细权衡.

  • 操作反馈问题

另外值得一提的是,由于客户端需要根据收到的服务器输入(包括本地输入)进行锁帧步进,所以一般是不能进行本地预测的,客户端只能提前做一点有限的视觉表现(不影响游戏逻辑情况下),这就会造成操作反馈滞后的问题.

  • … (线程异步等问题)

当然,相关的问题还有很多,不一而足,每个问题解决起来也非常困难,并且各个问题之间还相互掣肘,所以想要实现一个通用并且完善的输入同步方案是不可能的,一般都需要针对具体游戏需求来进行开发定制 …

状态同步

所谓状态同步,即服务器执行物理模拟,然后将物理状态同步给客户端的方式,按照不同的实现方式,状态同步还可以进一步细分 :

  • 快照同步

所谓快照同步,就是客户端本地不执行物理模拟,仅仅作为一个显示终端,用来显示服务器同步过来的物理状态,当然,由于客户端收到的物理状态同步一定会出现不连续的情况,所以表现物理状态时一般都需要进行插值,而一旦进行插值,那么一定是很难完全符合物理规律的,所以效果上不会很理想(尤其在同步频率较低的情况下),另外的,虽然在表现物理状态时我们可以做一定的外推,但还是由于客户端本地不执行物理模拟,所以效果上也是差强人意的(效果上不如帧间插值,并且只能外推一小段时间,否则效果上会难以接受),为了有更好的插值效果,我们往往还需要做一定的延迟处理,以期收到足够的同步状态来进行插值(为了尽可能多做帧间插值,而尽可能少做帧末外推).

可以看到,快照同步的物理表现效果有限,并且存在较大的延迟问题,但好处也是显而易见的,客户端逻辑很轻(本地不需要执行物理模拟),适用于一些对物理同步表现要求不高的游戏中.

  • 修正同步

所谓修正同步(实际上这个同步方式也被称为"状态"同步,但是为了防止混淆,我改称其为"修正"同步),是指客户端本地也执行物理模拟,但是会根据服务器同步过来的物理状态进行修正,因为客户端本地也执行了物理模拟,所以效果上能比快照同步自然(符合物理)很多,但同时也带来了一个大问题:

客户端如何基于收到的物理同步做本地修正呢 ?

拿一般的(非物理)位置同步方法来说,基本就是客户端收到服务器发来的同步位置,然后便直接做对齐(Snap)操作,当然直接对齐会比较生硬,客户端往往还会做些插值处理,这样的同步处理方式对于(非物理)普通的游戏单位来说也许已经足够了,但是对于需要物理模拟的单位,直接进行位置插值会显得非常不自然(和快照同步中的表现问题类似),如何改善呢 ? 一种可能的方式就是尝试给物理单位添加一个’修正’速度,让其以物理的方式逐步靠近目标点,更近一步的,由于客户端本地收到的服务器状态总是滞后的,我们还可以进一步预测物体的(服务器)所在位置(基于 网络传输时间, 物体移动速度 等数据),以此来尽可能的弥补滞后问题,当然,兜底逻辑也是必不可少的,如果发现一直’修正’不到目标位置的话,需要进行一次强制对齐(拖拽).

另一种同步处理方式则是每次客户端收到同步状态,在内部逻辑层就直接对齐(并继续执行物理模拟),外部表现层则做插值处理,实际表现效果上虽然也有不自然的地方,但是会优于快照同步的表现,并且实现逻辑上会简单一些.

总的来说,对于一般物体而言,经过上述的同步方式,物理同步效果可能已经能够接受了,直到我们需要与这些物体进行交互 …

物理交互 ? 不就是给物体施加(交互)作用力吗 ?

上面的疑问可能是你的第一反应,简单来说,所谓的物理交互确实就是给物体施加(交互)作用力(当然,还有力矩,后面说明从略),但由于需要进行同步,所以施加(交互)作用力的过程也需要上报,当然,我们也可以统一由服务器侧来触发(施加(交互)作用力),这样做虽然逻辑上会比较容易实现,但会造成交互的延迟(因为客户端需要发送交互请求给服务器并等待服务器响应),所以对于及时性要求比较高的游戏就不适用了,但如果我们仅仅在客户端本地施加(交互)作用力然后进行交互上报的话,由于服务器收到上报的时间总是滞后的,所以在服务器上施加(交互)作用力也必定是滞后的,这就势必会导致客户端与服务器之间物理状态的不一致,进而引发后续的同步拖拽 …

如何解决呢 ? 核心的思路便是进行 本地预测 + 重新模拟.

总体思路大概是这样的:

  • 客户端 与 服务器 执行固定间隔的物理帧更新(即物理计算的"逻辑"间隔是固定的(但实际的时间间隔可以是变化的)),由于物理帧逻辑间隔是一致的,所以 客户端 和 服务器 便可以进行物理帧号之间的对齐.
  • 客户端 总是提前模拟一定量的物理帧,这些提前模拟的物理帧即为预测帧(预测帧没有确认之前会进行缓存),拿之前所说的本地物理交互举例,由于客户端提前进行了预测,所以交互(物理帧输入)反馈可以做到及时响应.
  • 客户端 会将物理帧的输入依次同步给服务器,服务器收到后则执行对应的物理帧输入(如果物理帧已过则忽略,物理帧未到则缓存),并将执行结果反馈给客户端.
  • 客户端 收到服务器同步过来的物理帧结果,与自身本地的历史数据进行对比
    • 如果发现匹配则对历史数据做确认操作(从缓存中去除等)
    • 如果发现不匹配则进行重新模拟操作 : 将物理状态回退到对应的物理帧,然后应用服务器的物理帧结果,接着重新模拟物理至当前物理帧(当然,表现上仍然需要做一些插值处理)

经过上述流程,如果状态"一切良好"的话,那么客户端本地不仅反馈及时,并且还能和服务器保持同步,当然,方案代价也是很大的,上述流程虽然看上去并不复杂,但实际实现时需要处理很多问题,譬如重新模拟实现中的性能问题,业务逻辑使用中的整合问题等等,如果游戏规模较大,整体执行重新模拟将变得不切实际,此时还要考虑如何分流(一些物体走 本地预测 + 重新模拟;另一些物体走一般的物理同步)等问题,复杂度还是非常高的.

总结

浮光掠影的过了一下物理同步的实现方式,权当作一篇科普总结吧,有兴趣的朋友可以参考其他资料继续了解(譬如这里) ~

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值