Jump to content

  • Log In with Google      Sign In   
  • Create Account

How to send data for an RTS?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
9 replies to this topic

#1 James Lovejoy   Members   -  Reputation: 106

Like
0Likes
Like

Posted 13 October 2012 - 07:46 AM

Hi,

I'm making an RTS in C++ and I'm using enet for the multiplayer networking. I am currently sending each individual units' details in text format and I don't think this is the best option. So my question is this: how should I be sending data? Each unit is part of an array; can I send the whole array? And how would I decode that array at the other end?

Thanks,

James

Sponsor:

#2 m41q   Members   -  Reputation: 151

Like
1Likes
Like

Posted 13 October 2012 - 11:07 AM

Hi,

I´m programming in Java but I think the general concept may be useful for you too:
The Data that I transmit is an array of bytes.
The first byte determines wich data is sent (e.g. position of an unit,), the second the length of the data (e.g. 200 bytes). Next comes the data itself and then comes the next package.
On the other side I read the first two bytes and then read the data and from the transmitted bytes I am able to assign the right values to the objects.
You can convert ANY value into bytes and back. So if you have to send text, you convert it and when it is on the other side, you just reconvert it.

But as a sidenote, multiplayer programming is hard...
I would have been much faster with my own project if it wasn´t for the multiplayer Posted Image

#3 KnolanCross   Members   -  Reputation: 1317

Like
1Likes
Like

Posted 13 October 2012 - 12:02 PM

Depends on how your lib work, I have made a world explorer game for fun and in my project i did this way:
- Each message had one specific struct, the client would fill the information and send it to the server.
- Every message struct would begin with an unsigned int that was its message ID (for instance: login was id 0, start moving was id 1, and so on).
- The server had one socket for each player (so when I received information, I would easily know who sent it), it would buffer the information received and check the first four bytes of the message. If those four bytes were a valid ID, it would call a function that would validate de message content and process it.

You should probably adopt pretty much the same approach, create one struct for the unity update, something like this:

[source lang="cpp"]struct _unityUpdateMessage_t{ uint32_t id; uint32_t unityId; float64_t x, y, z, facingAngle; //extra info that may be needed, such as health, attack information, and so on...};[/source]
Just send the whole struct via socket and parse it on the server by getting the first fout bytes. I used a pointer approach in my case, here is an example:
[source lang="cpp"]unsigned int extractUnsignedInt(char** msg){ unsigned int* value; value = (unsigned int*) *msg; *msg += sizeof(unsigned int); return *value;}[/source]
Hope this helps.

Currently working on a scene editor for ORX (http://orx-project.org), using kivy (http://kivy.org).


#4 James Lovejoy   Members   -  Reputation: 106

Like
0Likes
Like

Posted 14 October 2012 - 10:12 AM

Depends on how your lib work, I have made a world explorer game for fun and in my project i did this way:
- Each message had one specific struct, the client would fill the information and send it to the server.
- Every message struct would begin with an unsigned int that was its message ID (for instance: login was id 0, start moving was id 1, and so on).
- The server had one socket for each player (so when I received information, I would easily know who sent it), it would buffer the information received and check the first four bytes of the message. If those four bytes were a valid ID, it would call a function that would validate de message content and process it.

You should probably adopt pretty much the same approach, create one struct for the unity update, something like this:

[source lang="cpp"]struct _unityUpdateMessage_t{ uint32_t id; uint32_t unityId; float64_t x, y, z, facingAngle; //extra info that may be needed, such as health, attack information, and so on...};[/source]
Just send the whole struct via socket and parse it on the server by getting the first fout bytes. I used a pointer approach in my case, here is an example:
[source lang="cpp"]unsigned int extractUnsignedInt(char** msg){ unsigned int* value; value = (unsigned int*) *msg; *msg += sizeof(unsigned int); return *value;}[/source]
Hope this helps.


Thanks for the reply. My data comes through in the form "void*" and yours "char**" by the looks of it, how can I make your code work in that case?

#5 KnolanCross   Members   -  Reputation: 1317

Like
1Likes
Like

Posted 14 October 2012 - 02:06 PM

Not sure how your function works, but I will assume you have a void* variable called "receivedMessage" ok?

[source lang="cpp"]char* messageToProcess = (char*) receivedMessage;unsigned int code = extractUnsignedInt(&messageToProcess);[/source]

Please notice that my function "walks" on the message, so the "messageToProcesses" will point to receivedMessage + sizeof(unsigned int) after the message is received. I coded it this way because I mostly would call those "extract<TYPE>" functions in a roll. For instance, If I wanted to process the _unityUpdateMessage_t I would use (in this case the id was already extracted):

[source lang="cpp"]int processUnityUpdateMessage(char* message){ unsigned unityId = extractUnsignedInt(&message); float x = extractFloat(&message); float y = extractFloat(&message); float z = extractFloat(&message); float angle = extractFloat(&message); // check if variables are valid, replace the unity, send a message for the clients so they update their positions too.}[/source]

Another important note, you should probably use more generic types (such as uint_32t, instead of unsigned int) if you want your program to work on both 32 and 64 bits programs.

Edited by KnolanCross, 14 October 2012 - 02:07 PM.

Currently working on a scene editor for ORX (http://orx-project.org), using kivy (http://kivy.org).


#6 James Lovejoy   Members   -  Reputation: 106

Like
0Likes
Like

Posted 14 October 2012 - 03:16 PM

Not sure how your function works, but I will assume you have a void* variable called "receivedMessage" ok?

[source lang="cpp"]char* messageToProcess = (char*) receivedMessage;unsigned int code = extractUnsignedInt(&messageToProcess);[/source]

Please notice that my function "walks" on the message, so the "messageToProcesses" will point to receivedMessage + sizeof(unsigned int) after the message is received. I coded it this way because I mostly would call those "extract<TYPE>" functions in a roll. For instance, If I wanted to process the _unityUpdateMessage_t I would use (in this case the id was already extracted):

[source lang="cpp"]int processUnityUpdateMessage(char* message){ unsigned unityId = extractUnsignedInt(&message); float x = extractFloat(&message); float y = extractFloat(&message); float z = extractFloat(&message); float angle = extractFloat(&message); // check if variables are valid, replace the unity, send a message for the clients so they update their positions too.}[/source]

Another important note, you should probably use more generic types (such as uint_32t, instead of unsigned int) if you want your program to work on both 32 and 64 bits programs.


Ok great, that works fine and dandy. Now let's say I needed to send an array this time (ie an array of projectiles), is there a way I could send the whole array at once?

#7 KnolanCross   Members   -  Reputation: 1317

Like
0Likes
Like

Posted 14 October 2012 - 09:16 PM

Depends on how this array is coded... if it is something like:

[source lang="cpp"]myProjectileType projectsArray[100];[/source]
or

[source lang="cpp"]myProjectileType *projectsArray = (myProjectileType*)malloc(sizeof(myProjectileType)*100;[/source]

You can send it via a socket, if it is a vector or a list, you can't simply send it. In those example 100 is just a random number, you can have any size.
Either way, you main problem will be finding how many positions are filled in the other side.
There are two approaches, the best one is create this array inside of another struct, specially made to be sent via socket, for instance:

[source lang="cpp"]struct _projectilesUpdate_t{ uint32_t messageId; uint32_t numberOfProjectiles; myProjectileType projectielsInfoArray[100];};[/source]
You must send the struct, and the size argument of your send function should be: (sizeof(uint32_t) + sizeof(uint32_t) + (sizeof(myProjectileType) * numberOfProjectiles)).

In the client side you should extract the messageId to find the message is projectile update. Then extract the second uint32_t to find how many positions of the array are filled, then you may cast the rest of the received message to a "myProjectileType*" and work it as an array. Something like this:

[source lang="cpp"]char* processMessage = (char*) receivedMessage; //again I assume you have your void* received message hereuint32_t messageId = extractUint32t(&processMessage);if (messageId == PROJECT_ID_CODE){ // assuming you have a define for that id uint32_t numberOfProjectiles = extractUint32t(&processMessage); if (numberOfProjectiles != 0 && numberOfProjectiles < PROJECTILES_LIMIT_PER_MESSAGE){ myProjectileType* projectileArray = (myProjectileType*) processMessage; uint32_t i; for(i = 0; i < numberOfProjectiles; ++i){ //process projectile info... such as somevear = projectileArray[i].some_member_of_myProjectileType; } }}[/source]

Two important notes, first is that you should take care that in this case there is a limit of 100 projectiles per message. If you have more, you need to send two messages (or increase that number). The best approach is to have a define, in the code above I took the liberty of naming it PROJECTILES_LIMIT_PER_MESSAGE.
Also, both machines must have the same endianess.

Finally, you should check if your received message doesn't have more information after the array. In order to do that you should check the amount of data receive and compare with the size of this message (that is variable, the formula is the same as I mentioned in the sending part of the post).

Hope this helps.

EDIT: Grammar.

Edited by KnolanCross, 14 October 2012 - 09:24 PM.

Currently working on a scene editor for ORX (http://orx-project.org), using kivy (http://kivy.org).


#8 BeerNutts   Crossbones+   -  Reputation: 2950

Like
2Likes
Like

Posted 16 October 2012 - 03:20 PM

FWIW, there's a little trick you can perform if you want to send a variable amount of data, but have it defined in a struct. You can do this to create a generic packet struct:

struct TPacketData
{
  uint16_t DataType;
  uint16_t DataSize;
  uint8_t Payload[1];
};

// Payload will initially be a 1 byte array, but you can allocate a large piece of data
// So, to send your TGameState GameState information:
TPacketData *GameStatePacket = (TPacketData *)malloc(sizeof(TPacketData) + sizeof(TGameState));

GameStatePacket->DataType = GAMESTATE_TYPE;
GameStatePacket->DataSize = sizeof(TGameState);
memcpy(GameStatePacket->Payload, GameState, sizeof(TGameState));

// SendData sends to server, but it reads the data size to know how big the full data is
SendData(GameStatePacket);

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

#9 BeerNutts   Crossbones+   -  Reputation: 2950

Like
0Likes
Like

Posted 16 October 2012 - 03:23 PM

Depends on how this array is coded... if it is something like:

[source lang="cpp"]myProjectileType projectsArray[100];[/source]
or

[source lang="cpp"]myProjectileType *projectsArray = (myProjectileType*)malloc(sizeof(myProjectileType)*100;[/source]

You can send it via a socket, if it is a vector or a list, you can't simply send it. In those example 100 is just a random number, you can have any size.
Either way, you main problem will be finding how many positions are filled in the other side.
There are two approaches, the best one is create this array inside of another struct, specially made to be sent via socket, for instance:

[source lang="cpp"]struct _projectilesUpdate_t{ uint32_t messageId; uint32_t numberOfProjectiles; myProjectileType projectielsInfoArray[100];};[/source]


As I mentioned in my last post, you can actually do this to handle dynamic number of projectiles:
[source lang="cpp"]struct _projectilesUpdate_t{ uint32_t messageId; uint32_t numberOfProjectiles; myProjectileType projectielsInfoArray[1]; // I believe this can even be 0, but I know 1 works};[/source]

So you can send more than 100 projectiles in one packet if you must.

Edited by BeerNutts, 16 October 2012 - 03:27 PM.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

#10 KnolanCross   Members   -  Reputation: 1317

Like
0Likes
Like

Posted 16 October 2012 - 04:10 PM

Yeah, you can do this, but I don't think it is a good idea because you will have to copy the array information into your memory allocated region, this will be slower.

You could cast the extra memory region and work directly on it to avoid the need of this memcpy. But you need to know very well what you are doing, otherwise you will run into a SEGFAULT right way (or even worse, invade a region that is not used yet and run into a segmentation fault in another random malloc call).

Currently working on a scene editor for ORX (http://orx-project.org), using kivy (http://kivy.org).





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS