Advertisement

Implementing multiplayer in MOBA-like game

Started by June 28, 2015 09:11 PM
14 comments, last by hplus0603 9 years, 4 months ago

If the server was made in C++, can I send the whole structs over the socket? Beacause I've read somewhere that its not recommended but didn't really understand this.

If the server was made in C++, can I send the whole structs over the socket? Beacause I've read somewhere that its not recommended but didn't really understand this.


There are several layers to decode here.

First, in a C or C++ program, a "struct" is laid out in memory in a certain way. This way can be specific to each compiler (or even to specific versions of a compiler) as well as specific to particular alignment rules depending on the CPU architecture.

"Sending a struct" really just means "sending a raw set of bytes that happened to be written by the compiler as a struct."

Yes, you can send a raw set of bytes -- in some respect, that is the only thing you can ever really send. Everything else has to go through some form of "marhsalling" on the sending side, and "demarshalling" on the receiving end. For example, to send a variable-length string, the sending side would have to first send the length of the string, and then the actual string data; the receiving end would first read the length, and then reserve enough space to receive the string data and return to the program.

The reason structs are "not recommended" is because you don't have full control over the layout. A struct that is laid out in one particular way on a 64-bit Intel platform, may be laid out a totally different way for a 32-bit ARM running in 16-bit Thumb mode. Specifically, the amount of padding inserted by the compiler for performance / alignment rules may differ.
A secondary concern is that of binary representation. IEEE-754 is pretty universal, and using "float" and "double" almost always means the same thing, and two's complement representation for integer values is also something that everybody does by now. (Fourty years ago, this was not true.)
But -- sizeof(size_t) may be different on different machines. sizeof(int) is 2 on Arduinos, and 4 on Raspberry Pis. And, also importantly, while the world is largely skewing towards little-endian representations these days, there still exist big-endian platforms and implementations out there. All of these are things that COULD change between sender and receiver, especially if you are multi-platform, or define an open protocol which multiple parties can implement.

Again, in networking, what really matters is the bytes that are sent on the wire. As long as sender and receiver agree on this, to an extremely high level of precision and determinism, any way to get there is fair game.
Depending on which language you're using, and how familiar you are with the language, you may not yet have the skills and experience needed to understand where these things will vary, and why -- if so, you're probably better off either using a marshaling library of some sort (protocol buffers) or sharpening your tools on lighter-weight systems programming tasks (binary file formats, 3D graphics, etc.)
enum Bool { True, False, FileNotFound };
Advertisement

I'm having a different problem now but it's still related to multiplayer implementation so I figured I'll write it here as well. I have the client and the server working and the clients get updates every 1/20 of a second containing information like positions of all characters.
What I'm doing in short on the client is I'm drawing the enemies in the past, meaning I store a previous packet in memory and when the next one arrives I interpolate the enemy movement from the previous packet position to the next one. When the packet arrives I also correct the position of a given player if it differs greatly from the data I receive from the server. The problem here is that it never is as smooth as I'd like, certainly not as smooth as movement of character just drawn by the client.

The correction formula: (distance is the distance between current player position and where it should be when the new packet arrives(which is the position of the previous packet)).

if (distance > 2.0){
position = pos;
}
if (distance > 0.1){
position += distVec*0.1f;
}

How do you go about drawing enemies? I'm 99% sure you have to draw them in the past. Maybe go one packet further and draw them with a delay of two packets?

How do you go about drawing enemies?


You either draw them "correctly, in the past," or you draw them "approximately, forward extrapolated."

For smoothness, you'll want to interpolate between the 20 Hz snapshots. If you draw in the past, that means that, when you receive a packet, you set the entity to move such that it will reach the determined position 50 milliseconds into the future (and then, presumably, stop.) If you draw in the future, you forward extrapolate position/velocity from the past-snapshot, out into the future-50ms, and set the entity to move such that it will reach that position in 50 milliseconds.

You may find the EPIC (Entity Position Interpolation Code) library/illustration helpful: http://www.mindcontrol.org/~hplus/epic/
enum Bool { True, False, FileNotFound };

So after reaching the position such player should stop? What if the update arrives earlier/later due to latency for example? I'll look into the link, thanks.

So after reaching the position such player should stop? What if the update arrives earlier/later due to latency for example?


Generally, you will measure the jitter of arriving updates, and delay the time at which you "arrive at the target" such that you're unlikely to not have the next packet. The term for this is usually something like "de-jitter buffer." If the next packet hasn't arrived, then the entity will indeed stop.
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement