I'm trying to implement FPS style game networking, and have a lot of it understood and have implemented them. But there are parts that all the material I can read has kind of glossed over. The only meaningful things I can find are actually forum posts on here (mostly by hplus0603 and fholm).
So I guess to start I will explain how my system currently works:
- Simulation is 60Hz on both server and client
- Client sends input commands every other frame
Once per tick, the server goes through all pending commands and applys them to the game world. This means a user can apply more than one user command per server tick. From reading Valve's articles it seems they do it this way, but I'm not 100% sure.
After applying on the user commands, the server reads the physics of the world and stores it into a world game state object that includes all the entities and sends this to the users with the server tick.
I imagine you can already see the problems in this system:
- When doing client server reconciliation, the ticks and positions don't really match up 100%. Maybe this is okay though?
- Lag compensation definitely won't work.. This is because the users position inside the gamestate will actually jitter around. Also the client tick and server tick don't match up.
- Interpolation probably won't work either..
So the first way I thought to fix it is to always have the server tick only execute 1 user command per tick. But I can't just tick 1 user command at a time, since user commands would end up missing. Especially since I send 2 at a time. Also how do I match up the user command tick to the server tick it was actually applied on? One issue I had with this was that the buffer was inexplicably filling up with like 20 user commands, and never draining them. So the player would end up very far behind (20 * 16ms) = 320ms. Also the receiving first game state is confusing to me.
Client:
- Client connects and receives first game state
- Sets its own local tick to the server tick it received in the game state. Let's say tick 1300.
- Client sends it's first user command with it's local tick (which will be the server tick it just received, we'll also just assume 1 user command per packet). The result of that user command was a position of 1,0.
Server:
- Client connects, add player to game state
- Send game state to everyone with server tick (1300)
- At this point a couple ticks will have passed (let's just say 5) before we've even received the first user command. That client's buffer is empty. Let's assume we don't execute anything when the user command buffer is empty.
- We received the previously connected users first command stamped with tick 1300. But the server is already on tick 1305. So it applys the user command on tick 1306 and send's it to the client with the stamp 1306.
Client
- Client has sent a few user commands by now without a game state.
- Receives second game state 1301 on the server.
- Checks his own history and the result of 1301 was 2,0. But the server says he was at 0,0 since it hasn't even applied any user commands.