Game synchronization scheme

In multiplayer games, multiple clients often use the following scheme to synchronize all player states:

  • State synchronization

    The client sends the game action to the server, the server receives it, calculates the result of the game behavior, and then delivers various states in the game through broadcast, and the client displays the content after receiving the state. This approach is similar to each client remotely operating the software on the server.

  • Frame synchronization

    The client sends the game action to the server, the server broadcasts and forwards all the client action (or the client directly sends through P2P technology), the client according to all the received game action to do the game calculation and display. This is tantamount to clients remotely controlling each other’s game software.


Synchronization logic

The key logic for describing frame synchronization in the simplest model is as follows

The client detects the logical frame of the server -> get the logical frame -> process it -> process the result -> complete the logical frame processing -> render the result to the screen -> End

The client detects user operations -> Package and report to the server -> End

In order to ensure the consistency of the game at all ends, frame synchronization games must be divided into logical layer and performance layer;

  • Logic layer

    The logical layer is used to process and calculate the logical frames in a unified manner. It is necessary to ensure the consistency of the results at each end. Certainty is the biggest characteristic of the logical layer.

    Certainty can be achieved in the following parts:

    • Mathematical determinism

      1. The random number

        When generating random numbers, the seeds should be consistent, and time stamps are not recommended. Try to agree that each end uses the same random number generator to ensure that the generated random numbers are consistent. The random function is a pseudo-random function, for example, the random function is as follows:

        // let randomSeed = 123;
        function random(){
            randomSeed = ( randomSeed * 9301 + 49297 ) % 233280.0;
            return randomSeed / ( 233280.0 ); 
        };
        Copy the code
      2. Fixed-point number

        Js classic accuracy problem is 0.1 + 0.2! == 0.3, in the game logic calculation, due to the inconsistency between the hardware environment and the system platform, floating point number will be calculated differently, and then the error will be amplified in the cumulative calculation process, resulting in the inconsistency in the final presentation. So we need to deal with floating point numbers, using integers or fixed-point numbers to calculate

        Methods for handling floating point numbers are as follows:

        • Look-up table calculation

          In the calculation of trigonometric functions, the method of looking up tables is used to obtain the corresponding values.

          In the case of sin A, the mapping of [0°, 1°… 90°] -> [sin 0°, sin 1°… sin 90°] is established in advance during compilation. The runtime calculates the Angle interval where the target Angle is located and searches the table to obtain the mapping result. Other Angle problems, cosine problems, can also be converted to sine of 0 to 90 degrees.

        • Enlarge truncation

          Multiply by a fixed integer (such as 1000), or shift it n places to the left. The purpose is to convert to integer operations. The benefits are simple, efficient, and intuitive (note: JS shifts cannot exceed 32 bits, such as 1<<32 === 1)

      3. Physics engine

        Most of the game engine with physics engine, or visible on the market most of the physics engine, will not consider the calculation precision, thus a factors could lead to problems with mathematical precision, therefore, as far as possible avoid the use of general physics engine, have the ability can be customized physics engine, or avoid the use of complex physical simulation, Do this by implementing simple physics simulations yourself.

    • Frame rate determinism

      Logical data cannot be updated by the engine’s own update and must be separated from rendering. Frame data can be cached as it is received and then calculated at a certain frame rate. The engine’s update only serves to render the calculated results into the screen.

      The logic layer is usually set to 15 frames per second, which reduces the timer pressure on the presentation layer. Of course, if set to 30 frames per second, the benefit is that the presentation layer can look smooth without interpolation

    • Execution order determinism

      1. Different dictionaries implement different sorting schemes, which makes the order of keys/values likely to be inconsistent. For security, if you must traverse the dictionary, you need to sort keys first, and values need to be retrieved by traversing sorted_keys.
      2. The order of execution of engine life functions may be inconsistent between different clients. For example, the start/onDestroy callback function of COcos may be inconsistent. Therefore, it is necessary to pay attention to the debugging of some life cycle functions during development to determine the call timing
  • The presentation layer

    Presentation of logic layer is more liberal than some, but for the sake of experience on optimization, such as the calculation is mapped to the presentation layer, logic layer may result due to accumulated frame too much rendering of flashing, therefore, need to consider doing some smooth frame processing, also can do some simple prediction, let the user operation to compare real-time mapping in the picture

To sum up, the architectural design of synchronous logic can usually be implemented in the following two forms:

  1. Command architecture, suitable for most games

    • Completely separate logic layer and render layer code
    • The logic layer interacts with the render layer code through events
  2. Mapping architecture, suitable for physics simulation games

    • Each render layer object has a logical object that it mounts to
    • The render layer object retrieves and updates data from the logical object in each update function

Rendering optimization

Since the frame rate of the logical layer is different from that of the rendering layer, and the delay problem is taken into account, it will cause flashing sensation if the logical calculation results are directly rendered to the screen during each rendering, so some optimization of the rendering layer is needed, including the following schemes:

  • Cache frame

    When the client detects a new logical frame, it does not process it immediately. Instead, it first caches the frame in a queue and then pushes the queue stack out of the queue until a certain number of frames are satisfied

  • Smooth frame

    Smoothing frame is based on the cache frame, the above push out operation is timed operation, so as to resist the network fluctuation caused by the inconsistency of the logical frame arrival time interval problem

  • To predict

    In the operation of the other players before input data has not yet arrived or processing, in order to enable the game to smooth, the client can not need server frame return confirmation executes instructions, but the player input, as soon as another player’s input is according to a recent input or other better plan to forecast, after receiving the server to the latest data, If the prediction is wrong, it rolls back to the last correct frame confirmed by the server, and then continues to chase the client frame

  • The interpolation

    If interpolation is not carried out, the movement of the object will be stuck because of the unsynchronization of the frame rate. The distance to be moved by a logical frame can be decomposed into several render frames to complete. Taking Cocos as an example, the object can be moved from the current position to the logical position by cc.tween

Even after the frame

Games may be due to various reasons will encounter in the process of bolt, there is no state considering the frame synchronization, in order to let the player back to the current session, can request frame, from the first frame to the current frame of data between the latest get executed after again and again during the update server is issued by the logical frame of cache, until after I will update when the frame, So as to achieve the effect of disconnection reconnection reset;

However, there is a fatal problem with this approach. If the player is disconnected for a long time, or the game is played for too long, too many frames are retrieved, or local processing takes too long, causing the player to be stuck for a long time and the experience to be terrible;

The simple and crude solution to the first point is to treat players who have been disconnected for too long as quitting the game. If not, allow a certain amount of deviation and use timed synchronization to achieve this.

And the second point, is also to do this, by using the method of timing synchronization of the game state can timing will be the main logic of game data and current biggest frame to another service, next time reconnection, obtain the data at the end of the cache, the game is reset to the cache data, request frame to the latest frame of cache, the data between the logic calculation, This reduces the amount of requested data and computation stress