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);
}
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)
PositionUpdatePacket
to server with your current coordinates. This packet only needs to contain (float x, float y, byte characterID
)Server-sided Structure:
Pros (Difficult to hack) Cons: (Less responsive control for laggy players)
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.PositionUpdatePacket
to ALL connected clients, including the one sending the request, with the current character position, as long as the character moves.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!