Physics in online games
By Glenn Fiedler
Translator: trcj
Original article: http://gafferongames.com/game-physics/networked-physics/
Introduction
Hello, everyone. Welcome to read the last article in the game physics series. I'm grenfiler.
In the previous article, we discussed how to use spring-like forces to simulate basic collisions, joints, and motors.
Now we will discuss how to perform physical simulation in the network environment.
Physical Simulation in the network is the treasure of multiplayer online games, and its wide application in the first-person shooting games is evidenced by its outstanding performance.
This article will show you how to apply the key networking skills from a first-person shooter game to your online games.
First-person shooting game
The physics of the first-person shooting game is very simple. The world is static, and role operations are basically restricted between running, jumping, and shooting. In order to prevent cheating, a first-person shooting game usually uses a client-server model, that is, a physical simulation operation is performed on the server. The client displays the approximate value of the calculation result to the player.
The problem is how to make every client display the behavior of other players as reasonably as possible while controlling its own players.
To achieve this in a concise and elegant manner, we have made the following Architecture for Physical Simulation:
1. Role physics is completely driven by player input;
2. The physical status can be completely encapsulated in a structure;
3. Given the initial state and the same input, physical simulation can be properly reproduced.
In this way, we need to encapsulate the player input driving the physical into a structure and encapsulate the status information used to display the player behavior into another one. The following is a simple example of a shooting game that only includes running and jumping:
struct Input{bool left;bool right;bool forward;bool back;bool jump;};struct State{Vector position;Vector velocity;};
Next, we need to make sure that the physical simulation can provide the same results as possible when the initial state and input are the same. It is not necessary to be accurate to the floating point precision. The results can be basically consistent within 1 to 2 seconds.
Network Programming Basics
Before solving the important problem of sending data, I would like to briefly discuss several issues related to network programming. In fact, the network is a pipe, and it is not complicated at all, right? Error! If you ignore the network principle, you will not feel any pain. Here are two basic knowledge you must know:
First, if your network programmer is skilled, he will use the unreliable data protocol UDP and establish a specific application network layer on it. The key point is that as a physical programmer, when you design a physical system to obtain the input and status information from the network layer, it should be able to handle packet loss. Otherwise, your system will be blocked and frozen when the network is poor.
Second, due to bandwidth limitations, you will have to compress data. As a physical programmer, you need to be cautious about this option. For accuracy, some data cannot be reduced, while others do not. Any lossy compressed data should be quantified as much as possible to ensure consistency between the two parties. To be as efficient as possible without damaging the simulation is the bottom line that should be followed at this time.
For more details, see my latest series of articles "network programming for game programmers".
Client input driver server Physical Simulation
The basic unit used for communication between our servers and clients is an unreliable data block. If you like it, it can be called an unreliable non-blocking Remote Process Call (RPC ). Non-blocking means that after the client sends the RPC to the server, it does not wait for the server to execute, but directly starts executing other code. Unreliable means that although the client sends RPC messages in an orderly manner, some calls do not reach the server, while others may be out of order when they arrive. These should be taken into account during the design to adapt to the network transport layer (UDP) Rules.
It can be seen that communication between the client and the server is completed through continuous RPC calls. I call it an "input stream ". This input stream is capable of handling packet loss and out-of-order key techniques, is to add a timestamp in each RPC packet. The server ignores packets earlier than the current time based on the local time, so that the unordered packets can be effectively excluded. Ignore the lost packages.
Back to the first-person shooting game example, we have defined the data structure from the client to the server:
struct Input{bool left;bool right;bool forward;bool back;bool jump;};class Character{public:void receiveInput(float time, Input input); // rpc method called on server};
This is the most basic data needed to describe a simple set of ground movements that contain jumps in the network. If you want to support player shooting, you need to add the mouse in this structure, because the opening of fire also needs to be determined on the server side.
Have you noticed that I have used RPC as a member function of a class? I assume that your network programmer has designed some pipeline structure on the UDP layer, and some structure that can correspond RPC to remote clients one by one.
Next, what should the server do with these RPC calls? Basically, it polls the input of each client in a loop. When receiving RPC from the client, the server calculates the physical status of the corresponding role. This means that the role status of the client may be slightly different from that of the server, and the role status may be too advanced or lagging behind. In general, different roles are updated while maintaining general synchronization.
Let's take a look at how these RPC calls are implemented in the server code:
void receiveInput(float time, Input input){if ( time < currentTime )return;const float deltaTime = currentTime - time;updatePhysics( currentTime, deltaTime, input );}
The main point of this Code is that the server updates the physical status of the role only when it receives input from the corresponding client. This ensures that it is fault tolerant to the latency or jitter generated during RPC transmission.
Result of client-side server deduction
Now, when the server sends a message back to the client. The server will produce a large amount of communication to broadcast to all clients.
After each physical update operation driven by the client RPC is completed, the server needs to broadcast the latest physical status to all servers.
The information is still sent to the client in the form of unreliable RPC:
void clientUpdate(float time, Input input, State state){Vector positionDifference = state.position - currentState.position;float distanceApart = positionDifference.length();if ( distanceApart > 2.0 )currentState.position = state.position;else if ( distanceApart > 0.1 )currentState.position += positionDifference * 0.1f;currentState.velocity = velocity;currentInput = input;}
The code above indicates that if the location difference between the two parties is too large (> 2 m), the role is directly placed in the server location; if the location difference is greater than 10cm, the role moves 10% from the current location to the server location; otherwise, the role will not be processed.
Because Server Update RPC needs to broadcast to the client, moving only a short segment to the target produces a smooth correction effect. This technique is called exponential smoothing moving average.
The side effects of this kind of smooth processing will lead to a certain degree of lag, but nothing is perfect in the world. We recommend that you only perform smooth processing on the intuitive data, such as position and rotation. Derivative data such as speed and angular velocity is not necessary because the sudden changes of values are not conspicuous in the derivative data.
Of course, these are just experiences. You should find the best practice for yourself.
Client Prediction
So far, our solution is to use the client input to drive the server to perform physical operations, broadcast the computing results, and then maintain an approximate server value accordingly. This is a perfect practice, but it has a major drawback. Delay!
When the player presses the forward direction key, the input needs to go to the server for a lap and return to the client again before the player's role can start to move. Those familiar with quake should be familiar with this effect. This problem was fixed in the subsequent quakeworld and introduced a method called client pre-judgment. This technology completely removes mobile latency and becomes the standard network processing skill for the next first-person shooter game.
The client pre-judgment method calculates the physical result directly after the player inputs, instead of waiting for the player to go to the server to complete the round. The server regularly sends the correct data to the client for verification. At any time, the physical status of the role is based on the server. In this way, even if the client is cheated, the physical system of the server will not be affected. Because all game logic runs on the server, client cheating can be basically eliminated.
The complexity of client-side prediction lies in how to process the proofreading information from the server. Due to the communication delay between the client and server, the proofreading information from the server is always "outdated. We need to go back to "past" to proofread the data and then calculate the current exact position accordingly.
The standard practice is to maintain a circular buffer on the client to save user input. Each input corresponds to an RPC call from the client to the server:
struct Move{float time;Input input;State state;};
Each time the client receives a verification data, it compares the physical status in the data with the physical status at the same time point in the buffer. If the difference between the two exceeds a certain threshold value, the client will return to this time point and re-calculate the subsequent input stored in the buffer based on the correct data:
const int maximum = 1024;Move moves[maximum];void advance(int &index){index ++;if (index>=maximum)index -= maximum;}int head = 0;int tail = 100; // lets assume 100 moves are currently storedvoid clientCorrection(float time, State state, Input input){while (time>moves[index].time && head!=tail)advance(head); // discard old movesif (head!=tail && time==moves[head].time){if ((moves[head].state.position-currentState.position).length>threshold){// rewind and apply correctioncurrentTime = time;currentState = state;currentInput = input;advance(head); // discard corrected moveint index = head;while (index!=tail){const float deltaTime = moves[index].time - currentTime;updatePhysics(currentTime, deltaTime, currentInput);currentTime = moves[index].time;currentInput = moves[index].input;moves[index].state = currentState;advance(index);}}}}
Sometimes, packet loss and disorder may cause inconsistency between server input and client storage. In this case, the rollback and re-calculation will force the role to the correct position. This forced instantaneous shift is too obvious, so we can use the same smoothing correction method as before. This should be done after the rollback and recalculation.
Disadvantages of client Prediction
Client pre-judgment seems perfect and incredible. A simple technique can completely eliminate latency. But is there any price? The answer is that when two physical bodies interact with each other on the server, the server will be forced to move instantly. Why? In fact, the client uses its own numerical value for physical deduction, but the results are quite different from those of the server. Instant migration.
In a simple FPS game that only supports running and jumping, a player passes through another player, tries to stand on the head of another player, or is overturned by an explosion. After all, any physical state changes caused by non-player input will result in instantaneous migration. In fact, there is no way to avoid discarding the client's pre-judgment results and directly using the server results.
This reminds me of an interesting question. Online Games are quietly evolving from the first-person shooting game in the static world to interacting with others and the surrounding environment in the dynamic world. In view of this trend, I would like to make a bold prediction: the client pre-judgment method described in this article may soon become obsolete.
Online physical Overview
So far, the solutions presented in this article have run well in the first-person shooting game. The main limitation is that each client has a clear ownership of each role, that is, in most cases, the client is the only factor affecting the role's physical.
This simplification assumption is the basis for the conventional technology of first-person shooter games. If your physical system is similar to this, these skills are your appetite. For example, in a racing game where each player controls a car, you only need to add a few additional physical states and user input to extend the system.
But if you want to make a physical game, there is no clear object ownership. For example, if there is a pile of squares, players can click and drag any square. At this time, no object exclusive to a specific player may even be dragged by multiple players at the same time. Maybe one player is standing on the square, and other players are deciding to drive and hit the square! Complicated enough!
In this case, more technologies need to be introduced. Client pre-judgment is obviously not competent, because it will lead to serious instant migration. The key to the problem is that the server cannot wait until it receives the input from the client to perform physical updates, because the object no longer clearly belongs to a client as in the first-person shooter game.
This means that physical updates on the server seem to be closer to the traditional approach. All objects are synchronously updated based on the client input they finally receive. This makes the server more sensitive to network issues such as packet delay and packet accumulation, and requires more work to ensure synchronization between the client and the server.
Solving these problems will be a challenge. I also hope that the solution and source code will be provided in the future.
Summary
Physical Simulation in online games is complicated. Mastering the core technologies used in the first-person shooting game makes it easier to understand.
I made a demo to match this article. In the demo, I used a cube to replace the FPS role. You can manipulate the cube for running and jumping. Sorry, you cannot shoot!
The demo contains a lot of visual content to help you understand concepts such as inverted, re-calculation, and smooth processing. Please download it now and have a try!