Я пытаюсь внедрить простой ECS для моего игрового движка.Я знаю, что моя реализация не строго ECS, но я рефакторинг моего кода, чтобы быть более основанным на компонентах.Пока у меня есть следующие классы:
Entity
: это контейнер компонентов, и, поскольку я хочу, чтобы моя сущность имела несколько компонентов одного типа, она сохраняет их в std::map<ComponentID,std::vector<std::unique_ptr<Component>>>
.Каждый компонент имеет уникальный идентификатор (целое число без знака), полученный из простого трюка с шаблоном, который я узнал в Интернете:
Функция GetUniqueComponentID:
using ComponentID = unsigned int;
inline ComponentID GetUniqueComponentID()
{
static ComponentID id = 0;
return id++;
}
содержит счетчик, которыйпросто генерирует увеличивающиеся числа.Я вызываю эту функцию из шаблона функции GetComponentID:
template <typename T>
ComponentID GetComponentID()
{
static ComponentID id = GetUniqueComponentID();
return id;
}
этот шаблон создает отдельную функцию для каждого компонента, который я добавляю в свою сущность, поэтому код, который должен получить компонент, может индексировать карту, используя GetComponentId<Component_type>
, с конкретным типом компонента в качестве аргумента шаблона для функции.
В классе сущности есть методы, такие как AddComponent и GetComponent, которые соответственно создают компонент и добавляют его к сущности, а также извлекают компонент (если имеется)):
class Entity
{
public:
Entity();
~Entity();
template <typename T, typename... TArgs>
T &AddComponent(TArgs&&... args);
template <typename T>
bool HasComponent();
//template <typename T>
//T &GetComponent();
template <typename T>
std::vector<T*> GetComponents();
bool IsAlive() { return mIsAlive; }
void Destroy() { mIsAlive = false; }
private:
//std::map<ComponentID, std::unique_ptr<Component>> mComponents; // single component per type
std::map<ComponentID, std::vector<std::unique_ptr<Component>>> mComponents; // multiple components per type
bool mIsAlive = true;
};
template <typename T, typename... TArgs>
T &Entity::AddComponent(TArgs&&... args)
{
T *c = new T(std::forward<TArgs>(args)...);
std::unique_ptr<Component> component(c);
component->SetEntity(this);
mComponents[GetComponentID<T>()].push_back(std::move(component));
return *c;
}
template <typename T>
bool Entity::HasComponent() // use bitset (faster)
{
std::map<ComponentID, std::vector<std::unique_ptr<Component>>>::iterator it = mComponents.find(GetComponentID<T>());
if (it != mComponents.end())
return true;
return false;
}
template <typename T>
std::vector<T*> Entity::GetComponents()
{
std::vector<T*> components;
for (std::unique_ptr<Component> &component : mComponents[GetComponentID<T>()])
components.push_back(static_cast<T*>(component.get()));
return components;
}
Поскольку я хочу хранить несколько компонентов одного типа, я храню их в std::map<ComponentID,std::vector<std::unique_ptr<Component>>>
.
Теперь мой вопрос:
Iнеобходимо создать иерархию компонентов для типа компонента: у меня есть компонент ForceGenerator, который является (абстрактным) базовым классом для всех видов конкретных ForceGenerator (Springs, Gravity и т. д.).Поэтому мне нужно создавать конкретные компоненты, но мне нужно использовать их полиморфно через указатель на базовый класс: моя физическая подсистема должна иметь дело только с указателями на базовый ForceGenerator, вызывая его метод Update (), который заботится об обновлении сил.
Я не могу использовать текущий подход, так как я вызываю AddComponent с другим типом каждый раз, когда создаю определенный компонент ForceGenerator, в то время как мне нужно хранить их в одном массиве (сопоставленном с идентификатором компонентабазовый ForceGenerator).
Как я мог решить эту проблему?