Kita mulai dari pendekatan sistem-komponen-entitas dasar .
Mari kita membuat kumpulan (istilah yang berasal dari artikel ini ) hanya dari informasi tentang jenis komponen . Ini dilakukan secara dinamis pada saat runtime, sama seperti kita akan menambah / menghapus komponen ke entitas satu per satu, tapi mari kita beri nama lebih tepatnya karena ini hanya tentang jenis informasi.
Kemudian kami membangun entitas yang menentukan kumpulan untuk masing-masing. Setelah kami membuat entitas, kumpulannya tidak dapat diubah yang artinya kami tidak dapat memodifikasinya secara langsung, tetapi kami masih dapat memperoleh tanda tangan entitas yang ada untuk salinan lokal (bersama dengan konten), membuat perubahan yang tepat untuknya, dan membuat entitas baru keluar itu.
Sekarang untuk konsep kunci: setiap kali suatu entitas dibuat, ia ditugaskan ke objek yang disebut assemblage bucket , yang berarti bahwa semua entitas dengan tanda tangan yang sama akan berada dalam wadah yang sama (misalnya dalam std :: vector).
Sekarang sistem hanya beralih melalui setiap ember minat mereka dan melakukan pekerjaan mereka.
Pendekatan ini memiliki beberapa keunggulan:
- komponen disimpan dalam beberapa (tepatnya: jumlah ember) potongan memori yang berdekatan - ini meningkatkan keramahan memori dan lebih mudah untuk membuang seluruh kondisi permainan
- komponen proses sistem secara linier, yang berarti peningkatan koherensi cache - bye bye kamus dan lompatan memori acak
- membuat entitas baru semudah memetakan kumpulan ke bucket dan mendorong kembali komponen yang diperlukan ke vektornya
- menghapus entitas semudah satu panggilan ke std :: pindah untuk menukar elemen terakhir dengan yang dihapus, karena pesanan tidak masalah saat ini
Jika kita memiliki banyak entitas dengan tanda tangan yang sama sekali berbeda, manfaat koherensi cache agak berkurang, tetapi saya tidak berpikir itu akan terjadi di sebagian besar aplikasi.
Ada juga masalah dengan pembatalan pointer setelah vektor dialokasikan kembali - ini bisa diselesaikan dengan memperkenalkan struktur seperti:
struct assemblage_bucket {
struct entity_watcher {
assemblage_bucket* owner;
entity_id real_index_in_vector;
};
std::unordered_map<entity_id, std::vector<entity_watcher*>> subscribers;
//...
};
Jadi, setiap kali karena alasan tertentu dalam logika permainan kami, kami ingin melacak entitas yang baru dibuat, di dalam bucket kami mendaftarkan entitas_watcher , dan begitu entitas tersebut harus std :: move'd selama penghapusan, kami mencari pengamat dan memperbarui mereka real_index_in_vector
nilai-nilai baru. Sebagian besar waktu ini hanya memaksakan pencarian kamus tunggal untuk setiap penghapusan entitas.
Apakah ada lagi kelemahan dari pendekatan ini?
Mengapa solusinya tidak disebutkan, meskipun cukup jelas?
EDIT : Saya mengedit pertanyaan untuk "menjawab jawaban", karena komentar tidak cukup.
Anda kehilangan sifat dinamis komponen pluggable, yang dibuat khusus untuk menjauh dari konstruksi kelas statis.
Bukan saya. Mungkin saya tidak menjelaskannya dengan cukup jelas:
auto signature = world.get_signature(entity_id); // this would just return entity_id.bucket_owner->bucket_signature or so
signature.add(foo_component);
signature.remove(bar_component);
world.delete_entity(entity_id); // entity_id would hold information about its bucket owner
world.create_entity(signature); // automatically assigns new entity to an existing or a new bucket
Sesederhana mengambil tanda tangan entitas yang ada, mengubahnya dan mengunggah lagi sebagai entitas baru. Sifat pluggable, dinamis ? Tentu saja. Di sini saya ingin menekankan bahwa hanya ada satu kelas "kumpulan" dan satu "ember". Bucket didorong oleh data dan dibuat saat runtime dalam jumlah yang optimal.
Anda harus melewati semua ember yang mungkin berisi target yang valid. Tanpa struktur data eksternal, deteksi tabrakan bisa sama sulitnya.
Nah, inilah mengapa kita memiliki struktur data eksternal yang disebutkan di atas . Solusinya sesederhana memperkenalkan iterator di kelas Sistem yang mendeteksi kapan harus melompat ke bucket berikutnya. The melompat akan murni transparan untuk logika.
sumber
Jawaban:
Anda pada dasarnya merancang sistem objek statis dengan pengalokasi kumpulan dan dengan kelas dinamis.
Saya menulis sistem objek yang bekerja hampir identik dengan sistem "kumpulan" Anda di masa sekolah saya, meskipun saya selalu cenderung menyebut "kumpulan" baik "cetak biru" atau "pola dasar" dalam desain saya sendiri. Arsitekturnya lebih merepotkan daripada sistem objek yang naif dan tidak memiliki keunggulan kinerja yang terukur dibandingkan beberapa desain yang lebih fleksibel yang saya bandingkan. Kemampuan untuk memodifikasi objek secara dinamis tanpa perlu mengubahnya atau mengalokasikannya kembali sangat penting ketika Anda bekerja pada editor game. Desainer ingin menyeret komponen ke definisi objek Anda. Kode Runtime bahkan mungkin perlu memodifikasi komponen secara efisien dalam beberapa desain, meskipun saya pribadi tidak menyukainya. Bergantung pada bagaimana Anda menghubungkan referensi objek di editor Anda,
Anda akan mendapatkan koherensi cache yang lebih buruk daripada yang Anda pikirkan dalam kebanyakan kasus non-sepele. Sistem AI Anda misalnya tidak peduli tentang
Render
komponen tetapi akhirnya terjebak iterating atas mereka sebagai bagian dari masing-masing entitas. Objek yang diiterasi lebih besar, dan permintaan cacheline akhirnya menarik data yang tidak perlu, dan lebih sedikit keseluruhan objek dikembalikan dengan setiap permintaan). Itu masih akan lebih baik daripada metode naif, dan komposisi objek metode naif digunakan bahkan di mesin AAA besar, jadi Anda mungkin tidak perlu lebih baik, tapi setidaknya jangan berpikir Anda tidak bisa memperbaikinya lebih lanjut.Pendekatan Anda memang masuk akal bagi sebagian orangkomponen, tetapi tidak semua. Saya sangat tidak menyukai ECS karena mendukung selalu menempatkan setiap komponen dalam wadah terpisah, yang masuk akal untuk fisika atau grafik atau yang lainnya tetapi tidak masuk akal sama sekali jika Anda mengizinkan beberapa komponen skrip atau AI komposer. Jika Anda membiarkan sistem komponen digunakan untuk lebih dari sekadar objek bawaan tetapi juga sebagai cara bagi perancang dan pemrogram gameplay untuk menyusun perilaku objek, maka masuk akal untuk mengelompokkan semua komponen AI (yang akan sering berinteraksi) atau semua skrip komponen (karena Anda ingin memperbarui semuanya dalam satu batch). Jika Anda menginginkan sistem yang paling berkinerja maka Anda akan memerlukan campuran alokasi komponen dan skema penyimpanan dan meluangkan waktu untuk mencari tahu dengan pasti mana yang terbaik untuk setiap jenis komponen tertentu.
sumber
Apa yang telah Anda lakukan adalah merekayasa ulang objek C ++. Alasan mengapa ini terasa jelas adalah bahwa jika Anda mengganti kata "entitas" dengan "kelas" dan "komponen" dengan "anggota" ini adalah desain OOP standar menggunakan mixin.
1) Anda kehilangan sifat dinamis komponen pluggable, yang dibuat khusus untuk menjauh dari konstruksi kelas statis.
2) koherensi memori paling penting dalam tipe data, bukan dalam objek menyatukan beberapa tipe data di satu tempat. Ini adalah salah satu alasan bahwa komponen + sistem diciptakan, untuk menjauh dari fragmen memori objek kelas +.
3) desain ini juga kembali ke gaya kelas C ++ karena Anda memikirkan entitas sebagai objek yang koheren ketika, dalam desain komponen + sistem, entitas tersebut hanya berupa tag / ID untuk membuat pekerjaan batin dapat dimengerti oleh manusia.
4) sama mudahnya bagi komponen untuk membuat serial sendiri daripada objek kompleks untuk membuat serial beberapa komponen dalam dirinya sendiri, jika tidak benar-benar lebih mudah untuk dicatat sebagai programmer.
5) langkah logis berikutnya di jalur ini adalah menghapus Sistem dan memasukkan kode itu langsung ke entitas, di mana ia memiliki semua data yang diperlukan untuk bekerja. Kita semua bisa melihat apa artinya =)
sumber
Menyatukan entitas seperti itu tidak sepenting yang Anda pikirkan, itulah sebabnya sulit untuk memikirkan alasan yang valid selain "karena itu adalah unit". Tetapi karena Anda benar-benar melakukan ini untuk koherensi cache yang bertentangan dengan koherensi logis, mungkin masuk akal.
Salah satu kesulitan yang bisa Anda miliki adalah interaksi antara komponen dalam ember yang berbeda. Ini tidak terlalu mudah untuk menemukan sesuatu yang dapat ditembaki oleh AI Anda, misalnya, Anda harus melewati semua ember yang mungkin berisi target yang valid. Tanpa struktur data eksternal, deteksi tabrakan bisa sama sulitnya.
Untuk melanjutkan tentang pengorganisasian entitas bersama untuk koherensi logis, satu-satunya alasan bahwa saya harus menjaga entitas bersama adalah untuk tujuan identifikasi dalam misi saya. Saya perlu tahu apakah Anda baru saja membuat entitas tipe A atau tipe B, dan saya menyiasatinya dengan ... Anda dapat menebaknya: menambahkan komponen baru yang mengidentifikasi kumpulan yang menyatukan entitas ini. Meski begitu, saya tidak mengumpulkan semua komponen untuk tugas besar, saya hanya perlu tahu apa itu. Jadi saya tidak berpikir bagian ini sangat berguna.
sumber