Component-based objects and XML loading

Published June 05, 2010
Advertisement
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 XMLObject: TowerObject: RailgunTower : public TowerFinished loading Objects from XMLInitializing Game LogicAdding Tower object to Scene- Adding Components-- Health- Finished adding Components- Setting Properties-- Health: 100-- MaxHealth: 100- Finished setting PropertiesFinished adding Tower object to SceneAdding 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 PropertiesFinished adding RailgunTower object to SceneFinished 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!
Previous Entry The day after...
Next Entry Been a while...
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement