如何解决如何将字符串文字映射到 C++
我正在编写一个小型 2D 游戏,目前正在为其添加脚本功能(使用 Lua 或 Python),我偶然发现了这个问题(我认为这将引导我为我的游戏):
我使用实体组件系统模式,实体的定义由脚本(Lua 表或 Python 字典)提供,因此每当我想构建实体时,我都会运行脚本:
player = {
transformComponent = {
position = {1.0,2.0,0.0},scale = {1.0,1.0}
},spriteComponent = {
fileName = 'imageFile.png',numRows = 4,numCols = 6
}
}
等等。 在 EntityFactory 中,我有一个 EntityFactoryFunctions 的映射,以实体的名称(例如“玩家”)为键,当我需要构造这样的命名实体时,我会调用它们。
现在,每个工厂函数将读取实体的表(dict)并获取它需要添加到实体的所有组件的名称。
Entity *CreateEntity(const std::string entityType) // table / dictionary name in script
{
Entity *newEntity = Scene::GetInstance().AddEntity();
return mEntityFactories[entityType](newEntity);
}
typedef Entity *(*EntityFactoryFunction)(Entity*);
std::map<std::string,EntityFactoryFunction> mEntityFactories;
问题是,我的 ECS 使用类型为 enity.AddComponent
Entity *PlayerFactory(Entity *entity)
{
// read components from Lua table / Python dictionary
// get strings of components' names and store them into vector
Vector<std::string> componentNames;
// create components and add to entity
for (const auto &componentName : componentNames)
{
Component *component = mComponentFactories[componentName](/* pass a reference to component table / dictionary */);
entity->AddComponent<......>(component); // I must kNow the component type
}
return entity;
}
解决方法
我可以想到一些解决您问题的方法。
- 不同类型的组件不是不同的 C++ 类型。
在这种情况下,您的组件只是一组属性。代码会查看您拥有哪些包,并且行为会有所不同。
- 您在 C++ 中有一组固定的组件类型。
在这里,脚本命名了各种组件。这些是 C++ 类型。组件名称和类型之间的映射存储在 C++ 中。关联可能就像一两个硬编码的 switch 语句一样简单。
- 您拥有 C++ 中组件类型的动态 st。
为了添加更多的组件类型,您可以加载另一个动态库,它注册新的组件类型以及组件名称和类型之间的关联。
- 更疯狂的事情。
例如,您发布了动态构建组件类型并动态加载它们的 C++ 编译器。或者,您编写自己的语言,而您的 C++ 代码实际上只是一个解释器。
我会排除 #4。
现在,在第 1 种情况下,您无事可做。
在第 2/3 种情况下,您仍然需要将该字符串映射到类型。
最简单的基于 #2 的方法是一堆硬编码的 switch 语句,它们采用您的类型字符串并编写处理具体类型的自定义代码。这很快,但不能很好地扩展。这是解决方案(a)。
另一个步骤是抽象 switch 语句并让它在多个地方使用。调用此解决方案 (b)。
另一种选择是将整个类型视为一个对象本身;您编写一个描述类的元类,并构建从字符串到元类的映射。元类本身对于您的所有类都是相同的类型。调用此解决方案 (c)。
我认为 (a) 很简单,但很无聊。你真的做了一个
if (componentName=="bob") {
/* code assuming the type is Bob */
} else if (componentName=="blue") {
...
(b) 的一个例子:
template<class T>struct tag_t{using type=T;};
template<class Tag>using type_t = typename T::type;
template<class T>constexpr tag_t<T> tag={};
template<class...Ts>
using tags_t = std::variant<tag_t<Ts>...>;
namespace Components{
using ComponentTag = tags_t<Transform,Sprite,Physics>;
ComponentTag GetTagFromName(std::string_view str) {
if(str=="transformComponent") return tag<Transform>;
if(str=="spriteComponent") return tag<Sprite>;
// ...
}
}
现在我们得到:
// create components and add to entity
for (const auto &componentName : componentNames)
{
Component *component = mComponentFactories[componentName](/* pass a reference to component table / dictionary */);
auto tag = Components::GetTagFromName(componentName);
std::visit([&](auto tag) {
using Type = type_t<decltype(tag)>;
entity->AddComponent<Type>(component); // I must know the component type
},tag);
}
在最终版本 (c) 中,我们可以:
for (const auto &componentName : componentNames)
{
IMetaComponent* meta = mComponentMetaFactory[componentName];
Component *component = meta->Create(/* pass a reference to component table / dictionary */);
meta->Add(entity,component);
}
此处,IMetaComponent
为需要在需要知道类型的组件上执行的每个操作获取虚拟方法。
MetaComponent 实现本身可以使用模板编写 90% 以上的代码,但它有一个不是模板的基础 IMetaComponent
。
(c) 具有许多优点,例如扩展能力以及对 MetaComponent
本身进行单元测试的能力。
(b) 的优点是一旦设置,您只需编写代码来完成您需要完成的事情。它确实需要 c++17 或 c++14 和 boost 才能获得良好的变体和 lambda 语法。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。