Я пишу шутер (як, наприклад, 1942, класична двовимірна графіка), і я хотів би використовувати підхід на основі компонентів. Поки я думав про наступний дизайн:
Кожен ігровий елемент (дирижабль, снаряд, боєприпас, ворог) - це Сутність
Кожна організація - це набір компонентів, які можна додавати або видаляти під час виконання. Приклади: положення, спрайт, здоров'я, IA, пошкодження, BoundingBox тощо.
Ідея полягає в тому, що Airship, Projectile, Enemy, Powerup НЕ ігрові класи. Суб'єкт господарювання визначається лише компонентами, якими він володіє (і які можуть змінюватися протягом часу). Отже, дирижабль гравця починається з компонентів Sprite, Position, Health та Input. Блок живлення має 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).
Основний цикл не дуже переймається сутностями: він управляє лише компонентами. Дизайн програмного забезпечення повинен дозволяти:
Давши компонент, отримайте сутність, якій він належить
Давши сутність, отримайте компонент типу "type"
Для всіх організацій щось зробіть
Для всіх компонентів об'єкта зробіть щось (наприклад: серіалізувати)
Я думав про наступне:
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: кожен тип компонента живе у своєму списку. Отже, є список спрайтів, список здоров'я, список пошкоджень тощо. Я знаю, що вони належать до тієї ж логічної сутності через ідентифікатор сутності. Це простіше, але повільніше: компонентам ІА потрібен доступ в основному до всіх інших компонентів сутності, і це потребує пошуку списку кожного іншого компонента на кожному кроці.
Який підхід, на вашу думку, є кращим? карта boost :: fusion підходить для використання таким чином?