Estou escrevendo um shooter (como 1942, gráficos 2D clássicos) e gostaria de usar uma abordagem baseada em componentes. Até agora, pensei no seguinte design:
Cada elemento do jogo (dirigível, projétil, powerup, inimigo) é uma Entidade
Cada entidade é um conjunto de componentes que podem ser adicionados ou removidos em tempo de execução. Exemplos são: Posição, Sprite, Saúde, IA, Dano, Caixa delimitadora etc.
A idéia é que Dirigível, Projétil, Inimigo, Powerup NÃO são classes de jogos. Uma entidade é definida apenas pelos componentes que possui (e que podem mudar com o tempo). Assim, o dirigível do jogador começa com os componentes Sprite, Position, Health e Input. Uma ligação inicial possui a Sprite, a Posição, a BoundingBox. E assim por diante.
O loop principal gerencia o jogo "física", isto é, como os componentes interagem entre si:
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());
...
Os componentes são codificados no aplicativo principal do C ++. As entidades podem ser definidas em um arquivo XML (a parte IA em um arquivo lua ou python).
O loop principal não se importa muito com entidades: ele gerencia apenas componentes. O design do software deve permitir:
Dado um componente, obtenha a entidade à qual ele pertence
Dada uma entidade, obtenha o componente do tipo "type"
Para todas as entidades, faça algo
Para todos os componentes da entidade, faça algo (por exemplo: serializar)
Eu estava pensando no seguinte:
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;
Com esse design, posso obter os números 1, 2, 3 (graças aos algoritmos boost :: fusion :: map) e 4. Também está tudo O (1) (ok, não exatamente, mas ainda é muito rápido).
Há também uma abordagem mais "comum":
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
...
};
Outra abordagem é se livrar da classe Entity: cada tipo de componente vive em sua própria lista. Portanto, há uma lista de Sprite, uma lista de Saúde, uma lista de Danos etc. Eu sei que eles pertencem à mesma entidade lógica por causa do ID da entidade. Isso é mais simples, mas mais lento: os componentes de IA precisam acessar basicamente todos os componentes de outras entidades e isso exigiria uma pesquisa na lista de componentes de cada um a cada etapa.
Qual abordagem você acha que é melhor? O mapa boost :: fusion é adequado para ser usado dessa maneira?