Saran tentang Menghubungkan Antara Sistem Komponen Entitas di C ++

10

Setelah membaca beberapa dokumentasi tentang sistem entitas-komponen, saya memutuskan untuk mengimplementasikan tambang. Sejauh ini, saya memiliki kelas Dunia yang berisi entitas dan manajer sistem (sistem), kelas Entitas yang berisi komponen sebagai std :: map, dan beberapa sistem. Saya memegang entitas sebagai std :: vector in World. Tidak masalah sejauh ini. Yang membingungkan saya adalah iterasi entitas, saya tidak dapat memiliki pikiran yang jernih tentang hal itu, jadi saya masih tidak dapat mengimplementasikan bagian itu. Haruskah setiap sistem memiliki daftar entitas lokal yang mereka minati? Atau haruskah saya hanya beralih melalui entitas di kelas Dunia dan membuat loop bersarang untuk beralih melalui sistem dan memeriksa apakah entitas memiliki komponen yang sistem tertarik? Maksudku :

for (entity x : listofentities) {
   for (system y : listofsystems) {
       if ((x.componentBitmask & y.bitmask) == y.bitmask)
             y.update(x, deltatime)
       }
 }

tapi saya pikir sistem bitmask agak akan memblokir fleksibilitas jika menanamkan bahasa scripting. Atau memiliki daftar lokal untuk setiap sistem akan meningkatkan penggunaan memori untuk kelas. Saya sangat bingung.

deniz
sumber
Mengapa Anda mengharapkan pendekatan bitmask untuk menghambat binding skrip? Sebagai tambahan, gunakan referensi (const, jika mungkin) di masing-masing loop untuk menghindari penyalinan entitas dan sistem.
Benjamin Kloster
menggunakan bitmask misalnya int, akan menampung hanya 32 komponen yang berbeda. Saya tidak menyiratkan akan ada lebih dari 32 komponen tetapi bagaimana jika saya miliki? saya harus membuat int lain atau 64bit int, itu tidak akan dinamis.
deniz
Anda bisa menggunakan std :: bitset atau std :: vector <bool>, tergantung pada apakah Anda menginginkannya run-time dynamic.
Benjamin Kloster

Jawaban:

7

Memiliki daftar lokal untuk setiap sistem akan meningkatkan penggunaan memori untuk kelas.

Ini adalah pertukaran ruang-waktu tradisional .

Sementara iterasi melalui semua entitas dan memeriksa tanda tangan mereka langsung ke kode, itu mungkin menjadi tidak efisien ketika jumlah sistem Anda tumbuh - bayangkan sistem khusus (biarkan input) yang mencari entitas tunggal yang mungkin menarik di antara ribuan entitas yang tidak terkait .

Yang mengatakan, pendekatan ini mungkin masih cukup baik tergantung pada tujuan Anda.

Meskipun, jika Anda khawatir tentang kecepatan, tentu saja ada solusi lain untuk dipertimbangkan.

Haruskah setiap sistem memiliki daftar entitas lokal yang mereka minati?

Persis. Ini adalah pendekatan standar yang harus memberi Anda kinerja yang layak dan cukup mudah untuk diterapkan. Overhead memori dapat diabaikan menurut pendapat saya - kita berbicara tentang menyimpan pointer.

Sekarang bagaimana mempertahankan "daftar minat" ini mungkin tidak terlalu jelas. Adapun wadah data, std::vector<entity*> targetskelas sistem di dalam cukup sempurna. Sekarang yang saya lakukan adalah ini:

  • Entitas kosong pada saat pembuatan dan bukan milik sistem apa pun.
  • Setiap kali saya menambahkan komponen ke entitas:

    • dapatkan bit signature saat ini ,
    • memetakan ukuran komponen ke kumpulan dunia dengan ukuran potongan yang memadai (secara pribadi saya menggunakan boost :: pool) dan mengalokasikan komponen di sana
    • dapatkan bit signature baru entitas (yang hanya "signature bit saat ini" ditambah komponen baru)
    • iterate melalui semua sistem dunia dan jika ada sistem yang tanda tangannya tidak cocok dengan tanda tangan entitas saat ini dan tidak cocok dengan tanda tangan baru, itu menjadi jelas kita harus mendorong kembali pointer ke entitas kita di sana.

          for(auto sys = owner_world.systems.begin(); sys != owner_world.systems.end(); ++sys)
                  if((*sys)->components_signature.matches(new_signature) && !(*sys)->components_signature.matches(old_signature)) 
                          (*sys)->add(this);

Menghapus entitas sepenuhnya analog, dengan satu-satunya perbedaan yang kami hapus jika suatu sistem cocok dengan tanda tangan kami saat ini (yang berarti bahwa entitas itu ada di sana) dan tidak cocok dengan tanda tangan baru (yang berarti entitas seharusnya tidak lagi berada di sana ).

Sekarang Anda mungkin mempertimbangkan penggunaan std :: list karena menghapus dari vektor adalah O (n), tidak menyebutkan bahwa Anda harus menggeser sebagian besar data setiap kali Anda menghapus dari tengah. Sebenarnya, Anda tidak harus - karena kami tidak peduli tentang pemrosesan pesanan pada tingkat ini, kami hanya dapat memanggil std :: remove dan hidup dengan kenyataan bahwa pada setiap penghapusan kami hanya perlu melakukan O (n) mencari kami entitas yang akan dihapus.

std :: list akan memberi Anda O (1) hapus tetapi di sisi lain Anda memiliki sedikit overhead memori tambahan. Juga ingat bahwa sebagian besar waktu Anda akan memproses entitas dan tidak menghapusnya - dan ini pasti dilakukan lebih cepat menggunakan std :: vector.

Jika Anda sangat kritis terhadap kinerja, Anda dapat mempertimbangkan pola pengaksesan data lain , tetapi bagaimanapun Anda mempertahankan semacam "daftar minat". Ingat juga bahwa jika Anda menjaga abstrak Entity System API Anda seharusnya tidak menjadi masalah untuk meningkatkan metode pemrosesan entitas sistem jika framerate Anda turun karena mereka - jadi untuk sekarang, pilih metode yang paling mudah bagi Anda untuk membuat kode - hanya kemudian profil dan tingkatkan jika perlu.

Patryk Czachurski
sumber
5

Ada pendekatan yang layak dipertimbangkan di mana setiap sistem memiliki komponen yang terkait dengan dirinya sendiri dan entitas hanya merujuk kepada mereka. Pada dasarnya, kelas Anda (disederhanakan) Entityterlihat seperti ini:

class Entity {
  std::map<ComponentType, Component*> components;
};

Ketika Anda mengatakan RigidBodykomponen yang dilampirkan pada Entity, Anda memintanya dari Physicssistem Anda . Sistem menciptakan komponen dan membiarkan entitas menyimpan pointer ke sana. Sistem Anda kemudian terlihat seperti:

class PhysicsSystem {
  std::vector<RigidBodyComponent> rigidBodyComponents;
};

Sekarang, ini mungkin terlihat sedikit kontra intuitif pada awalnya tetapi keuntungannya terletak pada cara sistem entitas komponen memperbarui keadaan mereka. Seringkali, Anda akan mengulangi melalui sistem Anda dan meminta mereka memperbarui komponen terkait

for(auto it = systems.begin(); it != systems.end(); ++it) {
  it->update();
}

Kekuatan memiliki semua komponen yang dimiliki oleh sistem dalam memori yang berdekatan adalah bahwa ketika sistem Anda mengulangi setiap komponen dan memutakhirkannya, pada dasarnya hanya perlu dilakukan

for(auto it = rigidBodyComponents.begin(); it != rigidBodyComponents.end(); ++it) {
  it->update();
}

Itu tidak harus mengulangi semua entitas yang berpotensi tidak memiliki komponen yang mereka perlu perbarui dan juga berpotensi untuk kinerja cache yang sangat baik karena semua komponen akan disimpan secara bersamaan. Ini adalah satu, jika bukan keuntungan terbesar dari metode ini. Anda akan sering memiliki ratusan dan ribuan komponen pada waktu tertentu, mungkin juga mencoba dan tampil sebaik mungkin.

Pada titik itu Worldhanya loop Anda melalui sistem dan memanggil updatemereka tanpa perlu mengulangi entitas juga. Ini (imho) desain yang lebih baik karena dengan begitu tanggung jawab sistem jauh lebih jelas.

Tentu saja, ada segudang desain seperti itu sehingga Anda harus hati-hati mengevaluasi kebutuhan gim Anda dan memilih yang paling tepat, tetapi seperti yang bisa kita lihat di sini, terkadang detail desain kecillah yang dapat membuat perbedaan.

pwny
sumber
jawaban yang bagus, terima kasih. tetapi komponen tidak memiliki fungsi (seperti pembaruan ()), hanya data. dan sistem memproses data itu. jadi sesuai dengan contoh Anda, saya harus menambahkan pembaruan virtual untuk kelas komponen dan pointer entitas untuk setiap komponen, apakah saya benar?
deniz
@deniz Semuanya tergantung pada desain Anda. Jika komponen Anda tidak memiliki metode apa pun selain data, sistem masih dapat mengulanginya dan melakukan tindakan yang diperlukan. Adapun untuk menghubungkan kembali ke entitas, ya Anda bisa menyimpan pointer ke entitas pemilik dalam komponen itu sendiri atau meminta sistem Anda mempertahankan peta antara pegangan komponen dan entitas. Namun biasanya, Anda ingin komponen Anda menjadi mandiri mungkin. Komponen yang tidak tahu sama sekali tentang entitas induknya itu ideal. Jika Anda membutuhkan komunikasi ke arah itu, lebih suka acara dan sejenisnya.
pwny
Jika Anda mengatakan itu akan lebih baik untuk efisiensi, saya akan menggunakan pola Anda.
deniz
@deniz Pastikan Anda benar-benar profil kode Anda lebih awal dan sering untuk mengidentifikasi apa yang berhasil dan tidak untuk
engin
oke :) saya akan melakukan tes stres
deniz
1

Menurut pendapat saya, arsitektur yang baik adalah membuat lapisan komponen dalam entitas, dan memisahkan manajemen setiap sistem di lapisan komponen ini. Sebagai contoh, sistem logika memiliki beberapa komponen logika yang mempengaruhi entitas mereka, dan menyimpan atribut umum yang dibagi ke semua komponen di entitas.

Setelah itu, jika Anda ingin mengelola objek masing-masing sistem di titik yang berbeda, atau dalam urutan tertentu, lebih baik untuk membuat daftar komponen aktif di setiap sistem. Semua daftar petunjuk yang dapat Anda buat dan kelola dalam sistem kurang dari satu sumber daya yang dimuat.

Superarce
sumber