Search code examples
c++game-engine

How to load parent and child entities from XML using tinyxml2?


I'm using XML to store my level files for a game engine that I'm working on. I recently added entity parenting, where each entity has a vector of pointers to it's children, as well as a pointer to it's parent. Each entity has a method that takes a pointer to add a parent, which also adds it to the parent's vector of children. There is also a method that adds a child, which does the same thing but in reverse. Problem: I have no idea how to load entities with children from the XML. Here's what I want a typical scene file to look like (I already have code to load entities and their components, I'm just not sure how to load parenting)

<scene>
    <entity name="Parent Entity">
        <transform posx="500" posy="100" scalex="1" scaley="1" rotation="0"/>
        <sprite image="Assets/Sprites/Avatar2.png"/>

        <entity name="Child Entity">
            <transform posx="0" posy="0" scalex="1" scaley="1" rotation="0"/>
            <sprite image="crimson-logo.png"/>
        </entity>
    </entity>

</scene>

So essentially what I need to do is loop over all the entities and create them using my entity manager's createEntity method (which returns a pointer to the new entity), check if they have children in the XML, and then if they do then call the parent entity's addChild method with a pointer to the child, or the current entity's addParent method with a pointer to the parent entity. I'm guessing I need to use a recursive function, but how would I write such a function?


Solution

  • This is a classical type of task where you use recursion. Recursion can be tricky as it is, but especially so when dealing with tinyxml - not the friendliest API.

    STEP 1: A find Helper

    Let's make it friendlier. At all levels we will want to visit all "entity" elements. Let's make a handy helper to use TinyXML to get those:

    auto find(TiXmlElement const* node, char const* name) {
        std::vector<TiXmlElement const*> found;
        for (
                auto el = node->FirstChildElement(name);
                el;
                el = el->NextSiblingElement(name)
            )
        {
            found.push_back(el);
        }
        return found;
    }
    

    So far so good, now we can more easily query nodes with a certain name.

    STEP 2: Parse a Single Entity

    Let's forget about that helper for a second, and assume we already have an "entity" node selected. Now we want to parse it into an Entity (name, sprite, etc.) and return a newly created entity:

    Entity* parse_entity(TiXmlElement const* node) {
        Entity* entity = g_manager.createEntity(node->Attribute("name"));
        // todo transforms, sprite info etc.
        return entity;
    }
    

    STEP 3: Build The Tree

    That sounds like the complicated part. But really, using our find helper from above, all we need is this:

    void parse_sub_entities(TiXmlElement const* node, Entity* parent = nullptr) {
        for (auto el : find(node, "entity")) {
            auto entity = parse_entity(el);
    
            if (parent && entity) {
                entity->addParent(parent);
                parent->addChild(entity);
            }
    
            parse_sub_entities(el, entity);
        }
    }
    

    All child nodes are parsed using parse_entity from Step #2, and only if we had a parent node we add the relationships.

    To finish it all up, we recurse into child entities, this time passing the current entity as the parent.

    Full Demo

    **Not Live On Coliru**¹

    #include <tinyxml.h>
    #include <vector>
    #include <list>
    #include <iostream>
    #include <iomanip>
    
    namespace { // helper functions for XML searching
        auto find(TiXmlElement const* node, char const* name) {
            std::vector<TiXmlElement const*> found;
            for (
                    auto el = node->FirstChildElement(name);
                    el;
                    el = el->NextSiblingElement(name)
                )
            {
                found.push_back(el);
            }
            return found;
        }
    }
    
    auto scene = R"(<scene>
        <entity name="Parent Entity">
            <transform posx="500" posy="100" scalex="1" scaley="1" rotation="0"/>
            <sprite image="Assets/Sprites/Avatar2.png"/>
    
            <entity name="Child Entity">
                <transform posx="0" posy="0" scalex="1" scaley="1" rotation="0"/>
                <sprite image="crimson-logo.png"/>
            </entity>
        </entity>
    
    </scene>)";
    
    struct Entity {
        std::string _name;
        Entity* _parent = nullptr;
        std::vector<Entity*> _children;
    
        explicit Entity(std::string name = "unnamed") : _name(std::move(name)) {}
    
        void addChild(Entity* e) { _children.push_back(e); }
        void addParent(Entity* e) {
            assert(!_parent || _parent == e);
            _parent = e;
        }
    };
    struct Mgr {
        std::list<Entity> _entities;
        Entity* createEntity(std::string name) {
            return &_entities.emplace_back(name);
        };
    } g_manager;
    
    Entity* parse_entity(TiXmlElement const* node) {
        Entity* entity = g_manager.createEntity(node->Attribute("name"));
        // todo transforms, sprite info etc.
        return entity;
    }
    
    void parse_sub_entities(TiXmlElement const* node, Entity* parent = nullptr) {
        for (auto el : find(node, "entity")) {
            auto entity = parse_entity(el);
    
            if (parent && entity) {
                entity->addParent(parent);
                parent->addChild(entity);
            }
    
            parse_sub_entities(el, entity);
        }
    }
    
    int main() {
        TiXmlDocument doc;
        doc.Parse(scene);
    
        parse_sub_entities(doc.RootElement());
    
        std::cout << "Manager has " << g_manager._entities.size() << " entities\n";
    
        for (auto& e: g_manager._entities) {
            std::cout << "==== Entity: " << std::quoted(e._name) << "\n";
            if (e._parent)
                std::cout << " - has parent " << std::quoted(e._parent->_name) << "\n";
            for (auto child : e._children)
                std::cout << " - has child " << std::quoted(child->_name) << "\n";
        }
    }
    

    Prints:

    Manager has 2 entities
    ==== Entity: "Parent Entity"
     - has child "Child Entity"
    ==== Entity: "Child Entity"
     - has parent "Parent Entity"
    

    ¹ no tinyxml installed on any online compiler I know