Reputation:
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.
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.
Upvotes: 2
Views: 1664
Reputation: 2986
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.
Upvotes: 1