1
\$\begingroup\$

I'm a newbie Java coder and I'm creating a 1v1 game with an authoritative server and two clients.

My whole network is based on UDP packets:

  • sending positions from clients
  • sending confirmations them from the server (and deleting old ones on client)
  • sending 'non cheated' positions to the opponent from the server

Everything was okay, until the last issue appeared.

When I send positions from the server to the opponent, they are being received in different timings. An example:

  • Position 1 comes in 43 ms
  • Position 2 comes in 15 ms (from the previous one)
  • Position 3 comes in 34 ms
  • Position 4 comes in 10 ms
  • Position 5 comes in 35 ms

As we all know - THIS IS NETWORKING, it is impossible to make it 'stable' that every packet comes within a constant: 30 ms time (omg that would be perfect).

And here comes my problem, I don't know how to adjust the interpolation that the 'opponent' is drawn as smooth as a normal player (me on my phone).

When the process is static (I click arrows and move my player 'step by step') it is really, really smooth. My gameloop has some timings and it works really well. But when I receive my opponent's position and set his 'desiredPosition(x,y)' his movement is not smooth, his 'positions' are not 'updating' in a constant time but with random delays (cause we get these positions in different timings).

And example of my issue:

The gameloop:

@Override
    public void run(){

        long beginTime;     // the time when the cycle begun
        long timeDiff;      // the time it took for the cycle to execute
        int sleepTime;      // ms to sleep (<0 if we're behind)
        int framesSkipped;  // number of frames being skipped 


        while(match_running){
            beginTime = System.currentTimeMillis();
            framesSkipped = 0;  // resetting the frames skipped
            // update game state 

            // MOST IMPORTANT METHODS - look below
            player_me.updatePosition(); // move me (smoothly) to desired position
            player_op.updatePosition(); // move the opponent (smoothly)

            // render state to the screen
            // draws the canvas on the panel
            repaint();  

            // calculate how long did the cycle take
            timeDiff = System.currentTimeMillis() - beginTime;
            // calculate sleep time
            sleepTime = (int)(FRAME_PERIOD - timeDiff);

            if (sleepTime > 0) {
                // if sleepTime > 0 we're OK
                try {
                    // send the thread to sleep for a short period
                    // very useful for battery saving
                    Thread.sleep(sleepTime);    
                } catch (InterruptedException e) {}
            }


            while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
                // we need to catch up
                // update without rendering
                player_me.updatePosition(); // move me to desired position
                player_op.updatePosition(); // move the opponent

                // add frame period to check if in next frame
                sleepTime += FRAME_PERIOD;  
                framesSkipped++;
            }


            // sending positions every 30 seconds
            time_now = System.currentTimeMillis();
            if(time_now >= (packet_past_time + 30)) {
                sendPositions();
                packet_past_time = System.currentTimeMillis();
            }
        }
    }

Listening for 'opponent's positions':

public void received(Object o){
    if(object instanceof OpponentPositionList)
        player_op.setDesiredPosition(object.x, object.y);  
}

So we set his new 'desired position'. The Gameloop is always calling his 'updatePosition()' which should move him to the 'desired position'. And it works totally smoothly with 'Player Me' cause it updates positions constantly, but it freezes when receiving positions 'not constantly' from the opponent:

public void updatePosition(){
     double xDistance = desiredX - this.x;
     double yDistance = desiredY - this.y;

     double distance = Math.sqrt(xDistance * xDistance + yDistance * yDistance);

       if (distance > 1) {
           this.x += xDistance * 0.5;
           this.y += yDistance * 0.5;
       }else{
          this.x = Math.round(this.desiredX);
          this.y = Math.round(this.desiredY);
       }
}

The main question is:

If positions come not 'constantly', with random 10-50 ms delays how to draw the opponent as smooth as a normal player on this device? With some sort of 'timed interpolation' or something? Any suggestions would be awesome. Thanks!

\$\endgroup\$
1
\$\begingroup\$

Your question boils down to an answer that results in client prediction for replicated movement of remote entities in your game. There are several theories and implementations that you can go with for this. Linear interpolation is a popular one for having the client predict where the server is going to tell it where various objects are. Here is a nice list with some sources about this topic.

Movement Queues

Here is a good example/discussion that talks about different approaches and their success / failures. It is valid because it is not specific to any language and can be applied to any game engine. It has an approach where you save (client side) the client side movements. Then, as a packet, you send them to the server. The server sends this client movement packet to all interested parties (other clients). This is neat because if you have a huge delay, rather than teleporting the character, you simply replay those saved packets that the client received of the remote entity.

Curves

Then there is the approach that was seen in Planetary Annihilation with the use of Curves. (P.S. The link has a lot of links in the article discussion this very topic). What that is essentially the client tells the server where it was at a given time. From there, the server tells all clients where that entity is at a given time using a list of times and positions. From there the client can smooth the movement using the time and position pairs. What is really great, and they mention this too, is that you get free game replay data for free.

Tick Rate Entity Interpolation

A third method is one found in the Source engine Multiplayer Networking guide. This example is pretty straight forward. It involves some interpolation math and using tick rates. May be the simplest to implement.

\$\endgroup\$
0
\$\begingroup\$

Linear Interpolation

Want to ensure that at time T, player B will appear at location L for player A?

Make player A speed up the movement of player B so that they get there in time.

E.g. The movement should last 5 seconds in total. The packet takes 1 second, so player A renders player B as moving at 1.25x of his usual speed.

No Interpolation

Packets typically take well under 250ms to reach their destinations, so you could just preserve player B's rendered speed and have player A see the action completed 250ms later.

They will not notice this unless your game is extremely quickly paced and the network is especially laggy at that time.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.