You need interpolation or simulation of entities. And you need synchronized timers.
The server runs the timer, clients copy the timer. Server sends a timer update to all clients from time to time to keep them close to sync. Best would be to take into account the latency. Have the server send a package with his current timer time to the client and the client sends it back. The server can read the time value and compare to his current time and calculate a clients latency. Take that latency into account when syncing timers. When the server sends a sync which takes 100 ms to travel to the client, you must add 100 ms to the timer value to really be in sync with server time.
Now you can operate on each client based on server time. Each client runs a simulation of what happens on the server. And clients fill gaps of time.
A server would send a message “unit U is in position x,y with direction d and speed v at time t”. The client can then
- save this position as reference position
- every frame calculate a current position based on direction and speed and time elapsed since recieving the reference position
Easy example, an entity is at position 10, 10 at a server time of 1000ms, moving right (east) at 10 per second, making it 1 per 100 ms.
The client recieves the information at a time of 1200ms, he saves the reference 10,10 at 1000ms, heading right with 10 per second.
Elapsed time is 200ms, so the position offset is 2. The client draws the entity at 12,10.
He does so until he recieves a new reference information.
Ideally, the new reference information is perfectly in sync. But it is possible, that the information differes. Perhaps the unit did stop or change direction. So the current display position and the real server side position differ. Dont matter, discard the current position and use the new reference.
Consequence is, that the unit “jumps” because the client simulates it too far into a wrong direction, because he didnt know that the unit stopped or changed direction. When he gets the new information, he just corrects things by jumping / warping the units to their correct position.
In fact this doesnt matter. The differences in position are usually minimal, a few pixels. And there is the chance that the player doesnt even see affected units, because his screen position is somewhere else.
The same way damage can apply “in the past”. Client 1 sends an attack against a unit of Client 2. It takes 200 ms to the server, he calculates damage and sends it to Client 2. It travels another 200 ms. As effect, the damage arrives 400 ms later than it actually happens.
The great trick is to keep things in sync there. Imagine the unit of Client 2 dies by the damage. But the information travels 400 ms, and in the meantime, the unit does an attack and damages another unit. In fact, this would be impossible, because the unit is already dead, but Client2 doesnt know it yet. So when the server recieves the message that the unit wants do deal damage, the server needs to know, that the unit already died and thus ignore any damage done later.
The message of the article I read, which I can only confirm, is, that when you need a controlled communication, use TCP, do not use UDP and implement an own layer of transport control.
Network playing is no easy topic. You should look for some articles about this. If you have only local networks and few entites, you can allow some errors and asyncs. If you plan to use many entites like in an RTS and if you have high latency connections, you need strong synchronization algorithms and a strong server to perform the extra computation or a dedicated server.
Tell us about your game. If you use shots as objects which travel, send position and speed over UDP. If you use shots as instant effect actions, and insist on using UDP, give each shot a unique ID and let a client confirm if he got the information. While you have no confirmation, continue to send this shot to all clients that didnt confirm yet.
-JAW