2

I would like to create a simple multiplayer game with Node.js and socket.io. My current timing setup is not optimal since the rendering is not smooth. So far, the players are just circles moving around on a canvas.

I try to explain this setup roughly by adding some code snippets, please let me know when more code is necessary.

Each time a client connects to the server, a new player is created and added to a player list. Also, we add listeners for key inputs by the clients (which basically change the direction).

io.on("connection", (socket) => {
    const p = playerList.add(socket.id);
    socket.on("keydown", (key) => p.addControlKeyDown(key));
    socket.on("keyup", (key) => p.addControlKeyUp(key));
}

Also server-side, we have an update loop. Since there is no requestAnimationFrame in Node.js, I tried to use setInterval. After each update, we send what is relevant for drawing (this is what p.extract() is for) to all clients.

function updateLoop() {
    for (p of playerList.players) {
        p.update();
    }
    const currentPlayers = playerList.players.map((p) => p.extract());
    io.emit("playerUpdate", currentPlayers);
}

const interval = setInterval(updateLoop, 30);

I have already played around with the 30 milliseconds here, but it doesn't solve the issue.

On the client, we listen for the updates:

let players = [];

socket.on("playerUpdate", (currentPlayers) => {
    players = currentPlayers;
});

On the client we also have the draw loop using requestAnimationFrame:

function drawLoop() {
    clearCanvas();
    players.forEach(drawPlayer);
    requestAnimationFrame(drawLoop);
}

The communication between the server and the client works, but as you can see, the timing is not really optimal.

example animation

How can this be improved?

My first idea was to avoid a client-side loop, since we already have on on the server, and directly send draw request to the clients from there, but this turns out to be even worse.

I am aware that for complex multiplayer games one has to put much more effort into synchronization, and that one also takes snapshots of the game world and actually renders the "past" to the clients. I hope that such complex methods are not necessary for this simple example.

  • It seems like you can render an update for the frame only when there is a response back from the server, right? If so, this is definitely a wrong idea. Your FPS will be totally depends on the network quality, which is not great most of the time, so you will be getting "freezes". – Eugene Obrezkov May 25 '20 at 11:43
  • I am not sure what you mean. The draw loop even works when no new update has been sent, since there is the global "players" variable in the client. But you are right that this might lead to freezing. This could be solved by linear interpolation and "looking ahead". But this issue is not what can be seen in the gif, right? Edit: Ok, maybe it's exactly this! I was thinking about longer freezes. –  May 25 '20 at 11:47
  • 1
    IMHO, you should take a look in this direction to resolve the issue - https://gamedev.stackexchange.com/questions/5053/interpolating-positions-in-a-multiplayer-game – Eugene Obrezkov May 25 '20 at 11:56
  • Thanks. There are multiple answers. Do you mean the post by Martin linked there? https://stackoverflow.com/questions/3276821/dealing-with-lag-in-xna-lidgren/3276994#3276994 ("What you should do in a real game is interpolate between the local position and the remote position."). Sounds interesting! –  May 25 '20 at 13:10
  • No, I meant another post, but it was just an example of what you should looking for. Seems like you found answers yourself then?) – Eugene Obrezkov May 25 '20 at 13:16
  • Alright, which post did you mean? I have implemented Martin's idea and it looks really nice already. It's amazingly smooth. However, this algorithm also adds friction to the movement, which I do not necessarily want to have. Video: https://drive.google.com/open?id=1PNj0GO7we5jg9C9qF9AUtoDBknw0r3aF (smoothness can perhaps only be seen in the downloaded version). –  May 25 '20 at 14:32
  • 1
    Your question seems like formatted well, so I made an upvote back. Seems like position interpolation helped, so I'll write an answer then? – Eugene Obrezkov May 26 '20 at 11:12

1 Answers1

1

First issue is that your rendering on the client is coupled with the response from the server. In other words, highly depends on QoS of your network, which is not great most of the time. You will be getting those "freezes".

You can get rid of that by introducing interpolation to your player's position (you were right in the comments).

Roughly said, you need to "take a guess" of where the other player will go until the response make it to the client.

There is a great explanation here

From the point of view of a client, this approach works as smoothly as before – client-side prediction works independently of the update delay, so it clearly also works under predictable, if relatively infrequent, state updates. However, since the game state is broadcast at a low frequency (continuing with the example, every 100ms), the client has very sparse information about the other entities that may be moving throughout the world.

So I'd recommend to you read through articles like the above. I gladly add few references we did in comments as well:

It is such a huge topic with already answered questions, so better to give you a direction of what you need to look for, instead explaining it again. I hope it helps you.

Eugene Obrezkov
  • 2,910
  • 2
  • 17
  • 34