Fast-Paced Multiplayer (Part II): Client-Side Prediction and Server Reconciliation

原文:http://www.gabrielgambetta.com/fpm2.html

Introduction

In the first article of this series, we explored a client-server model with an authoritative server and dumb clients that just send inputs to the server and then render the updated game state when the server sends it.

A naive implementation of this scheme leads to a delay between user commands and changes on the screen; for example, the player presses the right arrow key, and the character takes half a second before it starts moving. This is because the client input must first travel to the server, the server must process the input and calculate a new game state, and the updated game state must reach the client again.

Effect of network delays.Effect of network delays.

In a networked environment such as the internet, where delays can be in the orders of tenths of a second, a game may feel unresponsive at best, or in the worst case, be rendered unplayable. In this article, we’ll find ways to minimize or even eliminate that problem.

Client-side prediction

Even though there are some cheating players, most of the time the game server is processing valid requests (from non-cheating clients and from cheating clients who aren’t cheating at that particular time). This means most of the input received will be valid and will update the game state as expected; that is, if your character is at (10, 10) and the right arrow key is pressed, it will end up at (11, 10).

We can use this to our advantage. If the game world is deterministic enough (that is, given a game state and a set of inputs, the result is completely predictable),

Let’s suppose we have a 100 ms lag, and the animation of the character moving from one square to the next takes 100 ms. Using the naive implementation, the whole action would take 200 ms:

Network delay + animation.Network delay + animation.

Since the world is deterministic, we can assume the inputs we send to the server will be executed successfully. Under this assumption, the client can predict the state of the game world after the inputs are processed, and most of the time this will be correct.

Instead of sending the inputs and waiting for the new game state to start rendering it, we can send the input and start rendering the outcome of that inputs as if they had succeded, while we wait for the server to send the “true” game state – which more often than not, will match the state calculated locally :

Animation plays while the server confirms the action.Animation plays while the server confirms the action.

Now there’s absolutely no delay between the player’s actions and the results on the screen, while the server is still authoritative (if a hacked client would send invalid inputs, it could render whatever it wanted on the screen, but it wouldn’t affect the state of the server, which is what the other players see).

Synchronization issues

In the example above, I chose the numbers carefully to make everything work fine. However, consider a slightly modified scenario: let’s say we have a 250 ms lag to the server, and moving from a square to the next takes 100 ms. Let’s also say the player presses the right key 2 times in a row, trying to move 2 squares to the right.

Using the techniques so far, this is what would happen:

Predicted state and authoritative state mismatch.Predicted state and authoritative state mismatch.

We run into an interesting problem at t = 250 ms, when the new game state arrives. The predicted state at the client is x = 12, but the server says the new game state is x = 11. Because the server is authoritative, the client must move the character back to x = 11. But then, a new server state arrives at t = 350, which says x = 12, so the character jumps again, forward this time.

From the point of view of the player, he pressed the right arrow key twice; the character moved two squares to the right, stood there for 50 ms, jumped one square to the left, stood there for 100 ms, and jumped one square to the right. This, of course, is unacceptable.

Server reconciliation

The key to fix this problem is to realize that the client sees the game world in present time, but because of lag, the updates it gets from the server are actually the state of the game in the past. By the time the server sent the updated game state, it hadn’t processed all the commands sent by the client.

This isn’t terribly difficult to work around, though. First, the client adds a sequence number to each request; in our example, the first key press is request #1, and the second key press is request #2. Then, when the server replies, it includes the sequence number of the last input it processed:

Client-side prediction + server reconciliation.Client-side prediction + server reconciliation.

Now, at t = 250, the server says “based on what I’ve seen up to your request #1, your position is x = 11”. Because the server is authoritative, it sets the character position at x = 11. Now let’s assume the client keeps a copy of the requests it sends to the server. Based on the new game state, it knows the server has already processed request #1, so it can discard that copy. But it also knows the server still has to send back the result of processing request #2. So applying client-side prediction again, the client can calculate the “present” state of the game based on the last authoritative state sent by the server, plus the inputs the server hasn’t processed yet.

So, at t = 250, the client gets “x = 11, last processed request = #1”. It discards its copies of sent input up to #1 – but it retains a copy of #2, which hasn’t been acknowledged by the server. It updates it internal game state with what the server sent, x = 11, and then applies all the input still not seen by the server – in this case, input #2, “move to the right”. The end result is x = 12, which is correct.

Continuing with our example, at t = 350 a new game state arrives from the server; this time it says “x = 12, last processed request = #2”. At this point, the client discards all input up to #2, and updates the state with x = 12. There’s no unprocessed input to replay, so processing ends there, with the correct result.

Odds and ends

The example discussed above implies movement, but the same principle can be applied to almost anything else. For example, in a turn-based combat game, when the player attacks another character, you can show blood and a number representing the damage done, but you shouldn’t actually update the health of the character until the server says so.

Because of the complexities of game state, which isn’t always easily reversible, you may want to avoid killing a character until the server says so, even if its health dropped below zero in the client’s game state (what if the other character used a first-aid kit just before receiving your deadly attack, but the server hasn’t told you yet?)

This brings us to an interesting point – even if the world is completely deterministic and no clients cheat at all, it’s still possible that the state predicted by the client and the state sent by the server don’t match after a reconciliation. The scenario is impossible as described above with a single player, but it’s easy to run into when several players are connected to the server at once. This will be the topic of the next article.

Summary

When using an authoritative server, you need to give the player the illusion of responsiveness, while you wait for the server to actually process your inputs. To do this, the client simulates the results of the inputs. When the updated server state arrives, the predicted client state is recomputed from the updated state and the inputs the client sent but the server hasn’t acknowledged yet.


这个错误是由于无法连接到本地主机的10248端口导致的。这个端口通常是kubelet进程监听的端口,用于健康检查。出现这个错误可能是由于kubelet进程没有正确启动或者配置错误导致的。 解决这个问题的方法是检查kubelet进程的状态和配置。你可以按照以下步骤进行操作: 1. 检查kubelet进程是否正在运行。你可以使用以下命令检查kubelet进程的状态: ```shell systemctl status kubelet ``` 如果kubelet进程没有运行,你可以使用以下命令启动它: ```shell systemctl start kubelet ``` 2. 检查kubelet的配置文件。你可以使用以下命令查看kubelet的配置文件路径: ```shell kubelet --kubeconfig /etc/kubernetes/kubelet.conf --config /var/lib/kubelet/config.yaml --bootstrap-kubeconfig /etc/kubernetes/bootstrap-kubelet.conf config view ``` 确保配置文件中的端口号和地址正确,并且与你的环境相匹配。 3. 检查网络连接。你可以使用以下命令检查是否可以连接到localhost10248端口: ```shell curl -sSL http://localhost:10248/healthz ``` 如果无法连接,请确保端口没有被防火墙或其他网络配置阻止。 4. 检查docker的配置。有时候,kubelet进程依赖于docker进程。你可以按照以下步骤检查docker的配置: - 创建/etc/docker目录: ```shell sudo mkdir /etc/docker ``` - 编辑/etc/docker/daemon.json文件,并添加以下内容: ```json { "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" }, "storage-driver": "overlay2", "storage-opts": [ "overlay2.override_kernel_check=true" ], "registry-mirrors": ["https://tdhp06eh.mirror.aliyuncs.com"] } ``` - 重启docker进程: ```shell systemctl restart docker ``` 请注意,以上步骤是一种常见的解决方法,但具体解决方法可能因环境而异。如果以上步骤无法解决问题,请提供更多的错误信息和环境配置,以便我们能够更好地帮助你。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值