[英]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 模板的組件名稱? 我需要某種反射系統嗎?
我可以想到一些解決您問題的方法。
在這種情況下,您的組件只是屬性包。 代碼會查看您擁有哪些捆綁包並且行為不同。
在這里,腳本命名各種組件。 這些是 C++ 類型。 組件名稱和類型之間的映射存儲在 C++ 中。 關聯可能像一個或兩個硬編碼的 switch 語句一樣簡單。
為了添加更多的組件類型,您可以加載另一個動態庫,該庫注冊新的組件類型以及組件名稱和類型之間的關聯。
就像,您發布 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) 的優點是一旦設置好,您只需編寫代碼來完成您需要完成的事情。 它確實需要c++17或c++14和boost以獲得良好的變體和 lambda 語法才能使用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.