Linear impulse and incorrect positions in multiplayer

162 views Asked by At

I'm creating a game in LibGDX. I was initially following a tutorial for a super Mario brother game from youtube, however, I have changed the game to include my own characters and graphics. I am using Kryonet as a means of including multiplayer functionality in my game. This is where I have run into several problems.

Firstly I am using linear Impulse for movement, so when my character hits a powerup, there is a linear impulse applied and they are given strength to move forward faster. This works fine until you look at both screens, the position of character A is different on one player's screen and a different position on another's. The values I'm getting for linear impulse for both characters are also different. My normal right, left, up movements are also done with linear impulse. Due to the randomness of linear impulse values I've been getting this error that I'd say is like a sliding ice error, pushing an ice cube across a surface does not always guarantee the same position. Below is some code from my multiplayer client class.

client.addListener(new ThreadedListener(new Listener() {
            // What to do with the packets.
            public void connected(Connection connection) {

            }

            public void received(Connection connection, Object object) {

                if (object instanceof MovementJump) {
                    MovementJump packet = (MovementJump) object;
                    
                    PlayScreen.player.b2body.applyLinearImpulse(new Vector2(0, 4f),
                            PlayScreen.player.b2body.getWorldCenter(), true);
                }

                if (object instanceof MovementRight) {
                    MovementRight packet = (MovementRight) object;
                    
                    PlayScreen.player.b2body.applyLinearImpulse(new Vector2(packet.impulse, 0),
                            PlayScreen.player.b2body.getWorldCenter(), true);
                }

                if (object instanceof MovementLeft) {
                    MovementLeft packet = (MovementLeft) object;
                    
                    PlayScreen.player.b2body.applyLinearImpulse(new Vector2(-packet.impulse, 0),
                            PlayScreen.player.b2body.getWorldCenter(), true);
                }

                if (object instanceof MovementP2Jump) {
                    MovementP2Jump packet = (MovementP2Jump) object;

                    PlayScreen.player2.b2body.applyLinearImpulse(new Vector2(0, 4f),
                            PlayScreen.player2.b2body.getWorldCenter(), true);
                }

                if (object instanceof MovementP2Right) {
                    MovementP2Right packet = (MovementP2Right) object;
                    
                    PlayScreen.player2.b2body.applyLinearImpulse(new Vector2(packet.impulse, 0),
                            PlayScreen.player2.b2body.getWorldCenter(), true);
                }

                if (object instanceof MovementP2Left) {
                    MovementP2Left packet = (MovementP2Left) object;
                    
                    PlayScreen.player2.b2body.applyLinearImpulse(new Vector2(-packet.impulse, 0),
                            PlayScreen.player2.b2body.getWorldCenter(), true);
                }
            }

        }));
    }


This is some code from my playScreen

            if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)
                        && player.b2body.getLinearVelocity().x <= player.getMinSpeed()) {
                    
                    
                    MovementRight pos = new MovementRight();
                    MPClient.client.sendTCP(pos);
                }

                if (Gdx.input.isKeyPressed(Input.Keys.LEFT)
                        && player.b2body.getLinearVelocity().x >= -player.getMinSpeed()) {
                    
                    MovementLeft pos = new MovementLeft();
                    MPClient.client.sendTCP(pos);
                }

1

There are 1 answers

0
AudioBubble On BEST ANSWER

Instead of sending forces you should be sending coordinates of each character.

Why? If you apply a pulse and send a packet notifying the other clients of this pulse, their pulse will be added first after the ping delay, which is never going to be 0. This means their pulse will be added afterwards and their characters will therefore always be behind.

I can think of two structures of doing this, but there is likely many other ways! The first is client-sided and second is server-sided, both have pros and cons. Personally I recommend the server-sided structure because hacked MP games really throws off players.

Client-sided Structure:

Pros (Less Lag, More responsive controls for laggy players) Cons: (Easy to hack)

  1. Whenever your client's character's position is changed, send a PositionUpdatePacket to server with your current coordinates. This packet only needs to contain (float x, float y, byte characterID)
  2. Server sends the packet to all other connected clients.
  3. Each client updates the character based on the character ID.

Server-sided Structure:

Pros (Difficult to hack) Cons: (Less responsive control for laggy players)

  1. When a player makes an input (moves left for instance) then you send a MoveRequestPacket with the desired direction to the server. This only needs to be sent once through TCP whenever the direction changes or if player stopped.
  2. Server updates this player and moves in desired direction, ie the update method behavior. This would apply the pulses. Advantage here is server can do various hitbox checks and prevent movement if player wants to walk through a wall etc, to prevent hacks.
  3. Server continuously sends a PositionUpdatePacket to ALL connected clients, including the one sending the request, with the current character position, as long as the character moves.
  4. Each client updates the character.

In both structures PositionUpdatePacket must be sent to all connected clients continuously as long as the character moves, which means this packet will be sent a lot. To reduce the performance impact you should send it through UDP and only x times per second. Minecraft sends its position at a rate of 20t/s.

If you send PositionUpdatePacket at a lower frequency than your #update or #step methods then each character will stutter and lag. To combat this you can use interpolation which smoothen out the movement.

I hope this helped. Good luck!