⬆️ ⬇️

Network layer physics engine

Hello. One game project on box2d physics has become more, finished online fighting game on ragdoll physics. I would like to draw some conclusions and tell about the main technical problems that I had to face and about the methods of their solution. The article will interest beginner developers of [flash-] games using physics engines. Focus is on the network layer for the physics engine.





We recommend to see the blog on the topic - http://gafferongames.com/game-physics/ . There is an excellent comparison of TCP and UDP in relation to games.



Adobe Cirrus


')

Initially, Adobe Cirrus was chosen as the network transport for the physics engine, or RTMFP (Real Time Media Flow Protocol) in a different way. The technology is positioned as a protocol characterized by low latency, p2p topology, security and scalability, and most suitable for developing real-time applications with the interaction of many users.

For an application that is intensive in terms of network traffic, p2p support seemed like a big plus. The promised low pings would be very useful for an online fighting game.



RTMPF is a protocol over UDP. UDP support for flash applications at the time of this writing (09/08/2011) is not entered. The DatagramSocket class is in beta testing and is available only from air.



Refer to RTMPF as udp incorrectly. In different modes of operation RTMFP has different similarities and differences with UDP. Top Krcha justifies the choice of DIRECT_CONNECTIONS mode for games (

www.flashrealtime.com/building-p2p-multiplayer-games-at-adobe-max-2010 ). In this mode, Cirrus provides guaranteed message delivery, i.e. the advantage of small ping is lost. Characteristics of the protocol for different modes of operation and data types can be viewed in the PDF slides of the same presentation. In this article, “pinging” means the time of message delivery from one client to another. In practice, this ping was far from small, often unstable and with high lags.



Synchronization of objects of the physical world




The task is to ensure the consistency of the state of the physics world on different clients.

The traditional solution is to use the server. The server is authoritative, state is stored on it, clients constantly read their subset of state from the server standard. Dignity - the only authoritative state allows you to avoid controversial game situations and resolve them consistently from the point of view of the game as a whole (who won?). The disadvantage is that with a large ping from the client to the server, the response on the client will be very slow, as it takes double the ping time before pressing the key leads to a visible movement of the object on the client.



Therefore, the server scheme is used more often along with client prediction. The server is still authoritative, but the client models its own subset of the overall state. When the button is pressed forward, the movement on the client begins immediately - based on the results of local modeling. Periodically, the client makes corrections for its state so that it is synchronized with the server standard. The corrections are small in size, and the response to user input on the client is quick. There may be contradictions, for example, we win in terms of client status, but not in server conditions. The solution of disputable situations can be postponed until the next packet from the server (who won), or decisions on the client can be rolled back (the object jerks back, because on the server it moved forward more slowly than on the client).



The two previous options involve server monitoring of the entire state of the physical world. Objects that do not participate in the gameplay in principle may not be synchronized between clients. This applies to special effects, small flying objects, such as fragments. Further, you can use the mechanism of ownership of objects. Each client owns a certain part (the hero or any controlled object) of the state of the world and is authoritative in determining its condition.



Based on the choice of p2p, a hybrid scheme was chosen with customer ownership of the objects and “client prediction”. Each client owns his hero. FMS was not used, therefore, it was necessary to ensure complete synchronization of the world of customers through p2p. Each client models the world completely, which provides an acceptable response to user input. During the synchronization package, the client makes an amendment to the state of the opponent's hero (the client is responsible for the state of his hero).



Sync packs




Synchronize the state of physics only according to the directions and speeds will not succeed. Due to the errors of floating-point operations, the objects are out of sync very quickly (the same object at different points on different clients).



Sending user input to another client to use the received input for modeling on it is not suitable for the same reason — the rapid accumulation of errors and the divergence of objects on two clients. Position / angle data must be used. After receiving the targetState status packet from the remote client, we apply it to the local copy of the object owned by the remote client (pseudocode):



rival.applyState(targetState); ... void applyState(State targetState) { this.x = targetState.x; this.y = targetState.y; } 




The solution works, but because of the lags, the position of an object on neighboring packages can vary considerably, which is why we get a strong shaking or throwing from side to side. Therefore, smoothing is required in one form or another. The simplest option is instead of moving an object from the current point to the target, moving it to the center between these two points:



 void applyState(State targetState) { this.x = (this.x + targetState.x)*0.5; this.y = (this.y + targetState.y)*0.5; } 




Small shaking is eliminated, due to the delay introduced (when the target is reached) this method is comparable to a low-pass filter.

In our case, this did not help get rid of all the jumps, so smoothing was also introduced according to the forces. Instead of a forced installation of the object’s positions, we act on it with corrective forces / impulses to achieve the goal:



 void applyState(State targetState) { targetX = (this.x + targetState.x)*0.5; targetY = (this.y + targetState.y)*0.5; errorX = targetX - this.x; errorY = targetY - this.y; this.applyForce(k*errorX, k*errorY); } 




k - “stiffness” correction. In this case, the correction forces are proportional to the mismatch of positions, which gives some springiness to the objects. An alternative variant with normalization (errorX, errorY) showed unsuccessful results before applying force — it did not cope well with large mismatches.



However, sometimes there are lags for several seconds. In such cases, the last implementation of applyState is too slow and can lead to noticeable fluctuations. We work out such cases separately, forcing the target position (without interpolating the positions):



 void applyState(State targetState) { targetX = (this.x + targetState.x)*0.5; targetY = (this.y + targetState.y)*0.5; errorX = targetX - this.x; errorY = targetY - this.y; if(len(errorX,errorY)<MAX_SOFT_CORRECTION_ERROR) { this.applyForce(k*errorX, k*errorY); } else { this.x = (this.x + targetState.x)*0.5; this.y = (this.y + targetState.y)*0.5; } } 




Also, at large object accelerations (strikes, explosions, crashes), we temporarily switch to a correction with a forced position.



In case of non-guaranteed package delivery procedure or possible duplication of packages, it is enough to include the time stamp of the sending client at the time of sending the package and process it on the receiving one:



 long recentProcessedTimestamp; void onStateReceived(State state) { if(state.timestamp <= recentProcessedTimestamp) { //        return; } else if(currentLocalTimestamp()-state.timestamp > MAX_PERMISSIBLE_DELAY) { //   return; } else { recentProcessedTimestamp = state.timestamp; process(state); } } 




The order of the packets / duplication can be controlled by an integer packet identifier (counter), but the time stamp also allows rejecting too old packets. To compare time stamps, of course, you need to synchronize game time on clients. Synchronization from the server is possible, but more accurate results were achieved with iterative time calibration (the server is not involved) on the clients. In the case of different pings from different clients to the installation server, they can be knocked down (if you do not measure and not take into account these pings themselves in calibration), while with the iterative procedure the game time converges to small values ​​(within 10 ms) even with noticeably different ping



Gap Detection and Link Quality Testing




A gap in the sense of the game can be not only a real complete break of communication, but also a rather large delay, it all depends on the genre. The simplest option is a timeout since receiving the last packet from an opponent. Reliable method, it is used as a base.



However, in arcade games, gaps in positions are also important. When the gap in the position of the object in the local state and the remote exceeds the critical size, we consider the connection broken. This method is used with a small amendment - the relationship is considered broken after a series of continuous critical discontinuities, when the series length exceeds the constant. Single breaks occur periodically.



Using TCP / IP




Cirrus was unstable. High and chaotic pings, frequent breaks (in the sense of the game), there was no connection between many players at all. It was also decided to implement synchronization using a single alternative, tcp / ip. Testing has shown that tcp / ip in most cases gives a smaller ping and better maintains its stability (no bursts due to delays) than Cirrus in the DIRECT_CONNECTIONS mode. Let all players who cannot play through Cirrus choose tcp / ip. Suppose that among all the remaining players, the choice between transport is made on the basis of a smaller ping between two clients. Then, according to our statistics, only 20-30% of all players will use cirrus. Ping is measured three times, in the form of double turnover time between customers, after which the maximum value is taken as an estimate. Cirrus, apparently, is not so good for real-time games, as assured by Adobe.



Tcp / ip problems lie in guaranteed delivery. With frequent packet forwarding, even one delayed packet disrupts the entire work. While the problem package is being processed, others are coming. As a result, pings can increase up to several seconds and the connection is broken. However, Cirrus breaks more often.



Main conclusions




Link to the application http://vkontakte.ru/app2316895



Good luck in game development!

Source: https://habr.com/ru/post/128091/



All Articles