提问者:小点点

C++中如何将字符串文本映射到类型


我正在编写一个小型2D游戏,目前正在为其添加脚本功能(使用Lua或Python),而我在这个问题上磕磕绊绊(我认为这将导致我为我的游戏实现某种反射系统):

我使用的是Entity Component System模式,实体的定义是由脚本(Lua表或Python dict)提供的,因此每当我想构造实体时,我都会运行该脚本:

player = {
     transformComponent = { 
            position = {1.0, 2.0, 0.0},
            scale = {1.0, 2.0, 1.0}
     },
     spriteComponent = {
            fileName = 'imageFile.png',
            numRows = 4,
            numCols = 6
     }
}

等等。在EntityFactory中,我有一个EntityFactoryFunctions的映射,由实体的名称(例如'Player')键控,当我需要构造这样的命名实体时,我调用它们。

现在,每个工厂函数将读取实体的表(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;
}

如何获取要传递给函数模板的组件的名称?我需要某种反射系统吗?


共1个答案

匿名用户

我能想出几种办法来解决你的问题。

  1. 不同类型的组件并不是不同的C++类型。

在这种情况下,您的组件只是属性的束。代码会查看您拥有哪些包,并以不同的方式进行操作。

在这里,脚本命名了各种组件。这些是C++类型。组件名称和类型之间的映射存储在C++中。这种关联可能就像一两条硬编码的switch语句一样简单。

为了添加更多的组件类型,您可以加载另一个动态库,该库注册新的组件类型以及组件名称和类型之间的关联。

例如,您提供的C++编译器可以动态地构建组件类型并动态加载它们。或者,您编写自己的语言,而您的C++代码实际上只是一个解释器。

我会排除4号。

现在,在第一种情况下,你没有事可做。

在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)具有许多优点,如扩展能力和单元测试元组本身的能力。

(b)的优点是,一旦设置好,您只需编写代码,在需要的地方执行该操作。它确实需要C++17或C++14和boost来获得一个好的variant和lambda语法。

相关问题


MySQL Query : SELECT * FROM v9_ask_question WHERE 1=1 AND question regexp '(c++|中|如何将|字符串|文本|映|射到|类型)' ORDER BY qid DESC LIMIT 20
MySQL Error : Got error 'repetition-operator operand invalid' from regexp
MySQL Errno : 1139
Message : Got error 'repetition-operator operand invalid' from regexp
Need Help?