Questions about client-side-prediction
Hello together, OK, I have looked into client-side-prediction and this is how I would implement it: -The clients send on regular time-intervalls a state-message to the server containing the state of the unit controlled by them and attach a time-stamp with the current time. -Whenever the server gets a state-message it sets the state of the clinet-controlled unit to the send state (after cecking the correctness of course) and replies with a complete game state to which it appends the time-stamp send by the client. -Whenever the client gets a complete game state from the server, it calculates its age with the send time stamp (age=current_time-time_stamp), sets the game state to the sended state and forward predicts by the known age. So far so good. But a complete game state means a big message. Let's say there are 40 Players. Each player has: - 3 floats for a position - 3 floats for a velocity - 3 floats for a looking direction - about 4 byte for other needed information Makes a total of 40 bytes per Player => 1600 bytes per game state. Thats a lot and bigger than a regular MTU. So, this makes it very likely that the UDP message is lost. An alternative would be to put the complete game state into little mini states (states of single entities), but I would not know how to do the prediction with those mini-states. So my questions: Do you think my way of implementing this is OK? Would you do also "server-side-prediction"? What do you think about the big package problem? Thanks! Nathan
Reading your old posts, i'm supposing you're using UDP.
In you case, velocity could be deductible from current position and oldest position. 3 floats are saved this way. yes, it's a bit more inaccurate, but it would do the job.
Again, if you really want to use floats, ask yourself if float are the right type to do the job. In a FPS, the guy could move forward, backward, left and right. The server should know the speed of the guy (MaxSpeed, constant if all players are the same, a variable if the speed of the guy is different for each guy. This variable should be sent one time - start of the game, maybe? -). So we got 4 movement, and we know that the guy can't move left AND right, nor forward AND backward. So well have: 1 byte for the heading (0 to 255 should be enough for an angle) and 3 bit for the relative movement
0000: no move,
0001: forward
0010: backward
0011: left
0100: right
0101: forward left
0110: forward right
0111: backward left
1000: backward right
You'll get 4 other bits for other informations: firing, jumping, crouching and so on.
so you got the looking direction contained. In a FPS, you'll want the second axis (look up or down). Another byte (using the same technique as above). Again, a float has been saved.
Unless you're on a space shooter, you may forgot the nutation. Another float saved.
So you'll end up with 3 float, 2 byte, and 4 bits. You could save more by using double.
Oh and forget sending the entire game state. Only Quake network code used this (called a snapshot, and this network code is from 96). Send only the game update state. And send them only to people that need this information. I'll reuse my FPS example. If you don't see any player cause he is at the other end of the map, why should you get some information on his position? This isn't needed at all! Send him the updated position only when the enemy get in Line Of Sight.
This was a short example. The paquet could have been optimised differently if it was another gameplay type.
My thought about the big paquet: If you're above 512 byte in a UDP protocol, there's something wrong about your protocol :).
I hope all this information helps.
In you case, velocity could be deductible from current position and oldest position. 3 floats are saved this way. yes, it's a bit more inaccurate, but it would do the job.
Again, if you really want to use floats, ask yourself if float are the right type to do the job. In a FPS, the guy could move forward, backward, left and right. The server should know the speed of the guy (MaxSpeed, constant if all players are the same, a variable if the speed of the guy is different for each guy. This variable should be sent one time - start of the game, maybe? -). So we got 4 movement, and we know that the guy can't move left AND right, nor forward AND backward. So well have: 1 byte for the heading (0 to 255 should be enough for an angle) and 3 bit for the relative movement
0000: no move,
0001: forward
0010: backward
0011: left
0100: right
0101: forward left
0110: forward right
0111: backward left
1000: backward right
You'll get 4 other bits for other informations: firing, jumping, crouching and so on.
so you got the looking direction contained. In a FPS, you'll want the second axis (look up or down). Another byte (using the same technique as above). Again, a float has been saved.
Unless you're on a space shooter, you may forgot the nutation. Another float saved.
So you'll end up with 3 float, 2 byte, and 4 bits. You could save more by using double.
Oh and forget sending the entire game state. Only Quake network code used this (called a snapshot, and this network code is from 96). Send only the game update state. And send them only to people that need this information. I'll reuse my FPS example. If you don't see any player cause he is at the other end of the map, why should you get some information on his position? This isn't needed at all! Send him the updated position only when the enemy get in Line Of Sight.
This was a short example. The paquet could have been optimised differently if it was another gameplay type.
My thought about the big paquet: If you're above 512 byte in a UDP protocol, there's something wrong about your protocol :).
I hope all this information helps.
Thanks for your answer PERECil! Yes, you are right, I am using UDP!
So you are saying, sending the complete game state is ok, but I should be able to pack the game state (in many ways you describe) so that it is not to big anymore?
Some questions:
"You could save more by using double." - That I do not understand, how could I save by using double????
So with this problem solved, I restate my remaining questions:
Do you think my way of implementing this is OK? -> Since PERECil did not complain, I guess he think it's fine :)
Would you do also "server-side-prediction"?
And thanks in andvanve!
Nathan
So you are saying, sending the complete game state is ok, but I should be able to pack the game state (in many ways you describe) so that it is not to big anymore?
Some questions:
"You could save more by using double." - That I do not understand, how could I save by using double????
So with this problem solved, I restate my remaining questions:
Do you think my way of implementing this is OK? -> Since PERECil did not complain, I guess he think it's fine :)
Would you do also "server-side-prediction"?
And thanks in andvanve!
Nathan
Err forget about the double part... I forget always that double is double the size of the float :P.
Oh and, if you don't use the timestamp for calculations, I would tell you to use an incremental int for packet identification, cause you could have 2 packets sent with the same timestamp.
In my network application, I add 1 each time a new network packet is created. This is better, it is a truly unique identifier.
And to answer your questions, yes you should be able to pack your data in a more efficient way, but also send only the needed data to your client. With my basic optimisation, the base packet is reduced to 15 bytes + timestamp (using an Int16, 2 bytes, thats 17 bytes). For 40 players, 680 bytes. With the IP+UDP header, 708 bytes. You really should go under 512 byte (my maximum safe UDP packet size value) if you don't send unecessary data (other clients too far) to the client.
You've 484 bytes available. Divided by 17, you could send a max of 28 clients positions in this packet. The server could select the 28 nearest clients to send to a client.
And voila, job done :).
About the client side prediction, I didn't code any by myself, but I think packet to check prediction is a bad thing. You'll better end up by trying to predict with the current state and the past state the next action of the user. I'm not sure about it, but I think Quake worked like this.
I wouldn't do server side prediction... prediction is actually done for a smoother gameplay (prediction works well against packet loss, resulting the player is no more teleporting from a point to another, when a packet has been lost) and for visual comfort. The server doesn't need this stuff (unless it is a listen server, and even in such case, i would run a separate thread for the server and handling the local client like another client).
Oh and, if you don't use the timestamp for calculations, I would tell you to use an incremental int for packet identification, cause you could have 2 packets sent with the same timestamp.
In my network application, I add 1 each time a new network packet is created. This is better, it is a truly unique identifier.
And to answer your questions, yes you should be able to pack your data in a more efficient way, but also send only the needed data to your client. With my basic optimisation, the base packet is reduced to 15 bytes + timestamp (using an Int16, 2 bytes, thats 17 bytes). For 40 players, 680 bytes. With the IP+UDP header, 708 bytes. You really should go under 512 byte (my maximum safe UDP packet size value) if you don't send unecessary data (other clients too far) to the client.
You've 484 bytes available. Divided by 17, you could send a max of 28 clients positions in this packet. The server could select the 28 nearest clients to send to a client.
And voila, job done :).
About the client side prediction, I didn't code any by myself, but I think packet to check prediction is a bad thing. You'll better end up by trying to predict with the current state and the past state the next action of the user. I'm not sure about it, but I think Quake worked like this.
I wouldn't do server side prediction... prediction is actually done for a smoother gameplay (prediction works well against packet loss, resulting the player is no more teleporting from a point to another, when a packet has been lost) and for visual comfort. The server doesn't need this stuff (unless it is a listen server, and even in such case, i would run a separate thread for the server and handling the local client like another client).
First of all, you can easily quantize your look direction (and possibly velocity) to 32-bits each total (3x10 bits, possibly with a 2-bit shared exponent for velocity). You can probably quantize position to 3x16 bits as well; or at least 2x24 (for ground coordinates) and 1x16 (for elevation).
Second, you probably don't want to send ALL of the state as a single packet. Settle on a single largest packet size, and send whatever data most need sending in each packet. Trickle the game state out to the client over a few packets, and things will probably work much better. You of course need to send delta update packets interleaved with the full-state data.
Last, there are several reasons that most networked games top out at 16 or 32 players -- this is one of the reasons. It's just a lot of data, and compression and/or dealing with loss and/or lag is hard. Sending deltas and occasional baselines is usually one trick, but it's sometimes not enough.
Also, consider this: if you have 40 players, each of whom needs 1600 bytes of data, that's 64,000 bytes of data, or a good half-second of transmission on a T1 line. There's more than one reason to limit your send rate; one of them is network jitter/lag introduced by too much traffic on the line for other players.
Second, you probably don't want to send ALL of the state as a single packet. Settle on a single largest packet size, and send whatever data most need sending in each packet. Trickle the game state out to the client over a few packets, and things will probably work much better. You of course need to send delta update packets interleaved with the full-state data.
Last, there are several reasons that most networked games top out at 16 or 32 players -- this is one of the reasons. It's just a lot of data, and compression and/or dealing with loss and/or lag is hard. Sending deltas and occasional baselines is usually one trick, but it's sometimes not enough.
Also, consider this: if you have 40 players, each of whom needs 1600 bytes of data, that's 64,000 bytes of data, or a good half-second of transmission on a T1 line. There's more than one reason to limit your send rate; one of them is network jitter/lag introduced by too much traffic on the line for other players.
enum Bool { True, False, FileNotFound };
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement
Recommended Tutorials
Advertisement