structs or classes?

Started by
7 comments, last by Zahlman 16 years, 3 months ago
Having started game coding in Java, I naturally think of the elements of a game as a tree of objects, each with their respective data and functions. I have never dealt with networking, neither in Java or C++, but I did skim a page on winsock.h, which by the way I'm going to read thoroughly when I have the chance. Since I don't know much about how data is sent across networks, I have to make a couple assumptions based on what I read about sockets and what I know about data structures - what is sent in each packet is basically a struct - a container with nothing but primitives inside (and maybe more structs). I know its probably a bad idea to base the rest of what I'm going to say on that assumption, but I'm going to go with it... I'm making a 3D, multi-player, real time strategy game in C++ with OpenGL. This brings in many new features of programming that I've never dealt with - massive collision detection, 2d to 3d picking (tackled already, thx red book), and umm OpenGL graphics, to name a few :) So far I have coded a couple world testers, read through the first 5 chapters of red book, and currently I'm planning out how to store/keep track of the objects in the game. After a quick search it looks like arrays and linked-lists are used very often for this purpose - for their simplicity and ability to quickly cycle through every object and update it, render it, etc. Well, again, assuming what I said earlier, this is what I have so far:

struct object
{
   double x;
   double y;
   ubyte currentAnimation; // current action: movement, attack,... 0 is idle
   ubyte animationIndex;   // frame of that animation
};

object objectContainer[200];  // array containing all objects currently in game




I feel that this struct is simple yet powerful enough to contain any object - whether it be a ground unit, a building, or a projectile. If your asking how will I tell the objects apart - that I'll do by dividing the sections of the array - objectContainer[0-50] will be ground units, 51-100 buildings, etc. This is only temporary; eventually I will think through what pieces of data I need to access the most, and organize the data into a structure that suits those needs, but first I want to experiment with a fixed-size array. The beauty of this method is that all I need to send across the network to my opponent player is this single, relatively small, array - and he will know the current position and state of every object in the game (which he can then process and compare to his objectContainer). Theres a few details I'm overlooking, and this post is already longer than I thought it was going to be, so here are my questions: Is there a shred of truth in my assumption? or can you send a linked-list in a packet?? (data with pointers to more data, in general) Will the method I outlined work? Should I stick to classes, and instead insert objects of class object into the array? and can this be sent in a packet? OR should i keep data I will send across the network separately organized from local data? Any help about any of these questions and networking in general is much appreciated! Hell, laugh at my absurdly wrong networking assumptions and give me a beginners lesson - I need to be corrected + I just want to learn! [Edited by - saeedm on January 1, 2008 8:32:22 AM]
Advertisement
Quote:I feel that this struct is simple yet powerful enough to contain any object

What about units with multiple segments -- e.g. a tank with a turrent?
Or how about a marine with a limited supply of ammo? What about health? Build queues?

Data is usually sent across networks as a simple stream of bytes. No fancy packet mechanics, just one continuous stream. You can represent any data structure with those bytes, it's just a matter of how you decide to send and interpret them.

Quote:If your asking how will I tell the objects apart - that I'll do by dividing the sections of the array - objectContainer[0-50] will be ground units, 51-100 buildings, etc.


Very very very very bad idea. Magic numbers galore.

If you want to divide the array up into groups, do it by actually dividing the array:

object groundUnits[50];
object buildingUnits[50];
object bullets[50];

(or, better yet, using containers so you're not limited to a fixed number of anything:)

std::vector< object > units;
std::vector< object > buildings;
std::vector< object > bullets;

Alternatively, have object store some sort of category identifier:

struct object {    enum object_type { UNIT, BUILDING, BULLET } type;    double x;    double y;    ubyte currentAnimation; // current action: movement, attack,... 0 is idle    ubyte animationIndex;   // frame of that animation};std::vector< object > everything;


Just a couple of random pieces of advice to doll out in random order:

1) "magic" numbers are bad (e.g. assigning numbers meaning, like "51-100 are buildings"). Your code should be easy to understand, and it's not easy to understand if someone comes across comparisons to 50 and 100 in your code and has to go look up what they mean.

2) Don't aim to try and deal with everything ahead of time. Instead of "powerful enough to contain any object", a much better goal would be, say, "powerful enough to contain any of the objects I currently have, yet easy enough to fix/extend/change in the future". That is, don't try to predict what you're going to need, try to make your code easy enough to modify when you find out what you do need.

3) I suggest hitting up a basic networking tutorial, such as a chat client + server, before trying to rush out and create a networked game the first time around. Understanding the basics will give you a much better grounding and understanding of what you do and don't need to do in terms of it.

4) In C++, Classes and Structures are the same thing. There's only a couple cosmetic differences between the two keywords when used for this.
Thanks for your reply - haven't had the chance to read up on networking yet but I'll get to it today. Even still, I need to get a few things straight...

Quote:Very very very very bad idea. Magic numbers galore.


I was expecting some bashing for that :) Heres why I did it: (not saying I disagree with you, but for now this way looks easiest to me) I don't know if you're familiar with the picking feature built into OpenGL, but what I did was make the array index of objectContainer[] represent an objects 'name', for use with the namestack. So if my picking function returns a '59', I simply go to objectContainer[59] and that is the object that has been selected. Since RTS games rely heavily on picking for doing just about everything from selecting units to attacking, I need to have the 'name' value quickly accessible - if it was just another value in the struct it would be much slower to find the appropriate object. I'll definitely think through how to get around this, I don't like it either - but the thought of running hundreds of linear time searches every time I sweep select a large army draws me away from storing the 'type/name' value in the struct.

Quote:Data is usually sent across networks as a simple stream of bytes. No fancy packet mechanics, just one continuous stream. You can represent any data structure with those bytes, it's just a matter of how you decide to send and interpret them.


Sorry for not studying up before I ask you any more about networking, but I need a concrete example on this. Lets say I have a send() function, and a linked list 'llist' (the head). If I were to say send(llist), would c++ dereference and find all of the pieces of the linked list for me, package them, and send them as a 'stream of bytes'? Secondly, by stream did you imply that I only send data as it happens, like changes in movement/actions executed since the last packet?

About the point you make in 2): Sure, but isn't the point of thinking through these things simply to avoid having to make radical changes to the underlying engine in the future? To get a solid start which wont need more than a little tweaking to reach 'final product' status? I see myself balancing the two all the time - expandability and integrity.
Quote:Original post by saeedm
I don't know if you're familiar with the picking feature built into OpenGL, but what I did was make the array index of objectContainer[] represent an objects 'name', for use with the namestack. So if my picking function returns a '59', I simply go to objectContainer[59] and that is the object that has been selected. Since RTS games rely heavily on picking for doing just about everything from selecting units to attacking, I need to have the 'name' value quickly accessible - if it was just another value in the struct it would be much slower to find the appropriate object.


So, instead of having the picking function return 59, have it return a structure {1, 9}, where the '1' indicates which container to look in.

Also, strongly consider if you really want some kind of associative container, such as std::map.

Quote:Sorry for not studying up before I ask you any more about networking, but I need a concrete example on this. Lets say I have a send() function, and a linked list 'llist' (the head). If I were to say send(llist), would c++ dereference and find all of the pieces of the linked list for me, package them, and send them as a 'stream of bytes'?


No; you have to implement it. [google] "serialization". Note that Boost offers some stuff here.

It seems like serialization would be a real pain to deal with on more complex structures, right? like a vector filled with objects of a certain class... Would you recommend I start with continuous data structures, like arrays and structs? I've read up on winsock and I feel I could easily send such a structure.
Classes.
Quote:Original post by saeedm
It seems like serialization would be a real pain to deal with on more complex structures, right? like a vector filled with objects of a certain class... Would you recommend I start with continuous data structures, like arrays and structs? I've read up on winsock and I feel I could easily send such a structure.


There isn't really a way around it. "Serialization" is a catch-all term that describes the general process; some special cases are much easier than others.

But for what it's worth, (a) a std::vector stores things contiguously just like an array; (b) a struct isn't necessarily "contiguous" in that it may contain padding. Further, you should consider that if you are hoping to just send the raw bytes that happen to be in memory on computer A over to computer B's instance of the program and start using them, that you may be in for a bit of a shock. Any difference in hardware, operating system, compiler and even seemingly minor changes in compilation settings can cause this not to work.
Quote:Original post by Zahlman
Quote:Original post by saeedm
It seems like serialization would be a real pain to deal with on more complex structures, right? like a vector filled with objects of a certain class... Would you recommend I start with continuous data structures, like arrays and structs? I've read up on winsock and I feel I could easily send such a structure.


There isn't really a way around it. "Serialization" is a catch-all term that describes the general process; some special cases are much easier than others.


At least, in languages (such as C++) with poor reflection and metaprogramming support there isn't. Higher level languages like Ruby and C# have a lot more automated support for this kind of thing.
Quote:Original post by MaulingMonkey
Quote:Original post by Zahlman
Quote:Original post by saeedm
It seems like serialization would be a real pain to deal with on more complex structures, right? like a vector filled with objects of a certain class... Would you recommend I start with continuous data structures, like arrays and structs? I've read up on winsock and I feel I could easily send such a structure.


There isn't really a way around it. "Serialization" is a catch-all term that describes the general process; some special cases are much easier than others.


At least, in languages (such as C++) with poor reflection and metaprogramming support there isn't. Higher level languages like Ruby and C# have a lot more automated support for this kind of thing.


Realistically, though, that kind of support only increases the number of easy cases. It isn't necessarily practical to just dump everything. Although it often works very well to just save a repr() of a root object and read and eval() the file to reload, there are sometimes issues with implmenting the repr(), avoiding serialization of large built-in objects, avoiding redundancy, pretty-printing (to allow for external editing), etc.

This topic is closed to new replies.

Advertisement