Я пишу шутер (например, 1942, классическая 2D графика), и я хотел бы использовать подход, основанный на компонентах. До сих пор я думал о следующем дизайне:
Каждый элемент игры (дирижабль, снаряд, усиление, враг) является сущностью
Каждый объект представляет собой набор компонентов, которые можно добавлять или удалять во время выполнения. Примерами являются Позиция, Спрайт, Здоровье, IA, Урон, BoundingBox и т. Д.
Идея в том, что Airship, Projectile, Enemy, Powerup НЕ являются игровыми классами. Сущность определяется только теми компонентами, которыми она владеет (и которые могут изменяться с течением времени). Итак, игрок Дирижабль начинает с компонентов Sprite, Position, Health и Input. Powerup имеет Sprite, Position, BoundingBox. И так далее.
Основной цикл управляет игровой «физикой», то есть тем, как компоненты взаимодействуют друг с другом:
foreach(entity (let it be entity1) with a Damage component)
foreach(entity (let it be entity2) with a Health component)
if(the entity1.BoundingBox collides with entity2.BoundingBox)
{
entity2.Health.decrease(entity1.Damage.amount());
}
foreach(entity with a IA component)
entity.IA.update();
foreach(entity with a Sprite component)
draw(entity.Sprite.surface());
...
Компоненты жестко закодированы в основном приложении C ++. Сущности могут быть определены в файле XML (часть IA в файле lua или python).
Основной цикл не заботится о сущностях: он управляет только компонентами. Дизайн программного обеспечения должен позволять:
Получив компонент, получите сущность, к которой он принадлежит
Получив сущность, получите компонент типа «тип»
Для всех сущностей сделайте что-нибудь
Для всех компонентов сущности сделайте что-нибудь (например: serialize)
Я думал о следующем:
class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component {...};
class Position : public Component {...};
class IA : public Component {... virtual void update() = 0; };
// I don't remember exactly the boost::fusion map syntax right now, sorry.
class Entity
{
int id; // entity id
boost::fusion::map< pair<Sprite, Sprite*>, pair<Position, Position*> > components;
template <class C> bool has_component() { return components.at<C>() != 0; }
template <class C> C* get_component() { return components.at<C>(); }
template <class C> void add_component(C* c) { components.at<C>() = c; }
template <class C> void remove_component(C* c) { components.at<C>() = 0; }
void serialize(filestream, op) { /* Serialize all componets*/ }
...
};
std::list<Entity*> entity_list;
С этим дизайном я могу получить # 1, # 2, # 3 (благодаря алгоритмам boost :: fusion :: map) и # 4. Также все O (1) (хорошо, не совсем, но все еще очень быстро).
Существует также более «общий» подход:
class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component { static const int type_id = 0; };
class Position : public Component { static const int type_id = 1; };
class Entity
{
int id; // entity id
std::vector<Component*> components;
bool has_component() { return components[i] != 0; }
template <class C> C* get_component() { return dynamic_cast<C> components[C::id](); } // It's actually quite safe
...
};
Другой подход - избавиться от класса Entity: каждый тип Component живет в своем собственном списке. Итак, есть список Sprite, список работоспособности, список повреждений и т. Д. Я знаю, что они принадлежат одной и той же логической сущности из-за идентификатора сущности. Это проще, но медленнее: компонентам IA необходим доступ в основном ко всем другим компонентам объекта, и для этого потребуется поиск по списку каждого другого компонента на каждом шаге.
Какой подход ты думаешь лучше? подходит ли boost :: fusion map для такого использования?
источник
Ответы:
Я обнаружил, что компонентный дизайн и ориентированный на данные дизайн идут рука об руку. Вы говорите, что наличие однородных списков компонентов и исключение первоклассного объекта-сущности (вместо того, чтобы выбирать идентификатор сущности для самих компонентов) будет «медленнее», но это ни здесь, ни там, поскольку вы на самом деле не профилировали ни один настоящий код, который реализует оба подхода, чтобы прийти к такому выводу. Фактически, я могу почти гарантировать вам, что гомогенизация ваших компонентов и избежание традиционной тяжелой виртуализации будут быстрее благодаря различным преимуществам ориентированного на данные проектирования - более простое распараллеливание, использование кэша, модульность и т. Д.
Я не говорю, что этот подход идеален для всего, но системы компонентов, которые в основном представляют собой наборы данных, которые требуют одинаковых преобразований, выполняемых на каждом кадре, просто кричат, чтобы быть ориентированными на данные. Будут времена, когда компоненты должны будут взаимодействовать с другими компонентами разных типов, но в любом случае это будет неизбежным злом. Однако это не должно влиять на дизайн, поскольку существуют способы решения этой проблемы даже в крайнем случае, когда все компоненты обрабатываются параллельно, например, очереди сообщений и фьючерсы. .
Определенно, Google ищет ориентированный на данные дизайн, так как он относится к компонентным системам, потому что эта тема поднимается очень часто, и там довольно много дискуссий и анекдотических данных.
источник
если бы я написал такой код, я бы предпочел не использовать этот подход (и я не использую никакого повышения, если это важно для вас), поскольку он может делать все, что вы хотите, но проблема в том, что слишком много энтузиастов которые не разделяют некоторые компоненты, поиск тех, у которых они есть, займет некоторое время. кроме этого нет никакой другой проблемы, которую я могу вещь:
в этом подходе каждый компонент является базой для сущности, поэтому, если указатель компонента является также сущностью! Второе, о чем вы просите, - это иметь прямой доступ к компонентам некоторой сущности, например. когда мне нужно получить доступ к урону в одной из моих сущностей, которые я использую
dynamic_cast<damage*>(entity)->value
, поэтому, если уentity
него есть урон, он вернет значение. если вы не уверены,entity
имеет ли компонент повреждение или нет, вы можете легко проверить, чтоif (dynamic_cast<damage*> (entity))
возвращаемое значениеdynamic_cast
всегда NULL, если приведение неверно и тот же указатель, но с запрошенным типом, если он действителен. так что сделать что-то со всем,entities
что есть некоторые,component
вы можете сделать это, как показано нижеесли есть другие вопросы, я буду рад ответить.
источник
bool isActive
базовый компонентный класс. по-прежнему необходимо вводить используемые компоненты, когда вы определяете организации, но я не считаю это проблемой, и все же у вас есть отдельные обновления компонентов (вспомните что-то вродеdynamic_cast<componnet*>(entity)->update()
.