2017-09-01 52 views
1

您可能已經聽說過Entity Component System,其中一切都是Entity,並且每個實體都有一個控制其功能的列表Component如何將不同的對象存儲在數組中並獲取某種類型的對象?

我想了解如何將不同的對象(每個繼承Component)存儲在一個數組中,並能夠根據它們的類型從該數組中獲取對象。

我能想到的第一解決方案是具有用於對象類型enum繼承部件:

enum ComponentType : unsigned char // There will never be more than 256 components 
{ 
    EXAMPLE_COMPONENT, 
    ANOTHER_EXAMPLE_COMPONENT, 
    AND_ANOTHER_EXAMPLE_COMPONENT 
}; 

然後Component基類與吸氣劑一ComponentType type;,並且每個子組件設置其類型例如:

ExampleComponent::ExampleComponent() 
{ 
    type = EXAMPLE_COMPONENT; 
} 

然後我就會有一個GetComponent功能:

Component* Entity::GetComponent(ComponentType type) 
{ 
    for (unsigned int i = 0; i < m_components.size(); i++) 
    { 
     if (m_components.at(i).GetType() == type) 
     { 
      return &m_components.at(i); 
     } 
    } 

    return nullptr; 
} 

// Note: m_components is an std::vector; 

然後最後你會打電話GetComponent如:

(ExampleComponent*) component = entity.GetComponent(EXAMPLE_COMPONENT); 

這裏的問題是,你需要一個enum爲每種類型的組件,你也可以選擇使用GetComponent確保你後施放組件可以訪問自己的成員變量。

有沒有人知道在沒有必要使用enum的情況下進行此操作的正確方法,並且不需要投射組件?如果有一個解決方案仍然需要在每個組件中存儲一個類型變量,它最好是一個字節,不能大於4個字節。

編輯:我也不想提前使用模板

謝謝!

大衛

+0

我會用'std :: find_if'和一個lambda替換'Entity :: GetComponent'中的原始循環。我也將'NULL'的使用改爲'nullptr'並且擺脫了C風格的轉換'(ExampleComponent *)',並用適當的C++轉換替換它。也; 'void *'是討厭的 - 爲什麼不返回基類指針呢? –

+1

模板和'dynamic_cast'? – Jarod42

+2

對我來說,似乎你的'enum'模擬多態性。通過創建派生類來使用真正的多態。 –

回答

1

你的方法模擬多態性:具有type作爲成員和if語句中檢查該類型是典型的指示,利用一類層次的。你已經聲明你想使用從Component類型派生的對象,所以你也應該正確使用多態。

在你的方法的第二個問題是,您要過濾的「特定類型」,這或多或少相當於一個垂頭喪氣的 - 也就是說dynamic_cast<>():當你通過一定的ComponentTypeEntity::GetComponent(),它返回一個指針到Component,但該指針後面的對象始終是特定派生類的對象:在您的示例中,當您將EXAMPLE_COMPONENT傳遞給該函數時,總是會得到一個對象。

然後自然會出現以下問題:您想對此函數返回的對象做什麼?您只能調用Component接口/類中的方法,但不能派生類中的方法!所以沮喪的事情根本就沒有任何意義(如果你要返回一個指向從Component派生的類的對象的指針。

這裏是如何看起來像使用多態,並在getComponent()方法垂頭喪氣,返回一個指向派生類 - 請注意,該方法可以簡便地實現這種從Component派生的每個類別的模板:

#include <string> 
#include <vector> 
#include <iostream> 

class Component { 
public: 
    virtual std::string getType() = 0; 
}; 

using ComponentContainer = std::vector<Component*>; 

class AComponent : public Component { public: virtual std::string getType() { return "A"; }; }; 
class BComponent : public Component { public: virtual std::string getType() { return "B"; }; }; 
class CComponent : public Component { public: virtual std::string getType() { return "C"; }; }; 


class Entity { 
public: 
    template <typename T> 
    T* getComponent(); 

    void putComponent(Component* c) { m_components.push_back(c); } 

private: 
    ComponentContainer m_components; 
}; 


template<typename T> 
T* Entity::getComponent() 
{ 
    T* t = nullptr; 
    for (auto i : m_components) { 
     if ((t = dynamic_cast<T*>(i)) != nullptr) 
      break; 
    } 

    return t; 
} 

int main() 
{ 
    Entity e; 
    e.putComponent(new AComponent{}); 
    e.putComponent(new BComponent{}); 

    Component* c; 
    if ((c = e.getComponent<AComponent>()) != nullptr) 
     std::cout << c->getType() << std::endl; 

    // delete all the stuff 
    return 0; 
} 

dynamic_cast<>()的大量使用從性能和設計角度都存在問題:它應該只用於很少的情況,如果有的話。

所以設計問題可能是一切都存儲在一個容器?您可以改爲使用幾個容器,基於「行爲」。由於行爲在ECS中作爲派生類或接口實現,此實體的類似方法只會返回某些(子)接口的對象。這些組件將會全部實現一個給定的接口方法,所以對於下載的需求將被消除。

例如,假設你有「繪製組件」,這表明層次:

// Drawable interface 
class DrawableComponent : public Component { 
public: 
    virtual void draw() const = 0; 
}; 

// Drawable objects derive from DrawableComponent 
class DComponent : public DrawableComponent { 
public: 
    virtual void draw() const { /* draw the D component */ } 
}; 

然後,一個實體可以有DrawableComponent對象的容器,你只想遍歷這些對象,並呼籲draw()上每個:

using DrawableContainer = std::vector<DrawableComponent*>; 
// m_drawables is a memober of Entity with above type 
const DrawableContainer& Entity::getDrawables() { return m_drawables; } 

// then just draw those objects 
for (auto d : entity.getDrawables()) 
    d->draw(); // no downcast! 
+0

我絕對不會使用模板或'dynamic_cast'。這些組件不會有任何功能,但它們僅用於數據。我不會有任何繼承'DrawableComponent'的組件。它只是一些不同的組件,例如'DrawableComponent','TransformComponent','PhysicsComponent',它存儲着關於實體的不同數據。然後系統接納具有特定組件的實體並執行可能改變組件數據的功能。 –

相關問題