您的位置:首页 > 理论基础 > 计算机网络

网络游戏之帧同步物理模拟

2017-07-25 19:36 411 查看
笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。CSDN视频网址:http://edu.csdn.net/lecturer/144 本篇博客我们将用网络中的帧同步技术进行物理模拟,帧同步是通过仅发送控制该系统的输入而不是该系统的状态将系统从一台计算机连接到另一台计算机的方法。 在网络物理模拟中,这意味着我们发送少量的输入,同时避免发送状态像位置,方向,线速度和每个对象的角速度。
它的好处是带宽与输入的大小成比例,而不是模拟中的对象数量。 是的,凭借帧同步技术,您可以将一百万个物体的物理模拟网络与只有一个相同的带宽进行网络连接。
虽然这在理论上听起来很棒,但在实践中很难实现帧同步,因为大多数物理模拟不是确定性的。 编译器,OS和甚至指令集之
间的浮点行为差异使得几乎不可能保证浮点计算的确定性。帧同步技术模拟物理是比较困难的。
帧意味着给定相同的初始条件和相同的输入集合,您的模拟给出完全相同的结果,我的意思是完全相同的结果。那么确切的说,你可以在每一帧结束时对整个物理状态进行校验它将是一样的。



在上面你可以看到几乎是帧同步的模拟, 左边的模拟由玩家控制, 从相同的初始条件开始,右侧的模拟具有与两秒延迟相同的
输入。 两个模拟都以相同的增量时间向前推进(确保完全相同结果的必要前提条件),并且两个模拟都应用相同
的输入。 请注意,在最小的差异之后,模拟将进一步失去同步, 这个模拟是非帧同步。
正在发生的事情是,我使用的物理引擎(Open Dynamics Engine)使用随机数生成器来随机化约束处理的顺序以
提高稳定性。 这是开源的! 不幸的是,因为左边的模拟器以不同的顺序对右边的模拟进行了限制,导
致了略微不同的结果。
幸运的是,在同一台机器上,使用相同的二进制文件和相同的操作系统将ODE帧所需的所有内容都
是通过dSetRandomSeed运行模拟之前将其内部随机种子设置为当前帧数。 一旦这样做,ODE给出完全相同的结果,
左右模拟保持同步。



需要注意的是, 即使上述模拟在同一台机器上是帧同步,但并不一定意味着它也将在不同的编译器,不同的
操作系统或不同的机器架构之间帧同步。 事实上,由于浮点优化,调试和发布版本之间的差异可能不是帧同步的。

接下来看看具体实现,我们的示例物理模拟由键盘输入驱动:箭头键施加力量使玩家立方体移动,保持空间
提升立方体并吹动其他立方体,并保持“z”启用运动模式。
我们如何处处理联网入?我们必须发送键盘的整个状态吗? 不需要发送整个键盘状态,仅需要影响模拟的键的状态。 这也不是一个
好策略。 我们需要确保在右侧完全相同的输入,完全相同的时间,所以我们不能通过TCP发送“按键”和“关键释
放”事件。我们所做的是代表一个结构体的输入,从键盘中输入这个结构:
struct Input
{
bool left;
bool right;
bool up;
bool down;
bool space;
bool z;
}; 接下来,我们将这个输入从左边的模拟发送出去,让右边的模拟知道输入属于帧n。
这里是关键部分:右边的模拟只能在帧n的输入时模拟帧n。 如果没有输入,则必须等待。
举个例子,如果您正在使用TCP发送,您可以简单地发送输入,没有其他信息,另一方面您可
以读取进入的数据包,并且接收到的每个输入都对应于一个帧,用于模拟向前推进。 如果给定的渲
染帧没有输入到达,则右侧不能前进,它必须等待下一个输入到达。
所以让我们继续使用TCP,你每帧发送一次(每秒60次)从左到右的模拟输入。这里有点复杂 
由于我们无法模拟前进,除非我们有下一个帧的输入,仅仅通过网络到达任何输入,然后在输入端运行模拟是不够的,因为结果会非常不稳定。
在60HZ网络发送的数据通常不会在每个数据包之间达到很好的间隔,即1/60秒。
如果你想要这种行为,你必须自己实现。
你在这里做的是类似于Netflix在流式传输视频时所做的工作。 你最初暂停一下,所以你有一个
缓冲区,以防一些数据包迟到,然后一旦延迟已经过去,视频帧间隔正确的时间间隔。 如果你的缓
冲区不够大,那么视频播放将是交错的。 使用帧同步,您的模拟行为与完全相同的方式:当缓冲区
不够大以平滑抖动时显示挂起, 当然,增加缓冲区大小的成本是额外的延迟,所以你不能只是缓解
你的办法摆脱所有问题。玩家不会用1秒的额外延迟玩你的游戏。
解决的目标是在平均条件下,播出延迟缓冲器为帧n,n + 1,n + 2等提供稳定的输入流,很好地
隔开1/60秒, 在最坏的情况下,时间到达帧n并且输入尚未到达,但它返回null,并且模拟被迫等待。
如果数据包已经聚合并传送迟到,则可能有多个输入准备就绪到每帧出库。 在这种情况下,我限制
在每个渲染框架的4个模拟帧,所以模拟有机会赶上,但不会模拟这么长时间,它进一步落后,“死亡
之螺旋”。
使用这种播放缓冲策略并通过TCP发送输入,我们确保所有输入可靠和按顺序到达。 这是方便的,
毕竟,TCP是为这种情况设计的:可靠的有序数据。
但是这种想法是有问题的:



以上您可以看到模拟网络使用TCP上的帧同步在100ms延迟和1%数据包丢失, 如果您仔细观察右侧,
您可以每隔几秒看到一次。 这里发生的是每次数据包丢失时,TCP都必须等待RTT * 2(实际上可能会更糟)。
发生碰撞是因为帧同步正确的模拟,但是不能模拟没有输入n的帧n,所以它必须暂停等待输入n被重新发送!
这不是所有的, 它随着延迟和数据包丢失的增加而明显变差。 这是在250ms延迟和5%数据包丢失的情况
下使用TCP上的帧同步的相同模拟网络:



如果你没有丢包和/或非常小的延迟时间,那么你很可能会用TCP获得可以接受的结果。 但请注意,如果您
使用TCP,则它在恶劣的网络条件下表现得非常糟糕。
我们要代替TCP, 我们需要确保所有输入的可靠和顺序到达。 但是,如果我们在UDP数据包中发送输入,那
些数据包将丢失。 如果不是在事件发生后丢失数据包,并重新发送丢失的数据包,那么我们会冗余地包含每个UDP
数据包中的所有输入,直到我们知道另一方已经收到它们为止?
假如输入非常小(6位), 假设我们每秒发送60个输入(60fps模拟)和往返时间,我们知道它们将在30-250ms
范围内。 最多可能是2秒的最坏情况,在这一点上,我们会超时连接。 这意味着平均而言,我们只需要包含2-15帧
输入和最坏情况,我们需要120个输入,最差的情况是120 * 6 = 720位, 这只有90个字节的输入! 这是完全合理的。
当然,从右边的模拟到左边还需要另外一个数据包,所以左侧知道哪个输入已被接收。 每个帧正确的模拟从网
络中读取输入数据包,然后将它们添加到播放延迟缓冲区,并跟踪其接收到的最新输入,并将其作为“ack”或输入确
认发送回左边。
当左侧接收到该确认信号时,丢弃比最近接收的输入更早的输入。 这样,我们只有少量的输入与两次模拟之间
的往返时间成比例。
我们通过改变游戏的规则来替换TCP。

我们已经实现了完全不同的,更符合我们要求的,而不是“在UDP上实现TCP的95%”。 对于一个协议,由于我们知道它们很小,
冗余地发送输入,所以我们不必等待重新传输。

那么这种方法比通过TCP发送输入好多了?

让我们来看看…



上面的图片显示了使用这种技术在UDP上同步的帧同步,具有2秒的延迟和25%的分组丢失。 想象一下TCP在这些条件下
可能会有多糟糕。

因此,总而言之,即使在TCP应该具有最大优势的情况下,在唯一依赖可靠序列数据的网络模型中,
我们仍然可以轻松地用UDP构建的简单协议来解决问题。
希望对读者有所帮助。。。。。。。。。。。。。。。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: