多人游戏 - 客户端插值计算?

Posted

技术标签:

【中文标题】多人游戏 - 客户端插值计算?【英文标题】:Multiplayer Game - Client Interpolation Calculation? 【发布时间】:2015-12-16 21:42:09 【问题描述】:

我正在使用 javascript 中的套接字 io 创建多人游戏。除了客户端插值外,该游戏目前完美运行。现在,当我从服务器收到一个数据包时,我只需将客户端位置设置为服务器发送的位置。这是我尝试做的:

getServerInfo(packet) 
     var otherPlayer = players[packet.id]; // GET PLAYER
     otherPlayer.setTarget(packet.x, packet.y); // SET TARGET TO MOVE TO
     ...

所以我设置了玩家的目标位置。然后在 Players Update 方法中我只是这样做了:

var update = function(delta) 
    if (x != target.x || y != target.y)
        var direction = Math.atan2((target.y - y), (target.x - x));
        x += (delta* speed) * Math.cos(direction);
        y += (delta* speed) * Math.sin(direction);
        var dist = Math.sqrt((x - target.x) * (x - target.x) + (y - target.y)
                * (y - target.y));
        if (dist < treshhold)
            x = target.x;
            y = target.y;
        
       

这基本上是让玩家以固定的速度向目标方向移动。问题是玩家在下一个信息从服务器到达之前或之后到达目标。

编辑:我刚刚阅读了 Gabriel Bambettas Article 关于这个主题,他提到了这一点:

假设您在 t = 1000 收到位置数据。您已经在 t = 900 收到数据,因此您知道玩家在 t = 900 和 t = 1000 时的位置。所以,从 t = 1000 和 t = 1100,你展示了其他玩家从 t = 900 到 t = 1000 所做的事情。这样你总是向用户展示实际的移动数据,除非你“延迟”了 100 毫秒。

这再次假设它晚了 100 毫秒。如果您的 ping 变化很大,这将不起作用。

您能否提供一些伪代码,以便我了解如何执行此操作?

我在网上找到了这个问题here。但是没有一个答案提供了如何做到这一点的例子,只有建议。

【问题讨论】:

我也遇到了这个问题,我的游戏有抖动。您是否能够使用带有复选标记的答案来解决此问题?还是有其他因素可以帮助您解决这个问题?如果是这样,您能否提供某种示例实现?谢谢。 【参考方案1】:

完全对多人游戏客户端/服务器架构和算法很陌生,但是在阅读这个问题时,我首先想到的是在每个玩家的相关变量。

具体来说,卡尔曼预测步骤比简单的航位推算要好得多。此外,卡尔曼预测和更新步骤在某种程度上起到加权或最优插值器的作用。此外,玩家的动态可以直接编码,而不是使用其他方法中使用的抽象参数化。

同时,我快速搜索了一下:

An improvement of dead reckoning algorithm using kalman filter for minimizing network traffic of 3d on-line games

摘要:

在线 3D 游戏需要高效、快速的用户交互支持 通过网络,并且网络支持通常使用实现 网络游戏引擎。网络游戏引擎应尽量减少 网络延迟和缓解网络流量拥塞。最小化 游戏用户之间的网络流量,基于客户端的预测 (航位推算算法)被使用。每个游戏实体使用 算法来估计自己的运动(也包括其他实体的 移动),当估计误差超过阈值时,实体 将 UPDATE(包括位置、速度等)数据包发送到其他 实体。随着估计精度的提高,每个实体都可以 最小化 UPDATE 数据包的传输。为了提高 航位推算算法的预测精度,我们提出卡尔曼 基于滤波器的航位推算方法。为了展示真实的演示,我们 使用流行的网络游戏(BZFlag),并优化游戏 使用卡尔曼滤波器的航位推算算法。我们改进 预测准确率并将网络流量减少 12%。

可能看起来罗嗦,就像一个全新的问题来了解它的全部内容......以及离散状态空间。

简而言之,我会说卡尔曼滤波器是一种考虑到不确定性的滤波器,这就是你在这里得到的。它通常以已知的采样率处理测量不确定性,但可以对其进行改造以处理测量周期/阶段的不确定性。

这个想法是代替适当的测量,您只需使用卡尔曼预测进行更新。该策略类似于target tracking applications。

我自己在 stackexchange 上被推荐 - 花了大约一周的时间来弄清楚它们的相关性,但我已经在视觉处理工作中成功实施了它们。

(...这让我现在想尝试一下你的问题!)

由于我想要更直接地控制滤波器,我将其他人在 matlab 中自行实现的卡尔曼滤波器复制到 openCV(C++ 中):

void Marker::kalmanPredict()

    //Prediction for state vector 
    Xx = A * Xx;
    Xy = A * Xy;
    //and covariance
    Px = A * Px * A.t() + Q;
    Py = A * Py * A.t() + Q;


void Marker::kalmanUpdate(Point2d& measuredPosition)

    //Kalman gain K:
    Mat tempINVx = Mat(2, 2, CV_64F);
    Mat tempINVy = Mat(2, 2, CV_64F);
    tempINVx = C*Px*C.t() + R;
    tempINVy = C*Py*C.t() + R;
    Kx = Px*C.t() * tempINVx.inv(DECOMP_CHOLESKY);
    Ky = Py*C.t() * tempINVy.inv(DECOMP_CHOLESKY);

    //Estimate of velocity
    //units are pixels.s^-1
    Point2d measuredVelocity = Point2d(measuredPosition.x - Xx.at<double>(0), measuredPosition.y - Xy.at<double>(0));  
    Mat zx = (Mat_<double>(2,1) << measuredPosition.x, measuredVelocity.x);
    Mat zy = (Mat_<double>(2,1) << measuredPosition.y, measuredVelocity.y);

    //kalman correction based on position measurement and velocity estimate:
    Xx = Xx + Kx*(zx - C*Xx);
    Xy = Xy + Ky*(zy - C*Xy);
    //and covariance again
    Px = Px - Kx*C*Px;
    Py = Py - Ky*C*Py;

虽然我不希望你能够直接使用它,但是如果有人遇到它并了解状态空间中的“A”、“P”、“Q”和“C”是什么(提示提示,状态空间理解是一个先决条件)他们可能会看到如何连接这些点。

(顺便说一句,matlab 和 openCV 都有自己的卡尔曼滤波器实现......)

【讨论】:

感谢您的建议。在尝试将其实施到我的游戏中之前,我必须对此进行研究。从我读到的内容来看,对于看似如此简单的任务来说似乎有点过分了。 @TastyLemons 小心,不要让问题陈述的简单性影响解决方案! :) 不过我同意,这需要一些学习和概念理解(大部分是关于状态空间),但这似乎是技术所在。我的最终实现(在 openCV 中)的相关算法部分只有 30 行左右。 希望我能让它变得更简单 - 试试这个:au.mathworks.com/videos/… - 想象一下“盒子”是客户端服务器通信中的延迟......就像球在它后面一样,它与一个阻碍通讯的盒子(嘿,有意义吗?) 看完后我觉得我不能用它来玩游戏。因为玩家总是在改变方向和速度。所以像这样的预测行为是行不通的。 我将在这里反对我自己的利益:如果您试图使用过去的更新来估计以后某事的位置,那么您正在做的是 根据定义一个预测,没有办法解决这个问题。但是:这个答案实际上并不比我的简单。我只是提供了足够的细节来实际实施该建议。【参考方案2】:

这个问题一直悬而未决,要求提供更多细节,因此我将尝试填补 Patrick Klug 答案的空白。他合理地建议您在每个时间点同时传输当前位置和当前速度。

由于两个位置和两个速度测量给出了一个由四个方程组成的系统,它使我们能够求解一个由四个未知数组成的系统,即三次样条(它有四个系数,abcd)。为了使该样条曲线平滑,一阶和二阶导数(速度和加速度)在端点处应相等。有两种标准的等效计算方法:Hermite 样条 (https://en.wikipedia.org/wiki/Cubic_Hermite_spline) 和 Bézier 样条 (http://mathfaculty.fullerton.edu/mathews/n2003/BezierCurveMod.html)。对于这样的二维问题,我建议根据更新中的切线数据分离变量并为 xy 找到样条线,这称为钳位分段三次 Hermite 样条。与上面链接中的样条曲线相比,这有几个优点,例如基数样条曲线,它不利用该信息。控制点的位置和速度将匹配,您可以插值到最后一次更新,而不是之前的更新,如果游戏世界像太空战争一样本质上是极坐标,您可以轻松地将这种方法应用于极坐标。 (有时用于周期性数据的另一种方法是执行 FFT 并在频域中进行三角插值,但这听起来并不适用。)

这里最初出现的是 Hermite 样条曲线的推导,使用线性代数以一种有点不寻常的方式(除非我输入错误)会起作用。然而,cmets 说服我,为我所谈论的内容提供标准名称会更有帮助。如果您对它的工作原理和原因的数学细节感兴趣,这是一个更好的解释:https://math.stackexchange.com/questions/62360/natural-cubic-splines-vs-piecewise-hermite-splines

比我给出的算法更好的算法是将样本点和一阶导数表示为三对角矩阵,乘以系数的列向量,生成边界条件,然后求解系数。另一种方法是将控制点添加到贝塞尔曲线,其中采样点处的切线相交以​​及端点处的切线。两种方法都产生相同的、唯一的、平滑的三次样条。

如果您选择积分而不是接收更新,您可能能够避免的一种情况是,如果您获得了错误的积分样本。例如,您无法与平行切线相交,或者如果它以非零一阶导数返回到同一个位置,则无法判断发生了什么。您永远不会为分段样条曲线选择这些点,但如果对象在更新之间突然转向,您可能会得到它们。

如果我的电脑现在没有坏,我会在这里放置精美的图形,就像我发布到 TeX.SX 上的图形一样。不幸的是,我现在不得不放弃这些。

这比直线插值好吗?绝对:线性插值会得到直线路径,二次样条曲线不会平滑,高阶多项式可能会过拟合。三次样条是解决该问题的标准方法。

它们是否更适合外推,您尝试预测游戏对象会去哪里?可能不是:这样一来,你假设一个正在加速的玩家会继续加速,而不是他们会立即停止加速,这可能会让你走得更远。但是,更新之间的时间应该很短,所以你不应该走得太远。

最后,您可以通过更多的动量守恒进行编程,从而让事情变得更轻松。如果物体转动、加速或减速的速度受到限制,则它们的路径将无法与您根据它们的最后位置和速度预测的位置相差太大。

【讨论】:

这似乎过于复杂。我不确定这些值中的每一个在计算游戏对象的运动方面代表什么。你介意概括/简化你的答案吗? 如果正在减速的物体可能会继续减速,而正在加速的物体可能会继续加速,这样会更好。如果对象反其道而行之,情况会更糟。如果某些东西刹车和向右转弯可能会继续做同样的事情,你可能会更成功地预测 r 和 theta 而不是 x 和 y。如果物体总是在不可预测地改变方向,那么无论如何你都无法预测它们。 但是插值三次样条几乎是从几个已知点生成平滑路径的标准方法。我实际上并没有为此目的尝试过它们,但我知道这个理论。 这个答案建议使用位置和速度在两个连续的时间点之间插入三次曲线。事实上,如果它专注于想法而不是实现细节会更好。图片也很酷 =) 另外,在这里使用 BLAS 也不是一个好主意,因为系统非常很小。只需使用您拥有的任何方便的工具来解决它,不要乱用 HPC 计算。 好的。重写以提供对三次样条信息而不是大量数学信息的有用参考。【参考方案3】:

根据您的游戏,您可能更喜欢流畅的玩家移动而不是超精确的位置。如果是这样,那么我建议以“最终一致性”为目标。我认为您保持“真实”和“模拟”数据点的想法是一个很好的想法。只要确保不时强制模拟与真实收敛,否则差距会变得太大。

关于您对不同移动速度的担忧,我建议您在数据包中除了当前位置外,还包括玩家当前的速度和方向。这将使您能够根据自己的帧速率/更新时间更顺畅地预测播放器的位置。

基本上,您将计算当前模拟速度和方向,同时考虑最后模拟位置和速度以及最后已知位置和速度(更强调第二个),然后根据它模拟新的位置。

如果模拟和已知之间的差距太大,只需更加强调已知位置,其他玩家就会更快地赶上。

【讨论】:

你能提供一些代码示例吗?将速度与数据包一起发送的问题是,当其他玩家突然停止移动时,他们的速度设置为 0,这意味着他会比他在自己这边更快地停止移动,导致他停在错误的位置。 我没有关注你的担忧。如果速度为 0,那么您也已经知道小圈子中的精确位置,那么您可以简单地调整速度以匹配位置与任何感觉合适的速度限制。 所以你建议我发送玩家 x,y,speed,direction 然后根据这个速度让他们到那个 x,y 。当一个新的数据包进来时,只需将目标更新为新的 x,y 并使用新的速度移动?这会顺利进行吗?感谢您的回复帕特里克。您能给我一个伪代码示例,以便我了解您的意思吗? 您可能希望查看Kalman filter 作为此功能的更高级版本,尽管它可能超出或不适合此目的。 Udacity 对它们有一个不错的部分。 @TastyLemons,使用样条曲线意味着您可以在当前离散轨迹上拟合平滑曲线。当然,这样会很顺利。

以上是关于多人游戏 - 客户端插值计算?的主要内容,如果未能解决你的问题,请参考以下文章

如何检测客户端是不是故意离开 Google Play 游戏实时多人游戏中的房间?

使用Django进行多人游戏,服务器和客户端之间的通信使用啥?

多人游戏是不是应该总是根据每个客户端请求从数据库中请求数据?

多人游戏统一

用于实时多人游戏的 Node.js UDP

如何在 Unity Photon 多人游戏中更改其他客户端变量?