EntityManager - small nifty manager

Published September 29, 2016
Advertisement

While irrLicht got its own data serialization, I will use my own entity manager for that. It is super simple and very straightforward. For my game I want everything to be data driven, especially entity setup, loading and saving game. To store the data I will use plain xml and tinyXml to manipulate it.

The idea behind it is simple, you have a hierarchy of entities, each entity instantiates its children and their children instantiates its and so on. Same goes for save/load, entity loads/saves itself and then calls it children to do the same.

Before I go into details lets look at the data. First of all there are two type of files. First one is .entity.xml, which is a simple collection of entity descriptors. Data in these files is constant and cannot be changed by the game run-time (it just a rule, it actually can be with a simple fix, but I stick to the rule to keep everything clean). Then there is a save file, which is a hierarchy of entities from .entity.xm files and contains entities members that can be edited by game run-time.

Lets look at an example. Here is space_system.entity.xml:[code=xml:0] maxresdefault.jpg space.jpg


As you can see there is two entities with a single parameter "skybox". Skybox is constant and cannot be change during the run-time. Also there is "type" which is class ID, used for identification, and a "class" which is class name.


Here is a new game file, which is just like a safe file but all values at some default state:[code=xml:0] Main SA World SS1 SS1


Here you can actually the the hierarchy, Galaxy got System entities. This is a most basic example which I will use to get the blank system to load up for testing. All members can be changed, for example would change in save file if players moved to another system, or name can be change if player renames the system.


Now to the actual code. There three high level classes in entity system. First one is EntityDB, it loads all entity.xml files and keep them as a library for instantiating of game run-time entities. At the start of the run time it get a directory with entity.xml files and goes through each and one of them. Late GetDesc() function can be used to get descriptor of an entity.namespace GAME{ class EntityDB : public StaticSingleton, public Factory { public: // Entity DB bool LoadResources(const char* dirName); bool AddResource(const char* fileName); void FreeResources(); inline bool IsResourceLoaded(const StringId rcName) const { return m_entityRc.find(rcName)!=m_entityRc.end();} inline const XmlNode* GetDesc(uint32 typeId) const { EntityDescs::const_iterator it=m_entityDesc.find(typeId); return it!=m_entityDesc.end()?&((*it).second):NULL;} uint EnumDescs(std::vector &descs) const; EntityPtr Create(uint32 typeId, uint32 id); protected: typedef Factory EntityFactory; typedef std::map EntityDescs; typedef std::map EntityResources; EntityDescs m_entityDesc; EntityResources m_entityRc; // Utils EntityPtr Create(const char* name, uint32 id); public: struct EntityRegistrar { EntityRegistrar(const char* name, EntityFactory::CreateFunction func) { EntityDB::Ref().Add(name, func);} }; };}
Next class is Entity. That is where all the magic happens. In its Init() function it reads constant data from entity.xml. In its Load() function it read from the save file. There also a PostLoad() function, mainly to set up references to other entities after everything was loaded. In Activate() and Deactivate() function the visual state usually created and set.namespace GAME{ class Entity; typedef SharedPtr EntityPtr; class Entity { public: Entity():m_id(0),m_typeId(0) {} virtual ~Entity() {} uint32 GetId() const { return m_id;} uint32 GetTypeId() const { return m_typeId;} StringId GetClassId() const { return m_classId;} virtual bool Init(const XmlNode* desc) { m_desc = desc; return true;} virtual void Done(); virtual void Update(float dt); virtual bool Save(XmlNode& data); virtual bool Load(const XmlNode& data, bool newGame = false); virtual bool PostLoad(bool newGame = false); virtual void Activate(ISceneNode* parent); virtual void Deactivate(); virtual bool IsSerializable() { return true; } virtual EntityPtr GetChildById(uint32 id, bool recursively = true) const; protected: typedef std::vector Entities; uint32 m_id; uint32 m_typeId; StringId m_classId; const XmlNode* m_desc; Entities m_children; friend class EntityDB; }; }
Last class is EntityManager, which is the root node of all entities. It can be used to find any entity in hierarchy, to active, deactivate all entities, or simply clean them all up. Also a note, EntityManager only hold entities that need to be save or loaded. Entities without constant state, like for example projectiles, will be managed by different EntityManager.


Here is an Load() function from Entity class. As you can see, if the it is a new game, each entity will get new id, which then will be saved as its attributed. I will paste in whole Entity.cpp. Each function is pretty much a loop which goes through its children and calls same function on it!namespace GAME{ static StringId tagEntities("entities"); static StringId tagEntity("entity"); static StringId tagClass("class"); static StringId tagType("type"); static StringId tagId("id"); void Entity::Done() { for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { (*it)->Done(); } } void Entity::Update(float dt) { for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { (*it)->Update(dt); } } bool Entity::Save(XmlNode& data) { data.Attr(tagType) << m_typeId; data.Attr(tagClass) << m_classId; data.Attr(tagId) << m_id; if(m_children.size() > 0) { XmlNode childrenData = data(tagEntities); for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { if((*it)->IsSerializable()) { XmlNode node = childrenData(tagEntity); (*it)->Save(node); } } } return true; } bool Entity::Load(const XmlNode& data, bool newGame) { // load children const XmlNode& childrenData = data(tagEntities); for (const XmlNode node = childrenData(tagEntity);node;node.Next()) { uint32 typeId = 0, id = 0; node.Attr(tagType) >> typeId; if(newGame) { id = EntityManager::Ref().GenerateId(); } else node.Attr(tagId) >> id; EntityPtr e = EntityDB::Ref().Create(typeId, id); if(e) { const XmlNode* desc = EntityDB::Ref().GetDesc(typeId); if(desc) { if(e->Load(node,newGame)) { m_children.push_back(e); } else SLOGS_ERR(entities,("Cannot Load entity with type <%d> and id <%d>", typeId, id)); } else SLOGS_ERR(entities,("Cannot find entity desc with type <%d> and id <%d>", typeId, id)); } else SLOGS_ERR(entities,("Cannot create entity with type <%d> and id <%d>", typeId, id)); } return true; } bool Entity::PostLoad(bool newGame) { bool res = true; for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { res = res && (*it)->PostLoad(newGame); } return res; } void Entity::Activate(ISceneNode* parent) { for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { (*it)->Activate(parent); } } void Entity::Deactivate() { for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { (*it)->Deactivate(); } } EntityPtr Entity::GetChildById(uint32 id, bool recursively) const { for(Entities::const_iterator it=m_children.begin();it!=m_children.end();++it) if((*it)->GetId() == id) return *it; if(recursively) { for(Entities::const_iterator it=m_children.begin();it!=m_children.end();++it) { EntityPtr ch = (*it)->GetChildById(id, recursively); if(ch) return ch; } } return EntityPtr(); }}
Also in Entity you can see the way attributes are being read and written. A static member with attributed name is declared at the top of the class and then used to read or write into XmlNode, which got operators >> and << overloaded for most of the simple types. static StringId tagName("name"); static StringId tagActiveSys("active_system"); bool Galaxy::Save(XmlNode& data) { data(tagName) << m_name; data(tagActiveSys) << m_activeSys->GetId(); return Entity::Save(data); } bool Galaxy::Load(const XmlNode& data, bool newGame) { data(tagName) >> m_name; m_nodeSys = data(tagActiveSys); return Entity::Load(data, newGame); }
That is all for today. I hope all of that made at least a little bit of scene. But good news is that we are finally to the stage where different entities can be set up. I will start with a simple static entity which will load a mesh and place it somewhere in space. I will need to decide on scale of the voxel square as it will be a measurement of a single "pixel". Finally some exciting stuff!

1 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!
Profile
Author
Advertisement
Advertisement