C ++ Как сделать unordered_map с подклассами - PullRequest
0 голосов
/ 28 января 2020

Я создаю игровой движок и в настоящее время я работаю над Entity Component System. Вот некоторый код для лучшего понимания:

class Entity {
    public:
        ...
        void AddComponent(EntityComponent component, unsigned int id){
        m_Components.insert({id, component});}

        EntityComponent GetComponent(unsigned int id)
        {
        auto i = m_Components.find(id);
        ASSERT(i != m_Components->end()); // ASSERT is a custom macro
        return i->second;
        }

    private:
        ...
        std::unordered_map<unsigned int, EntityComponent> m_Components;
    };

Также простой родительский класс EntityComponent , который работает только с идентификаторами (я пропустил некоторый код, потому что в этой проблеме это не имеет значения) ):

class EntityComponent {
    public:
        ...

    private:

        unsigned int m_EntityID;
        size_t m_PoolIndex;
    };

И это дети. Одним из них будет TransformComponent :

class TransformComponent : public EntityComponent {

    public:

        TransformComponent(unsigned int entityID, unsigned int poolID = 0, glm::vec2 position = glm::vec2(1.0,1.0), glm::vec2 scale = glm::vec2(1.0,1.0), float rotation = 0) : EntityComponent(entityID, poolID),m_Rotation(rotation), m_Scale(scale), m_Position(position){}

        inline glm::vec2 GetPosition() { return m_Position; }
        inline glm::vec2 GetScale() { return m_Scale; }

    private:

        glm::vec2 m_Position;
        float m_Rotation;
        glm::vec2 m_Scale;

    };

Так что проблема в том, что я хочу создать Entity, добавить к нему TransformComponent и использовать его функции GetPosition (), GetScale ( ).

Создание сущности и добавление TransformComponent работает, но когда я хочу использовать GetComponent (id), он возвращает родительский класс EntityComponent , поэтому это означает, что я не могу использовать такие функции, как GetPosition ( ) et c.

Как я могу изменить код, чтобы я мог добавить различных потомков EntityComponent к unordered_map и получать их, используя их ID и использовать их методы publi c

1 Ответ

1 голос
/ 28 января 2020

Как упоминалось выше, первая проблема заключается в том, что вы должны хранить EntityComponent * или умный указатель EntityComponent, чтобы сохранить данные производного объекта от стирания при назначении базовому классу. Таким образом, тип контейнера может быть одним из:

std::unordered_map<int, EntityComponent*>
std::unordered_map<int, std::unique_ptr<EntityComponent>>
std::unordered_map<int, std::shared_ptr<EntityComponent>> 

Кроме того, виртуальный деструктор должен быть добавлен в EntityComponent для правильного удаления производных классов:

class EntityComponent
{
    virtual ~EntityComponent() = default;
    // other stuff ...     
};

Когда вы можете использовать dynamic_cast для проверьте, действительно ли EntityComponent является TransformComponent:

EntityComponent* ec = GetComponentById(id); // suppose that function returns TransformComponent or EntityComponent
TransformComponent* tc = dynamic_cast<TransformComponent*>(ec);
if (tc != nullptr)
{
    std::cout << "tc is transform component" << std::endl;
    tc->GetPosition();
    tc->GetScale();
}

Но, вероятно, для игрового движка dynamic_cast может быть медленным в некоторых случаях. Если вы знаете все ваши классы, производные от EntityComponent, вы можете добавить поле Тип, чтобы проверить фактический тип объекта, а затем привести его к нему с помощью static_cast, который работает во время компиляции:

enum class ComponentType
{
    BASE, 
    TRANSFORM,
    // other types
};

class EntityComponent
{
    ComponentType _type;
public:
    inline ComponentType GetType() const { return this->_type; }

    inline EntityComponent(ComponentType type = ComponentType::BASE) : _type(type) { }
    virtual ~EntityComponent() = default;
    // other staff
};

class TransformComponent : public EntityComponent
{
public:
    inline TransformComponent() : EntityComponent(ComponentType::TRANSFORM) { }
    // other staff
};

int main()
{
    EntityComponent* ec = GetComponentById(0);
    if (ec->GetType() == ComponentType::TRANSFORM)
    {
        std::cout << "ec is transform component" << std::endl;
        TransformComponent* tc = static_cast<TransformComponent*>(ec);
        tc->GetPosition();
        tc->GetScale();
    }
}

Существует несколько способов добавить TransformComponent для сопоставления как std::unique_ptr<EntityComponent>:

std::unordered_map<int, std::unique_ptr<EntityComponent>> components;

// emplace raw pointer
int id = 0;
components.emplace(id, new TransformComponent());

// release unique_ptr of derived class and emplace
auto tc = std::make_unique<TransformComponent>();
components.emplace(id, tc.release());

// create unique_ptr of base class and move it to map
std::unique_ptr<EntityComponent> ec(new TransformComponent());
components.insert(id, std::move(ec));
...