Proper multiplayer communication

Started by
6 comments, last by Waterlimon 10 years ago

Hello!

I am searching for some suggestions how to improve the multiplayer communication in my game. Currently, it is done in the following way:

- I create a server game and other players can connect to it using the ip and the port

- They send information about their player and the server receives it

- Then the server sends the information to every other player

The commands are of the following way:

update player Radev 0.0 0.0 100 100 5000 5000 0.0

rotate player Radev -0.4332702160639106

shoot player Radev 0.0001 0.77878

Do you think I should try to encode it in some way or just make the commands shorter like:

upRadev 0.0 0.0 100 100 5000 5000 0.0

rpRadev -0.4332702160639106

spRadev 0.0001 0.77878

However this does not make the commands a lot shorter in any case.

Any ideas?

Advertisement

I use Java for my game.

Generally you would transfer data over the network in binary form. So youd have a byte or two for the size of the packet, then a byte or so for the type, and then the data which is interpreted based on the type given.

For player positions and such it is often possible to send the delta values (eg change since last state) instead of the full data, and only once in a while send the full state (in case theres an error in the delta-values or some were missed etc.)

However, i suggest not to try to compress the data further since its probably unnecessary in your case and is likely to introduce a pile of new bugs.

Just send your data in binary form instead of human readable text. You can find a metric ton of tutorials and articles and forum posts with lots of advice with some googling.

o3o

I made the strings shorter by making the commands to be only by one character and accordingly the numbers to lose their precision.

Now, the commands are of the type:

rp yuyuyu -0.07 where yuyuyu is the name of the player

Before that the command was:

rotate player yuyuyu -0.4332702160639106

Since there aren't a lot of commands I think, maybe I can combine rp and -0.07 in a bit representation to save space.

I think I could help you better if you explain a bit what are you doing and how are you/do you want to do it. Answering those questions would be useful:

  1. What kind of game are you working on and what amount of data do you need to send?
  2. Is your game client also in Java?
  3. What classes/libraries are you using for network communication?
  4. How do you transmit the information through network? Are you using a library that allows you to send it as String, do you call String.getBytes and send it as a byte array...?
  5. Is there any reason why aren't you using Serializable objects and alike?

If you are really sending the string "rp SomeAwesomeNick -0.07", well... the string "SomeAwesomeNick" is 13 bytes length. And the string "-0.07" is 5 bytes length. Using an int type for the player (relating each player name to an integer id) and a float type for the angle would save you 9 bytes for the player and one byte for the angle. And you could even use shorter types. If I wanted to serialize it manually, I could build a byte array with the first byte identifying the command, the next four bytes for the integer id and the last four bytes for the float angle. In nine bytes I'd had the command, the player and the angle with a lot more precision than the string way. Hey, if I'm sure I'll never have more than 256 players and 256 directions are enough for me, I even could do it in three bytes. My point is use objects, not strings, and serialize to byte arrays.

Without any further clarification I find difficult to provide some useful advice, but in case you are just trying to figure out how to do it, I'll explain how I would do it.

If your client is also Java, then using plain ol' Serializable objects saves a lot of headaches, and if you are using sockets or something alike, it requires you to send the data as a byte array anyways. I would have a class implementing java.io.Serializable for each command you need, and just serialize it to a byte array and send it through the socket. For instance:


public class RotatePlayer implements Serializable {
	
	private static final long serialVersionUID = -171890897880063974L;

	long playerId;
	
	double angle;
}

Repeat for each command and that's all you need to worry for. You can save some bits by changing the types to a shorter ones, if that worries you. Even you could use 'byte' for angle and transform it to degrees before and after transmission, for many cases I would find 256 possible directions to be enough.

Yeah, sure, you can save some bytes by implementing your own serialization system (*), but is really worth it?

More on serializing/deserializing: http://stackoverflow.com/questions/2836646/java-serializable-object-to-byte-array

(*) Of course, Serializable objects add some amount of data because Java needs to know how to deserialize the object later. I tested the class above and serialized is 73 bytes lenght, which is like 57 bytes more than the data I really want to send, but in most cases the tradeoff pays for its ease to use. If that amount is too much, I would implement my own serializing system, but I would send bytes, not strings, and I would have one class for each command. You could have the first one or two bytes (depending on the amount of classes you need to serialize) for identifying the class and then provide a constructor that with the rest of bytes could build the object. You'll probably need to do that way also if your client is not writen in Java.

When you say "improve," what is your goal? Do you have any actual measured problem that you're trying to solve?

If saving space of state updates is very important, as suggested above, using binary marshaled data is more efficient. I typically quantize a position to three sixteen-bit or twenty-four-bit values. I generally quantize an orientation to three ten-bit values as a quaternion (re-constructing the fourth value,) using one of the last two bits out of 32 bits to tell the sign of the lost value. Velocity can often be packed into three bytes, and if that's not enough precision, six bytes certainly will be for almost all cases.

Also, I would recommend against using the Serializable interface/mechanism in Java (or any other language,) for networked games, as those are typically designed for business objects, where space is not of utmost concern. If you already know what the packet format will be, no need describing it in every packet you send.

For player IDs, I can get away with a single byte (or a short, if it's an "M"MOG) if I introduce the player name associated with an ID in a separate packet. For persistent single-shard worlds, it might make sense to send a full, globally unique, user ID, where 4 bytes is sufficient unless you plan on having more than 4 billion registed users :-)

So, a potential binary serialization of the data you suggest might look like this in binary (the lengths are in bits):

<type:4> <fields:4> <player:8> <position:48> <orientation:32> <speed:24> <shoot:24>

"type" lets you determine different packets (for speaking, inventory updates, or whatnot.) For this particular packet type, you assign a particular constant, such as 1.
"fields" is a bitmask of which of the position/orientation/speed/shoot fields are actually included (unless you always include all of them.)
"player" is the player for which this update is, as an ID within the game -- when joining a player is assigned an ID in the range 0..255.
"position" is the position, quantized to 16 bits. If your minimum/maximum position values are -1024..+1023.xxx, you would calculate the quantized value of each component as (short)(pos.x*32), and you'd re-constitute the float by dividing back out: comp_x/32.0
"orientation" is an orientation quaternion encoded as suggested above (the range of each component is -1..1 with 10 bits each)
"speed" is like "position" except smaller range (-4..+3.xxx) -- if this is not enough, use more bits.
"shoot" is a vector just like "speed."
enum Bool { True, False, FileNotFound };

Thank you. What I wanted was to make the commands shorter. I believe I will use your approaches. The problem was with the latency since the current commands were too long.

Wait, latency should not depend on the length of your commands, unless you are trying to push through so much data that it saturates the connection.

For latency, you need to do something different:

-Make sure your client sends the data soon after you have some and does not lets say buffer it (TCP tends to buffer data for a while before sending using something called nagle's algorithm, if you are using TCP you probably want to disable nagle's algorithm using a flag or something)

-Use UDP if you really need low latency because if TCP loses a packet it will ask the client to send it again, which means all following packets have to wait till the lost packet is resent.

-Hide the latency. All fast paced games need to hide latency, because it is often significant enough for the user to notice and there is nothing you can do to remove that latency (usually a few hundred ms). Essentially the data you receive from the other clients/server is always outdated, and you need to use that old data to predict what the current state of the game is on the other machines so it looks somewhat real time. Then when your predictions are wrong you correct for it (and try to hide these corrections).

o3o

This topic is closed to new replies.

Advertisement