Main Page | Modules | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Class Members | Related Pages | Examples

Gameworld Data

Most gameworlds are represented as a bunch of well organized data-structures. In strict server-client based games, the server holds a full copy of the world, and the client hold a subset of the data constituting the world. The datatypes might not even be the same on server and client, because the client might not need to know all about every entity in the game. This is why Ganef uses two distinct base classes for datatypes, ClientData and ServerData.

The idea is that every entity in your gameworld on the serverside inherit from ServerData. This gives you some very nice methods for replicating the data from server to one or more clients, the names and comments of these methods should explain what they do:

   public:
   /// Will send a create-object packet to all clients.
   void sendCreateObjectToAll() const;
   /// Creates and sends a create-object packet to a list of clients.
   void sendCreateObject(const std::map<unsigned int, Client *> & clientlist) const;
   void sendCreateObject( Client * client) const;

   /// Will send an update packet to all clients.
   void sendUpdateToAll(char type, bool reliable) const;
   /// Creates and sends an update packet to a list of clients.
   void sendUpdate(const std::map<unsigned int, Client *> & clientlist, char type, bool reliable) const;
   void sendUpdate( Client * client, char type, bool reliable) const;

   /// Will send a destroy/delete-object packet to all clients.
   void sendDestroyToAll() const;
   /// Create and sends a destroy-object packet to a list of clients.
   void sendDestroy(const std::map<unsigned int, Client *> & clientlist) const;
   void sendDestroy( Client * client ) const;
The important thing to notice here is that when you call the sendCreateXXX methods, a similar datastructure inheriting from ClientData will be initialized and inserted into the ClientFramework on the clients you specify. The important thing to notice here is that every class in your gameworld must have a unique id. This id should be returned by the serverside data with the virtual method:
   /// This must produce a unique id for this class, it will be used on the client side to construct new objects.
   virtual const unsigned int getClassId() const = 0;
And on the client side you should register a static method as the "constructor" for the client side class. This could be done as follows:
class MyServerData : public ServerData
{
   private:
      double x,y;
      /// [... other members ...]
   public:
      virtual const unsigned int getClassId() const { return 42; };
      /// Here the data-class should fill in all data required by the constructor on the client side.
      virtual void fillCreateObjectPacket(FrameworkPacket * packet) const
      {
         packet->writeInt32((int)x);
         packet->writeInt32((int)y);
      }
      /// [... other methods ...]
};

class MyClientData : public ClientData
{
   private:
      int x,y;
   public:
      MyClientData(ClientFramework * f, FrameworkPacket * p) : ClientData(f,p)
      {
         // Here you would read your class data from the packet
         x = p->readInt32();
         y = p->readInt32();
      }
      static ClientData * constructMyData(ClientFramework * f, FrameworkPacket * p)
      {
         return new MyClientData(f,p);
      };
};

class GameClient : public ClientFramework
{
   // [... class methods and members ...]
} app;
app.registerConstructor(42, &MyData::constructMyData);
Now if the server has an object of class MyServerData and calls "sendCreateObject(client)", a FrameworkPacket will be created and inserted in the outqueue on the server. Once the packet is sent and received on the client the packet will hint the client about what constructor method to call (based on the class id, in this case "42") and thereby accuire a new object of class MyClientData. An important thing to notice is that MyClientData calls the constructor "ClientData(ClientFramework * f, FrameworkPacket * p)" of ClientData. This is very important because the ClientData should get to read from the packet first, because the very first thing in the packet is the unique object id see UniqueFrameworkID.

This also gives you the option of deriving classes even more, and then in each contructor read what ever data is expected, hence reducing duplication of code. If you have the same inheritance structure on the server you must likewise call the method for writing the initial constructor packet recursively, here is an example:

class MyServerBaseData : public ServerData
{
   private:
      double x,y;
   public:
      virtual const unsigned int getClassId() const = 0; // Not implemented, because this class is still abstract
      /// Here the data-class should fill in all data required by the constructor on the client side.
      virtual void fillCreateObjectPacket(FrameworkPacket * packet) const
      {
         packet->writeInt32((int)x);
         packet->writeInt32((int)y);
      }
};
class MyServerRealData1 : public MyServerBaseData
{
   private:
      std::string name;
      MyServerBaseData * best_friend;
   public:
      virtual const unsigned int getClassId() const { return 42; };
      /// Here the data-class should fill in all data required by the constructor on the client side.
      virtual void fillCreateObjectPacket(FrameworkPacket * packet) const
      {
         // Let our base class write first
         this->MyServerBaseData::fillCreateObjectPacket(packet);
         // Then we write
         packet->writeString(name);
         packet->writePtr(best_friend);
      }
};
On the client you would have:
class MyClientBaseData : public ClientData
{
   private:
      int x,y;
   public:
      MyClientBaseData(ClientFramework * f, FrameworkPacket * p) : ClientData(f,p)
      {
         // Here the base data is read (before us the ClientData class read its unique framework id)
         x = p->readInt32();
         y = p->readInt32();
      }
};

class MyClientReal1Data : public MyClientBaseData
{
   private:
      std::string name;
      MyClientBaseData * best_friend;
   public:
      MyClientBaseData(ClientFramework * f, FrameworkPacket * p) : MyClientBaseData(f,p)
      {
         // It's time to read our data
         name = p->readString();
         p->readPtr(this, &best_friend, f);
      }

      static ClientData * constructMyData(ClientFramework * f, FrameworkPacket * p)
      {
         return new MyClientReal1Data(f,p);
      };
};


[Next: Setting up the server ]
Generated on Mon Feb 6 12:24:53 2006 for Ganef by  doxygen 1.4.4