Saya sedang menulis penembak (seperti 1942, grafis 2D klasik) dan saya ingin menggunakan pendekatan berbasis komponen. Sejauh ini saya memikirkan desain berikut:
Setiap elemen game (airship, proyektil, powerup, musuh) adalah Entitas
Setiap Entitas adalah seperangkat komponen yang dapat ditambahkan atau dihapus pada saat dijalankan. Contohnya adalah Posisi, Sprite, Kesehatan, IA, Kerusakan, BoundingBox dll.
Idenya adalah bahwa Airship, Proyektil, Musuh, Powerup BUKAN kelas permainan. Suatu entitas hanya ditentukan oleh komponen yang dimilikinya (dan yang dapat berubah selama waktu). Jadi pemain Airship dimulai dengan Sprite, Posisi, Kesehatan dan Komponen input. Powerup memiliki Sprite, Position, BoundingBox. Dan seterusnya.
Loop utama mengelola permainan "fisika", yaitu bagaimana komponen saling berinteraksi:
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());
...
Komponen di-hardcode dalam aplikasi C ++ utama. Entitas dapat didefinisikan dalam file XML (bagian IA dalam file lua atau python).
Loop utama tidak terlalu peduli tentang entitas: itu hanya mengelola komponen. Desain perangkat lunak harus memungkinkan untuk:
Diberikan komponen, dapatkan entitas miliknya
Diberikan entitas, dapatkan komponen tipe "tipe"
Untuk semua entitas, lakukan sesuatu
Untuk semua komponen entitas, lakukan sesuatu (misalnya: cerita bersambung)
Saya sedang memikirkan hal-hal berikut:
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;
Dengan desain ini saya bisa mendapatkan # 1, # 2, # 3 (terima kasih untuk meningkatkan :: fusion :: algoritma peta) dan # 4. Semuanya juga O (1) (ok, tidak persis, tapi masih sangat cepat).
Ada juga pendekatan yang lebih "umum":
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
...
};
Pendekatan lain adalah untuk menyingkirkan kelas Entity: setiap tipe Komponen tinggal di daftar sendiri. Jadi ada daftar Sprite, daftar Kesehatan, daftar Kerusakan dll. Saya tahu mereka milik entitas logika yang sama karena id entitas. Ini lebih sederhana, tetapi lebih lambat: komponen-komponen IA pada dasarnya memerlukan akses ke semua komponen entitas lainnya dan yang akan membutuhkan pencarian masing-masing daftar komponen lainnya pada setiap langkah.
Menurut Anda pendekatan mana yang lebih baik? apakah boost :: fusion map cocok untuk digunakan dengan cara itu?
sumber
Jawaban:
Saya telah menemukan bahwa desain berbasis komponen dan desain berorientasi data berjalan seiring. Anda mengatakan bahwa memiliki daftar komponen yang homogen dan menghilangkan objek entitas kelas satu (alih-alih memilih ID entitas pada komponen itu sendiri) akan "lebih lambat", tetapi itu tidak penting karena Anda belum benar-benar membuat profil kode nyata yang mengimplementasikan kedua pendekatan untuk sampai pada kesimpulan itu. Sebagai soal fakta, saya hampir dapat menjamin Anda bahwa menyeragamkan komponen Anda dan menghindari virtualisasi berat tradisional akan lebih cepat karena berbagai keunggulan desain berorientasi data - paralelisasi yang lebih mudah, pemanfaatan cache, modularitas, dll.
Saya tidak mengatakan pendekatan ini ideal untuk semuanya, tetapi sistem komponen yang pada dasarnya adalah kumpulan data yang membutuhkan transformasi yang sama dilakukan pada mereka setiap frame, hanya berteriak untuk menjadi berorientasi data. Akan ada saat-saat komponen perlu berkomunikasi dengan komponen lain dari tipe yang berbeda, tetapi ini akan menjadi kejahatan yang diperlukan. Namun, itu tidak boleh mendorong desain, karena ada cara untuk mengatasi masalah ini bahkan dalam kasus ekstrim bahwa semua komponen diproses secara paralel seperti antrian pesan dan masa depan .
Jelas Google sekitar untuk desain berorientasi data karena berkaitan dengan sistem berbasis komponen, karena topik ini banyak muncul dan ada sedikit diskusi dan data anekdotal di luar sana.
sumber
jika saya menulis kode seperti itu saya lebih suka atau tidak menggunakan pendekatan ini (dan saya tidak menggunakan dorongan apa pun jika itu penting bagi Anda), karena dapat melakukan semua hal yang Anda inginkan tetapi masalahnya adalah ketika ada terlalu banyak hidangan pembuka yang tidak berbagi beberapa konon, menemukan yang memiliki akan memakan waktu. selain itu tidak ada masalah lain yang saya dapat:
dalam pendekatan ini setiap komponen adalah basis untuk suatu entitas sehingga diberikan komponen itu juga pointer sebuah entitas! hal kedua yang Anda minta adalah memiliki akses langsung ke komponen beberapa entitas misalnya. ketika saya perlu mengakses kerusakan di salah satu entitas yang saya gunakan
dynamic_cast<damage*>(entity)->value
, jadi jikaentity
memiliki komponen kerusakan itu akan mengembalikan nilai. jika Anda tidak yakin apakahentity
ada kerusakan komponen atau tidak, Anda dapat dengan mudah memeriksaif (dynamic_cast<damage*> (entity))
nilai balikdynamic_cast
selalu NULL jika cetakan tidak valid dan penunjuk yang sama tetapi dengan jenis yang diminta jika valid. jadi untuk melakukan sesuatu dengan semuaentities
yang adacomponent
kamu bisa melakukannya seperti di bawah inijika ada pertanyaan lain saya akan dengan senang hati menjawab.
sumber
bool isActive
kelas komando dasar. masih ada kebutuhan untuk pengenalan komponen yang dapat digunakan ketika Anda mendefinisikan enteties tetapi saya tidak menganggap itu sebagai masalah, dan Anda masih memiliki seprate pembaruan yang sesuai (ingat sesuatu sepertidynamic_cast<componnet*>(entity)->update()
.