通过networking插值 – 在1v1游戏中接收和绘制对手

我是一个新手Java编码器,我正在创建一个权威服务器和两个客户端的1v1游戏。

我的整个networking是基于UDP数据包的:

  • 从客户发送职位
  • 从服务器发送确认(并在客户端删除旧的)
  • 从服务器发送“非被欺骗”的位置给对手

一切都很好,直到最后一个问题出现。

当我从服务器向对手发送位置时,他们在不同的时间被接收。 一个例子:

  • 位置1在43毫秒
  • 位置2在15毫秒(来自前一个)
  • 位置3在34毫秒
  • 位置4在10毫秒内
  • 位置5在35毫秒内

众所周知,这就是联网,每个数据包都在一个常量内:“30 ms”,这是完全不可能的。

而我的问题来了,我不知道如何调整“对手”的插值,就像一个普通玩家(我在我的手机上)那样平滑。

当这个过程是静态的时候(我点击箭头并且一步一步地移动我的玩家),这真的很平稳。 我的gameloop有一些时间,它运作得非常好。 但是,当我收到对手的位置并设置了'期望位置(x,y)'时,他的移动不是平滑的,他的'位置'不是在一个固定的时间内“更新”,而是随机的延迟(因为我们得到这些位置的时间不同)。

我的问题的例子:

gameloop:

@Override public void run(){ long beginTime; // the time when the cycle begun long timeDiff; // the time it took for the cycle to execute int sleepTime; // ms to sleep (<0 if we're behind) int framesSkipped; // number of frames being skipped while(match_running){ beginTime = System.currentTimeMillis(); framesSkipped = 0; // resetting the frames skipped // update game state // MOST IMPORTANT METHODS - look below player_me.updatePosition(); // move me (smoothly) to desired position player_op.updatePosition(); // move the opponent (smoothly) // render state to the screen // draws the canvas on the panel repaint(); // calculate how long did the cycle take timeDiff = System.currentTimeMillis() - beginTime; // calculate sleep time sleepTime = (int)(FRAME_PERIOD - timeDiff); if (sleepTime > 0) { // if sleepTime > 0 we're OK try { // send the thread to sleep for a short period // very useful for battery saving Thread.sleep(sleepTime); } catch (InterruptedException e) {} } while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) { // we need to catch up // update without rendering player_me.updatePosition(); // move me to desired position player_op.updatePosition(); // move the opponent // add frame period to check if in next frame sleepTime += FRAME_PERIOD; framesSkipped++; } // sending positions every 30 seconds time_now = System.currentTimeMillis(); if(time_now >= (packet_past_time + 30)) { sendPositions(); packet_past_time = System.currentTimeMillis(); } } } 

聆听“对手的位置”:

 public void received(Object o){ if(object instanceof OpponentPositionList) player_op.setDesiredPosition(object.x, object.y); } 

所以我们设定了他的新“理想位置”。 Gameloop总是调用他的'updatePosition()' ,这应该将他移动到'想要的位置'。 和“Player Me”完全一致,因为它不断地更新位置,但是当从对手那里“不是经常地”接收到位置时它会冻结,

 public void updatePosition(){ double xDistance = desiredX - this.x; double yDistance = desiredY - this.y; double distance = Math.sqrt(xDistance * xDistance + yDistance * yDistance); if (distance > 1) { this.x += xDistance * 0.5; this.y += yDistance * 0.5; }else{ this.x = Math.round(this.desiredX); this.y = Math.round(this.desiredY); } } 

主要问题是:

如果职位不是“经常”,随机10-50毫秒延迟如何吸引对手像这个设备上的普通球员那样stream畅? 用某种“定时插值”或者什么? 任何建议将是真棒。 谢谢!

你的问题归结为一个答案,导致客户端预测你的游戏中远程实体的复制移动。 有几个理论和实现,你可以为此。 线性插值是客户预测服务器将在哪里告诉它各种对象在哪里的stream行方式。 这里有一些关于这个主题的资料。

运动队列

这是一个很好的例子/讨论,谈论不同的方法和他们的成功/失败 。 这是有效的,因为它不是特定于任何语言,可以应用于任何游戏引擎。 它有一个方法可以保存(客户端)客户端的动作。 然后,作为一个数据包,您将它们发送到服务器。 服务器将这个客户端移动包发送给所有感兴趣的各方(其他客户端)。 这很简洁,因为如果你有一个很大的延迟,而不是传送角色,你只需重放客户端收到的远程实体保存的数据包。

曲线

然后就是用曲线在行星湮灭中看到的方法。 (PS链接在文章讨论中有很多链接这个话题)。 客户端本质上是告诉服务器在特定时间的位置。 从那里,服务器使用一个时间和位置列表,告诉所有客户在哪个实体在哪里。 从那里客户可以使用时间和位置对平滑运动。 真是太棒了,他们也提到这个,就是免费获得免费的游戏重播数据。

Tick Rate Entity插值

第三种方法是在Source引擎多人联网指南中find的 。 这个例子非常简单。 它涉及一些插值math和使用滴答率。 可能是最简单的实现。

线性插值

要确保在时间T,玩家B将出现在玩家A的位置L?

让玩家A加快玩家B的动作,让他们及时到达那里。

例如,运动应该持续5秒。 数据包需要1秒,所以玩家A使玩家B以他平常速度的1.25倍移动。

没有插值

数据包通常需要250ms以内才能到达目的地,所以你可以保持B的渲染速度,让玩家A在250ms后看到动作完成。

除非你的游戏速度非常快,networking特别滞后,否则他们不会注意到这一点。