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!