Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    64
  • comments
    28
  • views
    51372

Component-based objects and XML loading

Sign in to follow this  
Trefall

452 views

The last two days I've been doing some experimentation with the component system and object definition. The component-system lends itself very well to the data-driven model. I've always known this to be true, but I've never actually sat down and coded in support for it, until now.

So, what I first did, was to write two dummy objects in xml:

Tower.xml


Tower

Health



Health
100


MaxHealth
100






RailgunTower.xml


RailgunTower
Tower

Railgun



MaxHealth
150


DamageType
Bullet


BaseDamage
4


Accuracy
10


RateOfFire
80






So, an object definition involves defining the object's name, optionally which object it inherits from, and a list of components and property settings. This is all that's needed to define new objects entierly in xml data.

There's two stages then, to using this. First one needs to load each object definition and save the data in a structure, so that we don't need to access the xml file each time we want to make an instance of it. The second stage is on instanciation, where we make a new instance of the base object, and based on the ID, we add the list of components and property settings already loaded into our data structure.

registerObject

void ObjectFactory::registerObject(const char *fileName)
{
if(creators == 0)
creators = new std::map();

//Check if file has already been loaded, though fileName is registered as
//second value in the map, because accessing the first value is faster, and
//we need that speed in the run-time create() function more than we need that
//speed at register initialization.
std::map::iterator it = creators->begin();
for(; it != creators->end(); ++it)
{
if((*it).second == fileName)
{
return;
}
}

CL_String name = loadObject(fileName).c_str();

std::pair value(name, fileName);
creators->insert(value);
}



loadObject

CL_String ObjectFactory::loadObject(const char *fileName)
{
Common::Resource::IResource *res = manager->getResMgr()->create(cl_format("%1/%2", "Game/Objects", fileName).c_str(), "XML");

CL_String name = res->getString("Object/Name");
CL_String inherit;
try
{
inherit = res->getString("Object/Inherits");
}
catch(const CL_Exception &)
{
inherit = CL_String();
}

//If this object inherits from another, make sure that the
//parent object is already loaded, if not, load it before
//we continue
bool inheritSupported = false;
if(inherit != CL_String())
{
std::map::iterator creatorIt = creators->find(inherit);
if(creatorIt == creators->end())
{
registerObject(cl_format("%1%2", inherit, ".xml").c_str());
creatorIt = creators->find(inherit);
if(creatorIt != creators->end())
{
inheritSupported = true;
}
}
else
{
inheritSupported = true;
}
}

loadComponents(res, name, inherit, inheritSupported);
loadProperties(res, name, inherit, inheritSupported);

if(inheritSupported)
std::cout << "Object: " << name.c_str() << " : public " << inherit.c_str() << std::endl;
else
std::cout << "Object: " << name.c_str() << std::endl;

return name;
}



loadComponents

void ObjectFactory::loadComponents(Common::Resource::IResource *res, const CL_String &name, const CL_String &inherit, bool inheritSupported)
{
std::vector compTypes;

//First add inherited components
if(inheritSupported)
{
std::map>::iterator inheritIt = object_components.find(inherit);
if(inheritIt != object_components.end())
{
std::vector inheritCompTypes = inheritIt->second;
for(unsigned int i = 0; i < inheritCompTypes.size(); i++)
{
compTypes.push_back(inheritCompTypes);
}
}
}

//Then add unique components
Common::Resource::CL_XMLResource *cl_res = static_cast(res);
std::vector components = cl_res->getDoc().select_nodes("/Object/Components/Component");
for(unsigned int i = 0; i < components.size(); i++)
{
CL_DomElement compType = components.to_element();

int alreadyExist = -1;
for(unsigned int j = 0; j < compTypes.size(); j++)
{
if(compTypes[j] == compType.get_text())
{
alreadyExist = j;
break;
}
}

if(alreadyExist == -1)
compTypes.push_back(compType.get_text());
}
object_components[name] = compTypes;
}



loadProperties

void ObjectFactory::loadProperties(Common::Resource::IResource *res, const CL_String &name, const CL_String &inherit, bool inheritSupported)
{
std::vector> propTypes;

//First add inherited properties
if(inheritSupported)
{
std::map>>::iterator inheritIt = object_properties.find(inherit);
if(inheritIt != object_properties.end())
{
std::vector> inheritPropTypes = inheritIt->second;
for(unsigned int i = 0; i < inheritPropTypes.size(); i++)
{
propTypes.push_back(inheritPropTypes);
}
}
}

//Then add unique properties
Common::Resource::CL_XMLResource *cl_res = static_cast(res);
std::vector properties = cl_res->getDoc().select_nodes("/Object/Properties/Property");
for(unsigned int i = 0; i < properties.size(); i++)
{
CL_DomElement propType = properties.to_element();
CL_String propName = propType.get_child_string("Name");
CL_String propValue = propType.get_child_string("Value");

int alreadyExist = -1;
for(unsigned int j = 0; j < propTypes.size(); j++)
{
if(propTypes[j].first == propName)
{
alreadyExist = j;
break;
}
}

//If property already exist, then the last property value will always win
if(alreadyExist == -1)
propTypes.push_back(std::pair(propName, propValue));
else
propTypes[alreadyExist] = std::pair(propName, propValue);
}
object_properties[name] = propTypes;
}



That's the code used to register a new object defined in XML. Notice how we at inheritance make sure that the parten object already is loaded before continuing to extract the data, so that we don't access the XML files more often than requried.

Then for instanciation:

Somewhere we define that we wish to instanciate Tower and RailgunTower objects

Engine::Object::IObject *tower = objMgr->create("Tower");
Engine::Object::IObject *railgun_tower = objMgr->create("RailgunTower");



create

IObject *ObjectFactory::create(const char *name)
{
if(creators == 0)
throw CL_Exception("ObjectCreator map has not been instanciated!");

std::map::iterator creatorIt = creators->find(name);
if(creatorIt == creators->end())
throw CL_Exception(cl_format("%1 %2", "Unable to create object of type", name));

IObject *go = new IObject(manager, manager->getComponentFactory());

std::cout << "- Adding Components" << std::endl;
int fail = addComponents(go, name);
if(fail)
{
std::cout << "- Failed adding Components" << std::endl;
delete go;
go = NULL;
return NULL;
}
std::cout << "- Finished adding Components" << std::endl;

std::cout << "- Setting Properties" << std::endl;
fail = addProperties(go, name);
if(fail)
{
std::cout << "- Failed setting Properties" << std::endl;
delete go;
go = NULL;
return NULL;
}
std::cout << "- Finished setting Properties" << std::endl;

return go;
}



addComponents

int ObjectFactory::addComponents(IObject *go, const CL_String &name)
{
std::map>::iterator compIt = object_components.find(name);
if(compIt == object_components.end())
{
return 1;
}

std::vector compTypes = compIt->second;
for(unsigned int i = 0; i < compTypes.size(); i++)
{
try
{
go->AddComponent(compTypes);
std::cout << "-- " << compTypes.c_str() << std::endl;
}
catch(const CL_Exception &e)
{
std::cout << "Failed to add component to Object of type " << name.c_str() << "\n" << e.what() << std::endl;
}
}

return 0;
}



addProperties

int ObjectFactory::addProperties(IObject *go, const CL_String &name)
{
std::map>>::iterator propIt = object_properties.find(name);
if(propIt == object_properties.end())
{
return 1;
}

std::vector> propTypes = propIt->second;
for(unsigned int i = 0; i < propTypes.size(); i++)
{
CL_String name = propTypes.first;
if(!go->HasProperty(name))
continue;

CL_String value = propTypes.second;
Entity::IProperty *propInterface = go->GetIProperty(name);
propInterface->SetFromString(value);
std::cout << "-- " << name.c_str() << ": " << value.c_str() << std::endl;
}

return 0;
}



And that's it. Here's the Output I get from my test code:

Loading Objects from XML
Object: Tower
Object: RailgunTower : public Tower
Finished loading Objects from XML
Initializing Game Logic
Adding Tower object to Scene
- Adding Components
-- Health
- Finished adding Components
- Setting Properties
-- Health: 100
-- MaxHealth: 100
- Finished setting Properties
Finished adding Tower object to Scene
Adding RailgunTower object to Scene
- Adding Components
-- Health
-- Railgun
- Finished adding Components
- Setting Properties
-- Health: 100
-- MaxHealth: 150
-- DamageType: Bullet
-- BaseDamage: 4
-- Accuracy: 10
-- RateOfFire: 80
- Finished setting Properties
Finished adding RailgunTower object to Scene
Finished initializing Game Logic



Next up is to add in Lua scripting of components, so that new components can be defined entierly in script.

The goal is that I should be able to run my application, and then start to define/edit objects and define/edit component logic, and just hotload it while the application is running. One thing I learned from the Tower Defense project, was that the last couple weeks, I probably spent 75% or more of my time just loading the game! That's about to change!
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

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
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!