Tôi đang viết một game bắn súng (như 1942, đồ họa 2D cổ điển) và tôi muốn sử dụng một phê duyệt dựa trên thành phần. Cho đến nay tôi nghĩ về thiết kế sau:
Mỗi yếu tố trò chơi (khí cầu, đạn, sức mạnh, kẻ thù) là một Thực thể
Mỗi Thực thể là một tập hợp các thành phần có thể được thêm hoặc xóa trong thời gian chạy. Ví dụ như Position, Sprite, Health, IA, Damage, BoundingBox, v.v.
Ý tưởng là Airship, Projectile, Enemy, Powerup KHÔNG phải là các lớp trò chơi. Một thực thể chỉ được xác định bởi các thành phần mà nó sở hữu (và có thể thay đổi trong thời gian). Vì vậy, Airship của người chơi bắt đầu với các thành phần Sprite, Position, Health và Input. Một powerup có Sprite, Position, BoundingBox. Và như thế.
Vòng lặp chính quản lý trò chơi "vật lý", tức là cách các thành phần tương tác với nhau:
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ác thành phần được mã hóa cứng trong ứng dụng C ++ chính. Các thực thể có thể được định nghĩa trong một tệp XML (phần IA trong tệp lua hoặc python).
Vòng lặp chính không quan tâm nhiều đến các thực thể: nó chỉ quản lý các thành phần. Thiết kế phần mềm nên cho phép:
Cho một thành phần, có được thực thể nó thuộc về
Đưa ra một thực thể, lấy thành phần của loại "loại"
Đối với tất cả các thực thể, làm một cái gì đó
Đối với tất cả thành phần của thực thể, hãy làm một cái gì đó (ví dụ: serialization)
Tôi đã suy nghĩ về những điều sau đây:
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;
Với thiết kế này, tôi có thể nhận được # 1, # 2, # 3 (nhờ các thuật toán boost :: fusion :: map) và # 4. Ngoài ra mọi thứ đều là O (1) (ok, không chính xác, nhưng nó vẫn rất nhanh).
Ngoài ra còn có một sự chấp thuận "phổ biến" hơn:
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
...
};
Một cách chấp nhận khác là loại bỏ lớp Thực thể: mỗi loại Thành phần sống trong danh sách riêng của nó. Vì vậy, có một danh sách Sprite, danh sách Sức khỏe, danh sách Thiệt hại, v.v ... Tôi biết chúng thuộc cùng một thực thể logic vì id thực thể. Điều này đơn giản hơn, nhưng chậm hơn: các thành phần IA cần truy cập cơ bản vào tất cả các thành phần của thực thể khác và điều đó sẽ yêu cầu tìm kiếm danh sách của các thành phần khác ở mỗi bước.
Mà bạn nghĩ là tốt hơn? Bản đồ boost :: fusion có phù hợp để sử dụng theo cách đó không?