I'm implementing a multiplayer asteroids clone to learn about client/server network architecture in games. I have spent time reading GafferOnGames and Valve's publications on their client/server tech. I am having trouble with two concepts.
Currently I have an authoritative game server simulating physics with box2d and sending out the state of the world to clients about 20 times per second. Each client keeps track of the last few snapshots it received and lerps between two states to smooth out the movement of sprites. However it's not that smooth. It can be smooth for a while, then jerky a bit, then back to smooth, etc. I have tried both TCP and UDP, both are about the same. Any idea what my problem might be? (Note: I implemented this for single player first, and the sprite movement is perfectly smooth at 60fps when updating the physics world only 20 times per second).
In order to solve the first problem I thought maybe the client should run a box2d simulation as well and just update the positions of its sprites to match the server snapshots when they don't match. I thought this might be smoother since my single player implementation is smooth. Is this a good idea?
Even if it won't fix the above problem, is it necessary for client-side prediction? For example, if a player attempts to move their ship, how will they know if they hit an asteroid, wall, or enemy ship without a physics simulation? It seems like their ship would appear to pass through the object it should collide with before they receive a snapshot from the server that says they hit the object.
Thanks!
Answer
Definitely run the simulation on both the clients and the server. Anything else have too long latency. You should be sure that the simulations match by inserting objects in the same order, using a fixed time step and avoiding pointer comparison. I haven't tried this with Box2D but it is generally possible to achieve the same behavior on all machines in a physics simulation. All math is usually based on IEEE 754 binary32 floats and their behavior is strictly defined for operations such as +-*/
to name a few. You need to be careful about sin
, cos
and the likes tough, since they can differ between runtimes (this is especially important when developing for multiple platforms). Also make sure that you use a strict setting for float optimizations in your compiler. You can still synchronize objects by periodically sending out the state of objects from the server. Don't update unless the difference is larger than a threshold to avoid unnecessary stuttering.
One issue that come to mind is the creation of new objects and how that will change the simulation between clients. One way to fix this is letting the server create all objects. If the current time step is t
, the server will schedule a object to be added at t+d
. Thus, a new-object list, with objects to add and when to add them, can be can be maintained at all the clients and updated by the server well in advance. If d
is large enough, you minimize the risk of different results. If you really can't handle difference you can force a client to wait for information about new objects for a certain time step before simulating that time step.
No comments:
Post a Comment