Merancang game berbasis komponen


16

Saya sedang menulis penembak (seperti 1942, grafis 2D klasik) dan saya ingin menggunakan pendekatan berbasis komponen. Sejauh ini saya memikirkan desain berikut:

  1. Setiap elemen game (airship, proyektil, powerup, musuh) adalah Entitas

  2. 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:

  1. Diberikan komponen, dapatkan entitas miliknya

  2. Diberikan entitas, dapatkan komponen tipe "tipe"

  3. Untuk semua entitas, lakukan sesuatu

  4. 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?


2
mengapa downvote? Apa yang salah dengan pertanyaan ini?
Emiliano

Jawaban:


6

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.


apa yang Anda maksud dengan "berorientasi data"?
Emiliano

Ada banyak informasi di Google, tapi di sini adalah artikel yang layak yang muncul yang harus memberikan gambaran tingkat tinggi, diikuti oleh diskusi yang berkaitan dengan sistem komponen: gamesfromwithin.com/data-oriented-design , gamedev. net / topik / ...
Skyler York

saya tidak bisa setuju dengan semua hal yang berhubungan dengan DOD, karena saya pikir itu tidak dapat menyelesaikan sendiri, maksud saya hanya DOD yang dapat menyarankan aprroch yang sangat baik untuk menyimpan data tetapi untuk memanggil fungsi dan prosedur Anda perlu menggunakan prosedur atau OOP approch, maksud saya masalahnya adalah bagaimana menggabungkan kedua metode ini untuk mengambil manfaat paling banyak baik untuk kinerja dan kemudahan pengkodean, misalnya. dalam struktur saya sarankan akan ada masalah kinerja ketika semua entitas tidak berbagi beberapa komponen tetapi dapat dengan mudah diselesaikan dengan menggunakan DOD, Anda hanya perlu membuat array yang berbeda untuk berbagai jenis objek.
Ali1S232

Ini tidak menjawab pertanyaan saya secara langsung tetapi sangat informatif. Saya ingat sesuatu tentang Arus Data di masa-masa Universitas saya. Ini adalah jawaban terbaik sejauh ini, dan itu "menang".
Emiliano

-1

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:

// declare components here------------------------------
class component
{
};

class health:public component
{
public:
    int value;
};

class boundingbox:public component
{
public :
    int left,right,top,bottom;
    bool collision(boundingbox& other)
    {
        if (left < other.right || right > other.left)
            if (top < other.bottom || bottom > other.top)
                return true;
        return false;
    }
};

class damage : public component
{
public:
    int value;
};

// declare enteties here------------------------------

class entity
{
    virtual int id() = 0;
    virtual int size() = 0;
};

class aircraft :public entity, public health,public boundingbox
{
    virtual int id(){return 1;}
    virtual int size() {return sizeof(*this);};
};

class bullet :public entity, public damage, public boundingbox
{
    virtual int id(){return 2;}
    virtual int size() {return sizeof(*this);};
};

int main()
{
    entity* gameobjects[3];
    gameobjects[0] = new aircraft;
    gameobjects[1] = new bullet;
    gameobjects[2] = new bullet;
    for (int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            if (dynamic_cast<boundingbox*>(gameobjects[i]) && dynamic_cast<boundingbox*>(gameobjects[j]) &&
                dynamic_cast<boundingbox*>(gameobjects[i])->collision(*dynamic_cast<boundingbox*>(gameobjects[j])))
                if (dynamic_cast<health*>(gameobjects[i]) && dynamic_cast<damage*>(gameobjects[j]))
                    dynamic_cast<health*>(gameobjects[i])->value -= dynamic_cast<damage*>(gameobjects[j])->value;
}

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 jika entitymemiliki komponen kerusakan itu akan mengembalikan nilai. jika Anda tidak yakin apakah entityada kerusakan komponen atau tidak, Anda dapat dengan mudah memeriksa if (dynamic_cast<damage*> (entity))nilai balik dynamic_castselalu NULL jika cetakan tidak valid dan penunjuk yang sama tetapi dengan jenis yang diminta jika valid. jadi untuk melakukan sesuatu dengan semua entitiesyang ada componentkamu bisa melakukannya seperti di bawah ini

for (int i=0;i<enteties.size();i++)
    if (dynamic_cast<component*>(enteties[i]))
        //do somthing here

jika ada pertanyaan lain saya akan dengan senang hati menjawab.


mengapa saya mendapatkan suara turun? apa yang salah dengan solusi saya?
Ali1S232

3
Solusi Anda sebenarnya bukan solusi berbasis komponen karena komponen tidak terlepas dari kelas permainan Anda. Semua instance Anda bergantung pada relasi IS A (warisan) alih-alih relasi HAS A (komposisi). Melakukannya dengan cara komposisi (entitas menangani beberapa komponen) memberi Anda banyak keunggulan dibandingkan model pewarisan (yang biasanya menjadi alasan Anda menggunakan komponen). Solusi Anda tidak memberikan manfaat dari solusi berbasis komponen dan memperkenalkan beberapa keanehan (pewarisan berganda dll). Tidak ada lokalitas data, tidak ada pembaruan komponen terpisah. Tidak ada modifikasi runtime komponen.
batal

pertama-tama pertanyaan menanyakan struktur bahwa setiap instance komponen hanya terkait dengan satu entitas, dan Anda dapat mengaktifkan dan menonaktifkan komponen dengan hanya menambahkan bool isActivekelas 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 seperti dynamic_cast<componnet*>(entity)->update().
Ali1S232

dan saya setuju masih akan ada masalah ketika dia ingin memiliki komponen yang dapat berbagi data tetapi mempertimbangkan apa yang dia minta saya kira tidak akan ada masalah untuk itu, dan lagi ada beberapa trik untuk masalah itu juga bahwa jika Anda mau saya bisa jelaskan.
Ali1S232

Meskipun saya setuju bahwa mungkin untuk mengimplementasikannya dengan cara ini, saya tidak berpikir itu ide yang bagus. Anda desainer tidak dapat membuat objek sendiri, kecuali jika Anda memiliki satu kelas über yang mewarisi semua komponen yang mungkin. Dan sementara Anda dapat memanggil pembaruan hanya pada satu komponen, itu tidak akan memiliki tata letak dalam-memori yang baik, dalam model yang dikomposisikan semua komponen contoh dari tipe yang sama dapat disimpan dekat dalam memori dan diulang tanpa kehilangan cache. Anda juga mengandalkan RTTI, yang merupakan sesuatu yang biasanya dimatikan dalam game karena alasan kinerja. Tata letak objek yang diurutkan memperbaiki sebagian besar.
batal
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.