如何使用 tinyxml2 从 XML 加载 parent 和 child 实体?
How to load parent and child entities from XML using tinyxml2?
我正在使用 XML 来存储我正在开发的游戏引擎的关卡文件。我最近添加了实体 parenting,其中每个实体都有一个指向它 children 的指针向量,以及一个指向它 parent 的指针。每个实体都有一个方法,该方法采用一个指针来添加一个 parent,该方法还将它添加到 parent 的 children 向量中。还有一种添加 child 的方法,它做同样的事情,但相反。问题:我不知道如何从 XML 加载带有 children 的实体。这是我想要的典型场景文件的样子(我已经有了加载实体及其组件的代码,我只是不确定如何加载 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>
所以基本上我需要做的是遍历所有实体并使用我的实体管理器的 createEntity
方法(returns 指向新实体的指针)创建它们,检查它们是否有children 在 XML 中,然后如果他们调用 parent 实体的 addChild
方法并指向 child 或当前实体的 addParent
方法和指向 parent 实体的指针。我猜我需要使用递归函数,但我该如何编写这样的函数?
这是使用递归的经典任务类型。递归本身可能很棘手,但在处理 tinyxml 时尤其如此 - 不是最友好的 API.
第 1 步:find
助手
让它变得更友好。在所有级别,我们都希望访问所有“实体”元素。让我们做一个方便的助手来使用 TinyXML 来获取这些:
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;
}
到目前为止一切顺利,现在我们可以更轻松地查询具有特定名称的节点。
第 2 步:解析单个实体
让我们暂时忘掉那个助手,假设我们已经选择了一个“实体”节点。现在我们要将它解析成一个实体(名称、精灵等)和return一个新创建的实体:
Entity* parse_entity(TiXmlElement const* node) {
Entity* entity = g_manager.createEntity(node->Attribute("name"));
// todo transforms, sprite info etc.
return entity;
}
第 3 步:构建树
这听起来很复杂。但实际上,使用上面的 find
助手,我们只需要:
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);
}
}
使用第 2 步中的 parse_entity
解析所有子节点,并且仅当我们有父节点时才添加关系。
为了完成这一切,我们递归到子实体,这次将当前实体作为父实体传递。
完整演示
**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";
}
}
打印:
Manager has 2 entities
==== Entity: "Parent Entity"
- has child "Child Entity"
==== Entity: "Child Entity"
- has parent "Parent Entity"
¹ 我知道的任何在线编译器上都没有安装 tinyxml
我正在使用 XML 来存储我正在开发的游戏引擎的关卡文件。我最近添加了实体 parenting,其中每个实体都有一个指向它 children 的指针向量,以及一个指向它 parent 的指针。每个实体都有一个方法,该方法采用一个指针来添加一个 parent,该方法还将它添加到 parent 的 children 向量中。还有一种添加 child 的方法,它做同样的事情,但相反。问题:我不知道如何从 XML 加载带有 children 的实体。这是我想要的典型场景文件的样子(我已经有了加载实体及其组件的代码,我只是不确定如何加载 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>
所以基本上我需要做的是遍历所有实体并使用我的实体管理器的 createEntity
方法(returns 指向新实体的指针)创建它们,检查它们是否有children 在 XML 中,然后如果他们调用 parent 实体的 addChild
方法并指向 child 或当前实体的 addParent
方法和指向 parent 实体的指针。我猜我需要使用递归函数,但我该如何编写这样的函数?
这是使用递归的经典任务类型。递归本身可能很棘手,但在处理 tinyxml 时尤其如此 - 不是最友好的 API.
第 1 步:find
助手
让它变得更友好。在所有级别,我们都希望访问所有“实体”元素。让我们做一个方便的助手来使用 TinyXML 来获取这些:
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;
}
到目前为止一切顺利,现在我们可以更轻松地查询具有特定名称的节点。
第 2 步:解析单个实体
让我们暂时忘掉那个助手,假设我们已经选择了一个“实体”节点。现在我们要将它解析成一个实体(名称、精灵等)和return一个新创建的实体:
Entity* parse_entity(TiXmlElement const* node) {
Entity* entity = g_manager.createEntity(node->Attribute("name"));
// todo transforms, sprite info etc.
return entity;
}
第 3 步:构建树
这听起来很复杂。但实际上,使用上面的 find
助手,我们只需要:
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);
}
}
使用第 2 步中的 parse_entity
解析所有子节点,并且仅当我们有父节点时才添加关系。
为了完成这一切,我们递归到子实体,这次将当前实体作为父实体传递。
完整演示
**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";
}
}
打印:
Manager has 2 entities
==== Entity: "Parent Entity"
- has child "Child Entity"
==== Entity: "Child Entity"
- has parent "Parent Entity"
¹ 我知道的任何在线编译器上都没有安装 tinyxml