Piszę strzelankę (jak 1942, klasyczna grafika 2D) i chciałbym użyć aplikacji opartej na komponentach. Do tej pory myślałem o następującym projekcie:
Każdy element gry (sterowiec, pocisk, wzmocnienie, wróg) jest bytem
Każda jednostka to zestaw komponentów, które można dodawać lub usuwać w czasie wykonywania. Przykłady to Pozycja, Sprite, Zdrowie, IA, Obrażenia, BoundingBox itp.
Chodzi o to, że Sterowiec, Pocisk, Wróg, Wzmocnienie NIE są klasami gier. Jednostka jest definiowana tylko przez składniki, które posiada (i które mogą się zmieniać w czasie). Tak więc sterowiec gracza zaczyna się od komponentów Sprite, Position, Health i Input. Powerup ma Sprite, Position, BoundingBox. I tak dalej.
Główna pętla zarządza „fizyką” gry, tj. Wzajemnym oddziaływaniem komponentów:
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());
...
Komponenty są zakodowane na stałe w głównej aplikacji C ++. Jednostki można zdefiniować w pliku XML (część IA w pliku lua lub python).
Główna pętla nie dba zbytnio o byty: zarządza tylko komponentami. Projekt oprogramowania powinien umożliwiać:
Biorąc pod uwagę komponent, pobierz encję, do której on należy
Biorąc pod uwagę jednostkę, pobierz komponent typu „typ”
Zrób dla wszystkich bytów
Zrób coś dla wszystkich elementów encji (np .: serializuj)
Myślałem o następujących kwestiach:
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;
Dzięki temu projektowi mogę uzyskać # 1, # 2, # 3 (dzięki algorytmom boost :: fusion :: map) i # 4. Także wszystko jest O (1) (ok, niezupełnie, ale wciąż jest bardzo szybkie).
Istnieje również bardziej „powszechna” metoda:
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
...
};
Innym podejściem jest pozbycie się klasy Entity: każdy typ komponentu żyje na własnej liście. Istnieje więc lista duszków, lista zdrowia, lista obrażeń itp. Wiem, że należą do tej samej jednostki logicznej z powodu identyfikatora jednostki. Jest to prostsze, ale wolniejsze: komponenty IA potrzebują dostępu w zasadzie do wszystkich innych komponentów encji, a to wymagałoby przeszukiwania listy komponentów na każdym etapie.
Który według Ciebie jest lepszy? czy mapa boost :: fusion nadaje się do takiego zastosowania?