簡體   English   中英

如何將 map 字符串文字轉換為 C++ 中的類型

[英]How to map string literals to types in C++

我正在編寫一個小型 2D 游戲,我目前正在向它添加腳本功能(使用 Lua 或 Python),我偶然發現了這個問題(我認為這將導致我為我的游戲實現某種反射系統):

我正在使用實體組件系統模式,並且實體的定義由腳本(Lua 表或 Python 字典)提供,所以每當我想構建一個實體時,我都會運行腳本:

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 的 map,以實體的名稱(例如“播放器”)為鍵,當我需要構造這樣的命名實體時,我會調用它們。

現在,每個工廠 function 將讀取實體的表(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<COMPONENT_TYPE>() 類型的 function:

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;
}

如何獲取要傳遞給 function 模板的組件名稱? 我需要某種反射系統嗎?

我可以想到一些解決您問題的方法。

  1. 不同型號的元器件C++型號沒有區別。

在這種情況下,您的組件只是屬性包。 代碼會查看您擁有哪些捆綁包並且行為不同。

  1. 您在 C++ 中有一組固定的組件類型。

在這里,腳本命名各種組件。 這些是 C++ 類型。 組件名稱和類型之間的映射存儲在 C++ 中。 關聯可能像一個或兩個硬編碼的 switch 語句一樣簡單。

  1. 您在 C++ 中有一個動態的組件類型。

為了添加更多的組件類型,您可以加載另一個動態庫,該庫注冊新的組件類型以及組件名稱和類型之間的關聯。

  1. 更瘋狂的東西。

就像,您發布 C++ 編譯器,它可以動態構建組件類型並動態加載它們。 或者,您編寫自己的語言,您的 C++ 代碼實際上只是一個解釋器。


我會排除#4。

現在,在第 1 種情況下,您無事可做。

在第 2/3 號的情況下,您仍然需要將該字符串 map 轉換為類型。

最簡單的基於#2 的方法是一堆硬編碼的 switch 語句,它們接受你的類型字符串並編寫處理具體類型的自定義代碼。 這很快,但不能很好地擴展。 這是解決方案(a)。

另一個步驟是抽象 switch 語句並讓它在多個地方使用。 將此解決方案稱為 (b)。

另一種選擇是將整個類型視為 object 本身; 你編寫一個描述你的類是什么樣的元類,並從你的字符串到元類構建一個 map。 元類本身對於您的所有類都是相同的類型。 將此解決方案稱為 (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) 的優點是一旦設置好,您只需編寫代碼來完成您需要完成的事情。 它確實需要以獲得良好的變體和 lambda 語法才能使用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM