当前位置: 首页 > 知识库问答 >
问题:

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

郑燕七
2023-03-14

我正在编写一个小型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个答案

锺离玮
2023-03-14

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

  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语法。

 类似资料:
  • 我有下面的结构,我想用MapStruct映射这个。 下面是mapstruct为toDTO方法生成的实现 下面是mapstruct为toEntity方法生成的实现 我的问题是方法只在文本不为空时设置注释。但是方法不检查空文本或空文本。因此,如果我在DTO中获得,它将创建一个新的comment对象并将文本设置为null。如何避免这一点?有人能解释一下这种行为并建议我正确的做法吗?谢了!

  • 问题内容: 当前,我有一堆实现接口的Java类,这意味着它们都具有方法。这个想法是,每个类都有几个(例如<10)成员,并且每个类都通过方法映射到该类中的方法,如下所示: 你明白了。 这对我来说很好,但是现在我需要一个从键到函数的运行时可访问的映射。并非每个函数 实际上都 返回一个String(有些返回void),并且我需要动态地访问每个具有键的类中每个函数的返回类型(使用反射)。我已经有一位经理,

  • 嗨,我尝试将以下Source类映射到以下Destation类。我使用了以下映射以将字符串值映射到列表字符串。它没有正确映射。我需要知道如何使用Dozer将2个字符串值映射到一个目标字符串列表中。

  • 问题内容: 我有一个类别hibernate模型: 其中有一个类型字符串字段。我也有一个Java枚举,它表示类别的类型: 我想用它代替字符串类型。SQL在varchar参数中接受两个不同的值:或。我希望Category模型类接受一个枚举变量- 每当hibernate要求它时,都以某种方式将其映射到字符串。 可能吗? 问题答案: 是的,有可能。它应该是:

  • 我有一个类别Hibernate模型: 它们具有类型字符串字段。另外,我还有一个Java枚举,它表示类别的一种类型:

  • 问题内容: 当我用Java进行操作时,我在stdout中得到了很好的输出。在不干预标准输出的情况下,如何在变量中获得相同的a字符串表示形式?像什么? 问题答案: 使用。 所有的亦是在此之后是做头套下。地图的格式在中描述。 返回此映射的字符串表示形式。字符串表示形式由键值映射列表组成,这些键值映射由地图视图的迭代器返回,并用大括号(“ {}”)括起来。相邻的映射用字符“,”(逗号和空格)分隔。每个键