Sign in to follow this  
DANNER

Creating a Structured Game World Loader

Recommended Posts

So I've been working on my own 3D Engine (written in C++) for some time now with the intent that it be highly structured with separate subsystems that are not tightly coupled.  i.e. The visibility subsystem is completely independent of the rendering API and  the majority of the engine is rendering API agnostic.   To that end, I've also decoupled all the file loading from the rest of the engine (I don't know if it exactly adheres to the MVC pattern since I'm not a app programmer by trade, but it's close I suppose).
 
Here how it works: Just about every resource (entity, light, texture, shader) is specified in an XML file.  To load the objects there is a Builder class which creates these descriptor objects.  All these descriptors look similar the following:
 
class CDescriptorEntity
{
public:
    CDescriptorEntity();
    virtual ~CDescriptorEntity();
    
    typedef enum EClassEnum
    {
        ENTITY_CLASS_NONE,
        ENTITY_CLASS_GENERIC
        ENTITY_CLASS_PLAYER
    } EClass;
   
    // Lots and lots of Get property methods called
    // by the actual game object to set it self up
 
    const char* GeEntityName() const
    {
        return m_entityName.c_str();
    }
    
    EClass GetClass() const
    {
        return m_class;
    }
    
    const CVector3& GetPos() const
    {
        return m_pos;
    }
    
    const char* GetModelName() const
    {
        return m_modelName.c_str();
    }
   
    // Even more GetXXX() method I'm not showing here
    ...
protected:
    // Storage for each of the properties[/color]
    std::string m_entityName;
    EClass m_class;
    CVector3 m_pos;
    std::string m_modelName;
    
}; 
 
These descriptor classes don't actually load the data.  They simply store it.  Instead a subclass loads it from the XML file (I use libXML btw to actually get the xml config file parsed)
 
class CDescriptorEntityLibXML : public CDescriptorEntity
{
public:
    CDescriptorEntityLibXML();
    virtual ~CDescriptorEntityLibXML();
 
    void LoadNode(xmlNodePtr entityNode)
    {
        //...Load all the members of CDescriptorEntity
        //within the xmlNode passed in
    }
};
 
Then in the builder object I use these descriptors to actually create the in game object like the following
 
 
...
 
// If we've found an entity xmlNode
 
// ...declare a descriptor on the stack[/color]
CDescriptorEntityLibXML entityDescriptor;
 
// Read the data from the xml file
entityDescriptor.LoadNode(xmlNode);
 
// Instantiate the entity
CEntity* gameEntity = new CEntity();
 
// Set up the entity with all the descriptor
// data by calling all the Get methods
gameEntity->Load(entityDescriptor);
 
// Add entity to a resourceManager so we can keep track of it
resourceManager.AddEntity(gameEntity);
...
 
This is actually a simplified example of how my engine does it.  The entity descriptor here is quite simple compared to other objects, most of which have tons more data per object, thus more fields and Get methods. 
 
This system is nice in that the each in game object class doesn't need to know anything about how the object is encoded in the file. That's all handled by the descriptor's derived class.  The entity just needs to call the Get() functions in the descriptor to setup the data from the config file.   If I want to store the game world data in a different format or something, I could derive a new class from the base descriptor and have it fill all the descriptor fields.  I could also create game objects from an in-game console and just pass a descriptor to the world builder to create the object (i.e. the object itself doesn't need to know where the data came from)
 
The problem I'm running into is that I need to write so much tedious code in order to load each of these different type of objects. I've got at least 20 of these descriptor classes and then 20 more derivates (i.e. one libXML version for each base class).  Each new type of object requires another pair of these descriptor.  It's just a lot of code for such a seemly simple task.
 
There must be a better way that requires less code.  Are there any examples of engines that have more sane world loaders?  Are there libraries or even languages that other Engines use that are better suited for the task?

Share this post


Link to post
Share on other sites

Back when I was writing my serializations, I investigated a couple of serialization-oriented libraries. None bought me 100%.

In the end, I wrote my own binary serialization. Yes, ideally I wanted something more automated but no matter how hard I tried, certain (de)serialization transforms were always too complicated. As time passed, I realized my codebase stabilized and the binary serialization proven to be pretty stable. I streamlined it using a more flexible system. Some entities are still not using this system, but that's no problem as the two are functionally equivalent.

 

What's the difference between my code base and yours? Of course I can only speculate, but here's what I think it's going on.

By doing 

CEntity* gameEntity = new CEntity();

You're basically implying all entities are the same. Thus you're really pushing all the attributes, for al the derived classes to the base class, a well known, seldom justified anti-pattern. Obvious problem is for example: what m_pos value for a levelName property?

If "all the entities are the same" they should really be the same... internal attributes should be internal and code should work at a higher level than code.

Keep in mind there's a lot of difference between a texture, a light, a sound source and a player-moved model. The first three have very specific meanings to your system. The last? Not so much.

So maybe think at this separation between system objects and gameplay-oriented objects. Your system isn't really concerned with all the detail involving gameplay-oriented things.

What I did in the end is to take advantage of this, and I could figure out a way to (de)serialize all gameplay-oriented objects automatically.

You might also need to consider "component based systems".

Share this post


Link to post
Share on other sites
Thanks for the reply Krohm.  I am mostly designing a "component based system."  A lot of other objects can  be attached to this entity object (light, camera, meshes, paths, etc.)
 

You're basically implying all entities are the same.


What I posted was actually a simplification of what I actually do. There are actually many different type of objects and I allocate the correct one based on the class specified in the descriptor. So what actually done is something more like the following (in the case of my path class):
CPath* path = NULL;

switch(pathDescriptor.GetClass())
{
case CDescriptorPath::ELIPSE:
    path = new CPathElipse();
    break;
case CDescriptorPath::SMOOTH_SPLINE:
    path = new CPathSmoothSpline();
    break;
case CDescriptorPath::USER_CONTROLLED:
     path = new CPathUserControlled();
     break;
    ...
}
In my system, it actually takes more serialization code to support many classes like this.
 
I think I'm just resigned to the belief that serialization sucks.   It tedious to keep it tidy. Edited by DANNER

Share this post


Link to post
Share on other sites

Yes, I agree.

We can get the rid of the switch by turning it in a loop (ab?)using factory objects, but we still have to register them. Preprocessor macros have a bitter taste of their own.

I have been using a relatively nice method based on templates... but it's on a non-performance path and I'd be careful in using this pattern for more critical code (as a matter of fact, I have transitioned to this method only in a very specific case). The produced implementation has no branches (they are all hidden) but at the end of the day, we still need to register the various signatures by hand, albeit they look much nicer.

Share this post


Link to post
Share on other sites

We can get the rid of the switch by turning it in a loop (ab?)using factory objects, but we still have to register them.

 
I'm curious what you're talking about here.and how to get rid of the switch.
 

...but it's on a non-performance path...

 

All this serialization is in what I consider a non-critical path. This would all be run before entering the game work (during the level load) and I'm probably bounded by file I/O anyways.  So any pattern to reduce code (even at the expense of some perf) is probably ok.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this