I'm working on the networking architecture for our game, but I'm not too experienced with multiplayer programming so I thought I'd ask for a sanity check of my ideas. Any feedback would be appreciated
For now, I'm assuming:
* I want to keep server CPU usage down low
* Both dedicated servers and one-client-as-server are required
* Gameplay is heavily physics simulation based, and slightly non-deterministic
* Direct client-to-client physics collisions are avoided in the gameplay (e.g. client-controlled objects pass through each other instead of colliding)
* Avoiding the ability for cheating is a high priority
* Players will have <200ms pings to the server
* Our gameplay / physics update code is very fast (think ~1ms per frame)
I'm using a server-authoritative model inspired by Quake 3 / Counter-strike, based on unreliable delta-compressed gamestate snapshots.
When connecting, the client synchronizes their wall-clock to the server's, and then sets their game clock to be 100ms ahead of the server's game clock -- i.e. every client will be extrapolating/predicting 100ms into the future.
Each frame on the client, it gathers user input and runs the game logic as usual, producing a predicted game-state. They also bundle up the last quarter-second worth of user inputs + timestamps and send them to the server (unreliable) -- the redundancy (sending old+new input snapshots instead of just the current shanpshot) is to guard against packet loss.
Each frame on the server, it will have a queue of time-stamped user inputs -- hopefully many of them will be in the future (for players with a packet trip time of under 100ms). The server consumes any inputs that are not in the future and applies them this frame. Every N frames (network update rate), the server sends a game-state snapshot to each client (using Q3 delta model).
On the client, if a snapshot has arrived from the server, it stores a small backup the current predicted position/orientation of each physics object and the current game-time value, and then replaces it's game-state with the version from the server. This game state will be around 100 to 200ms in the past though, so after applying it, the client runs it's Update function as many times as required to re-advance time to where it was actually up to (replaying it's own inputs each Update). This produces an updated predicted gamestate, but may also cause all physics objects to snap/jump/teleport to new positions in cases where the previous predictions did not match the server's behavior.
The difference between the new game-state and the backed-up positions/orientations are then subtracted to produce a position/orientation "error" value for each physics object. These errors are stored with each object and added on when computing their visual transforms -- this hides the fact that the objects have snapped/jumped. Over a few frames, the "error" values are lerped towards 0, which smoothly animates objects over to the new positions instead of 'snapping' there.
Cons:
* High client CPU usage, due to needing to perform many rewind-and-repredict physics ticks every time a server game-state packet arrives.
* Misprediction of other client's actions causes 200ms extrapolation errors (movement that suddenly starts/stops might have a ping-sized delay / error smoothing).
Pros:
* No latency on the client's own actions (when pings are <200ms).
* Low server CPU usage - no rewinding of gamestates on the server.
[edit] Here's a bunch of links on the Q3/Counter-strike model that I'm ripping off / converting to use extrapolation instead of interpolation:
http://trac.bookofhook.com/bookofhook/trac.cgi/wiki/Quake3Networking
http://www.ra.is/unlagged/network.html
http://fabiensanglard.net/quake3/network.php
https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
https://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization