Я пишу шутер (например, 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 для такого использования?