[译]二.客户端预测和协调服务器(游戏中的同步)

原文:https://www.gabrielgambetta.com/client-side-prediction-server-reconciliation.html

第一篇很简单, 不翻译啦. 

 

上篇文章中的根据权威中心服务器取同步位置的方法, 玩家那里显示的是一样的, 但是会导致玩家输入和屏幕显示的延迟,例如:按下向右的箭头, 但是角色在半秒之后才开始移动.这是因为客户端输入首先需要发送到服务器端,服务器必须处理输出并计算新的游戏状态,并且更新的游戏状态必须再次发送到达客户端.

Effect of network delays.

在网络环境中,如互联网,延迟可能在十分之一秒左右,一个游戏在最好的情况下可能没感觉出延迟,但是在在最坏的情况下,是无法玩的。在本文中,我们将找到最小化甚至消除该问题的方法。

 

客户端预测

即使有一些作弊玩家,大多数时候游戏服务器都在处理有效的请求(来自非作弊客户端和在特定时间不作弊的客户端)。这意味着接收到的大部分输入都是有效的,并将按预期更新游戏状态;也就是说,如果您的角色位于(10,10)并且按下了右箭头键,则它将结束于(11,10)。

我们可以利用这个优势。如果游戏世界具有足够的确定性(即,给定一个游戏状态和一组输入,结果完全可以预测)。

假设我们有100毫秒的延迟,角色从一个正方形移动到下一个正方形的动画需要100毫秒。使用简单的实现,整个操作需要200毫秒:

Network delay + animation.

由于世界是确定性的,所以我们可以假设发送到服务器的输入将成功执行。在这个假设下,客户端可以在处理输入之后预测游戏世界的状态,并且大多数情况下这是正确的。

与发送输入并等待新的游戏状态开始呈现不同,我们可以发送输入并开始展示输入的结果,就好像它们已经成功一样,而我们则等待服务器发送“真实”的游戏状态,这通常会与本地计算的状态相匹配:

Animation plays while the server confirms the action.

现在玩家的行为和屏幕上的结果之间完全没有延迟,而服务器仍然是权威的(如果被黑客攻击的客户端发送无效的输入,它可以在它自己的屏幕上展示任何它想要的内容,但不会影响服务器的状态,也不会影响到其他玩家看到的)。

同步问题

在上面的例子中,我仔细地选择了这些数字,以使所有的工作都正常。但是,考虑一个稍微修改过的场景:假设我们与服务器之间有250毫秒的延迟,从一个方块移动到下一个方块需要100毫秒。假设玩家连续按了2次右键,试图将2个方块移动到右侧。

使用到目前为止的技术,这就是将要发生的事情:

Predicted state and authoritative state mismatch.

当新游戏状态到来时,我们在t=250毫秒时遇到了一个有趣的问题。客户端的预测状态是x=12,但是服务器说新的游戏状态是x=11。因为服务器是权威的,所以客户机必须将字符移回x=11。但是,一个新的服务器状态到达t=350,表示x=12,所以字符再次跳,这次向前。

从玩家的角度来看,他按了两次右箭头键;角色向右移动了两个方块,站在那里50毫秒,向左跳了一个方块,站在那里100毫秒,向右跳了一个方块。当然,这是不可接受的。

协调服务器

解决这个问题的关键是要认识到客户端在当前时间看到了游戏世界,但是由于延迟,从服务器获取的更新实际上是过去的游戏状态。到服务器发送更新的游戏状态时,它还没有处理客户端发送的所有命令。

不过,这并不难解决。首先,客户端向每个请求添加一个序列号;在我们的示例中,第一个按键是请求1,第二个按键是请求2。然后,当服务器回复时,它包括它处理的最后一个输入的序列号:

Client-side prediction + server reconciliation.

现在,在t=250时,服务器会说“根据我对您请求的了解,您的位置是x=11”。因为服务器是权威的,所以它将字符位置设置为x=11。现在让我们假设客户端保留它发送到服务器的请求的操作。根据新的游戏状态,它知道服务器已经处理了请求1,因此它可以丢弃该操作。但是它也知道服务器仍然需要发送处理请求2的结果。因此,再次应用客户端预测,客户端可以根据服务器发送的最后一个授权状态,加上服务器尚未处理的输入,计算出游戏的“当前”状态。

因此,当t=250的时候,客户机得到“x=11,最后处理的请求1”。它丢弃发送的输入的操作#1–但它保留了#2的操作,服务器尚未确认该操作。它用服务器发送的x=11更新它的内部游戏状态,然后应用服务器仍然看不到的所有输入——在现在这种情况下,就是输入2,“向右移动”。客户端最终结果是x=12,这是正确的。

继续我们的示例,在t=350时,服务器将进入一个新的游戏状态;这次它将显示“x=12,上次处理的请求#2”。此时,客户端将丢弃所有输入到#2,并用x=12更新状态。现在没有未处理的输出的请求要去执行,因此处理将以正确的结果结束。

零碎提示

上面讨论的例子只提到了运动,但同样的原理几乎可以应用于其他任何事情。例如,在基于回合的战斗游戏中,当玩家攻击另一个角色时,您可以显示血液和表示所造成伤害的数字,但在服务器这样说之前,您不应该更新角色的健康状况。

由于游戏状态的复杂性,这并不总是容易逆转的,你可能想避免杀死一个角色,直到服务器这样说,即使它的血量在客户端的游戏状态下降到零以下(如果另一个角色在收到致命攻击之前使用了急救包,但服务器还没有告诉你怎么办?)

这给我们带来了一个有趣的观点——即使世界是完全确定的,并且没有客户机作弊,客户机预测的状态和服务器发送的状态在协调后仍然可能不匹配。上面这种情况当只有一个玩家的时候是不可能遇到的,但当几个玩家同时连接到服务器很容易遇到。这将是下一篇文章的主题。

总结

当使用权威服务器时,您需要在等待服务器实际处理您的输入时给执行一个响应的假象。为此,客户端模拟输入的结果。当更新的服务器状态到达时,预测的客户端状态将从更新的状态和客户端发送但服务器尚未确认的输入重新计算。

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,在 Python 使用 socket.io 进行通信有以下流程: 1. 在服务器,首先要创建一个 socket.io 服务器,并绑定一个口号。 2. 然后在客户,创建一个 socket.io 客户并连接到服务器的指定口。 3. 在服务器,可以使用监听函数来等待客户的连接,并在接收到连接后进行相应的处理。 4. 在客户,可以使用监听函数来接收服务器的消息,并进行相应的处理。 5. 在服务器客户,可以使用 send 函数来向对方发送消息。 6. 在客户服务器之间的通信可以是双向的,即客户可以向服务器发送消息,服务器也可以向客户发送消息。 希望这对你有帮助。 ### 回答2: Python的Socket.io是一个用于处理实时应用程序的库,可以在服务器客户之间进行双向通信。下面是Python Socket.io服务器客户之间的通信流程: 1. 服务器初始化:在服务器,首先通过导入相关的库,如socketio和eventlet,创建一个Socket.io服务器实例。 2. 客户连接:当一个客户想要和服务器进行通信时,它需要连接到服务器客户可以使用与服务器相同的库(如socketio)来进行连接。客户发送连接请求到服务器,并等待服务器的响应。 3. 服务器接受连接:服务器接收到客户的连接请求后,会生成一个唯一的会话ID,并将该ID发送给客户作为响应。这个会话ID用于后续的通信标识。 4. 双向通信:一旦客户服务器建立了连接,并通过会话ID进行了标识,它们可以开始进行双向通信。客户可以向服务器发送消息,也可以接收服务器发送的消息。 5. 服务器处理消息:服务器收到客户发送的消息后,可以根据消息的内容进行相应的处理。服务器可以使用不同的事件进行消息的分类,如"message"事件表示普通消息,"join"事件表示加入/离开房间等。 6. 客户接收消息:当服务器处理完消息后,可以将处理结果发送给客户客户可以监听不同的事件,并针对特定的事件进行处理。这样,客户就可以根据服务器发送的消息来更新UI或执行其他操作。 7. 断开连接:当客户服务器想要断开连接时,它们可以发送一个断开连接的请求。这个请求可以是双向的,任一方发送断开连接请求后,另一方都会接收到该请求,并断开连接。 总结来说,Python Socket.io服务器客户之间的通信流程包括服务器初始化、客户连接、服务器接受连接、双向通信、服务器处理消息、客户接收消息和断开连接。这样,服务器客户可以进行实时的双向通信。 ### 回答3: Python Socket.IO 是一个基于 Python 的库,用于实现实时的双向通信。它使用了 WebSocket 协议,使得服务器客户能够进行全双工通信。下面是 Python Socket.IO 服务器客户的通信流程: 1. 服务器启动:首先,服务器使用 `socketio.Server()` 创建一个 Socket.IO 服务器对象。 2. 客户连接:客户使用 `socketio.Client()` 创建一个 Socket.IO 客户对象。然后,客户使用 `client.connect()` 方法连接到服务器。 3. 服务器监听连接事件:服务器使用 `@server.on('connection')` 装饰器监听客户连接事件。当客户成功连接到服务器时,会触发这个事件。 4. 客户发送消息:客户使用 `client.emit()` 方法发送消息。可以自定义事件名称和消息内容。 5. 服务器监听消息事件:服务器使用 `@server.on('message')` 装饰器监听客户发送的消息事件。当客户发送消息时,服务器会接收到,并触发这个事件。 6. 服务器发送消息:服务器可以使用 `server.emit()` 方法发送消息给客户。同样可以自定义事件名称和消息内容。 7. 客户监听消息事件:客户使用 `client.on()` 方法监听服务器发送的消息事件。当服务器发送消息时,客户会接收到,并触发这个事件。 8. 客户断开连接:客户可以使用 `client.disconnect()` 方法主动断开与服务器的连接。 9. 服务器监听断开连接事件:服务器使用 `@server.on('disconnect')` 装饰器监听客户断开连接事件。当客户断开与服务器的连接时,会触发这个事件。 以上是 Python Socket.IO 服务器客户的基本通信流程。可以根据需要扩展其他功能,如加入认证、使用房间等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值