Kapan / di mana memperbarui komponen


10

Alih-alih mesin game berat warisan saya yang biasa saya bermain-main dengan pendekatan yang lebih berbasis komponen. Namun saya mengalami kesulitan membenarkan di mana membiarkan komponen melakukan hal mereka.

Katakanlah saya memiliki entitas sederhana yang memiliki daftar komponen. Tentu saja entitas tidak tahu komponen apa itu. Mungkin ada komponen yang memberikan entitas posisi di layar, yang lain mungkin ada untuk menggambar entitas di layar.

Agar komponen ini berfungsi, mereka harus memperbarui setiap frame, cara termudah untuk melakukannya adalah dengan berjalan di atas pohon adegan dan kemudian untuk setiap entitas memperbarui setiap komponen. Tetapi beberapa komponen mungkin perlu sedikit lebih banyak manajemen. Misalnya komponen yang membuat suatu entitas collidable harus dikelola oleh sesuatu yang dapat mengawasi semua komponen collidable. Komponen yang membuat suatu entitas yang dapat ditarik membutuhkan seseorang untuk mengawasi semua komponen yang dapat ditarik lainnya untuk mengetahui urutan pengundian, dll ...

Jadi pertanyaan saya adalah, di mana saya memperbarui komponen, apa cara yang bersih untuk membawanya ke manajer?

Saya telah berpikir tentang menggunakan objek manajer singleton untuk masing-masing jenis komponen tetapi yang memiliki kelemahan biasa menggunakan singleton, cara untuk meringankan ini sedikit adalah dengan menggunakan injeksi ketergantungan tetapi kedengarannya seperti kerja keras yang berlebihan untuk masalah ini. Saya juga bisa berjalan di atas pohon adegan dan kemudian mengumpulkan komponen-komponen yang berbeda ke dalam daftar menggunakan semacam pola pengamat tetapi tampaknya agak boros untuk melakukan setiap frame.


1
Apakah Anda menggunakan sistem dalam beberapa cara?
Asakeron

Sistem komponen adalah cara biasa untuk melakukan ini. Saya pribadi hanya memanggil pembaruan pada semua entitas, yang memanggil pembaruan pada semua komponen, dan memiliki beberapa kasus "khusus" (seperti manajer spasial untuk deteksi tabrakan, yang statis).
ashes999

Sistem komponen? Saya tidak pernah mendengar itu sebelumnya. Saya akan mulai Googling, tetapi saya akan menerima tautan yang direkomendasikan.
Roy T.

1
Entity Systems adalah masa depan pengembangan MMOG yang merupakan sumber daya yang hebat. Dan, sejujurnya saya selalu bingung dengan nama-nama arsitektur ini. Perbedaan dengan pendekatan yang disarankan adalah bahwa komponen hanya menyimpan data dan sistem memprosesnya. Jawaban ini sangat relevan juga.
Asakeron

1
Saya menulis semacam posting blog tentang masalah ini di sini: gamedevrubberduck.wordpress.com/2012/12/26/…
AlexFoxGill

Jawaban:


15

Saya sarankan mulai dengan membaca 3 kebohongan besar Mike Acton, karena Anda melanggar dua dari mereka. Saya serius, ini akan mengubah cara Anda mendesain kode Anda: http://cellperformance.beyond3d.com/articles/2008/03/three-big-lies.html

Jadi, yang mana yang Anda langgar?

Kebohongan 3 - Kode lebih penting daripada data

Anda berbicara tentang injeksi ketergantungan, yang mungkin berguna dalam beberapa (dan hanya beberapa) contoh tetapi harus selalu membunyikan bel alarm besar jika Anda menggunakannya, terutama dalam pengembangan game! Mengapa? Karena itu seringkali merupakan abstraksi yang tidak perlu. Dan abstraksi di tempat yang salah mengerikan. Jadi kamu punya game. Gim ini memiliki manajer untuk berbagai komponen. Semua komponen sudah ditentukan. Jadi buat kelas di suatu tempat di dalam kode loop permainan utama Anda yang "memiliki" para manajer. Suka:

private CollissionManager _collissionManager;
private BulletManager _bulletManager;

Berikan beberapa fungsi getter untuk mendapatkan setiap kelas manajer (getBulletManager ()). Mungkin kelas ini sendiri adalah Singleton atau dapat dicapai dari satu (Anda mungkin memiliki singleton Game sentral di suatu tempat). Tidak ada yang salah dengan data dan perilaku hard-code yang didefinisikan dengan baik.

Jangan membuat Manajer Manajer yang memungkinkan Anda mendaftar Manajer menggunakan kunci, yang dapat diambil menggunakan kunci itu oleh kelas lain yang ingin menggunakan manajer. Ini adalah sistem yang hebat dan sangat fleksibel, tetapi berbicara tentang permainan di sini. Anda tahu persis sistem mana yang ada dalam game. Kenapa berpura-pura tidak? Karena ini adalah sistem untuk orang yang menganggap kode lebih penting daripada data. Mereka akan berkata "Kode ini fleksibel, data hanya mengisinya". Tetapi kode hanyalah data. Sistem yang saya jelaskan jauh lebih mudah, lebih dapat diandalkan, lebih mudah dirawat dan jauh lebih fleksibel (misalnya, jika perilaku satu manajer berbeda dari manajer lain, Anda hanya perlu mengubah beberapa baris alih-alih mengerjakan ulang seluruh sistem)

Kebohongan 2 - Kode harus dirancang di sekitar model dunia

Jadi, Anda memiliki entitas di dunia game. Entitas memiliki sejumlah komponen yang mendefinisikan perilakunya. Jadi, Anda membuat kelas Entitas dengan daftar objek Komponen, dan fungsi Pembaruan () yang memanggil fungsi Pembaruan () dari setiap Komponen. Baik?

Tidak :) Itu mendesain model dunia: kamu punya peluru di gimmu, jadi kamu menambahkan kelas Bullet. Kemudian Anda memperbarui masing-masing Bullet dan beralih ke yang berikutnya. Ini benar-benar akan membunuh kinerja Anda dan itu memberi Anda basis kode berbelit-belit mengerikan dengan kode duplikat di mana-mana dan tidak ada penataan logis dari kode yang sama. (Lihat jawaban saya di sini untuk penjelasan yang lebih terperinci tentang mengapa desain OO tradisional menyebalkan, atau lihat Desain Berorientasi Data)

Mari kita lihat situasi tanpa bias OO kita. Kami menginginkan yang berikut, tidak lebih tidak kurang (harap dicatat tidak ada persyaratan untuk membuat kelas untuk entitas atau objek):

  • Anda memiliki banyak entitas
  • Entitas terdiri dari sejumlah komponen yang menentukan perilaku entitas
  • Anda ingin memperbarui setiap komponen dalam gim setiap frame, lebih disukai dengan cara yang terkontrol
  • Selain mengidentifikasi komponen sebagai milik bersama, tidak ada entitas yang perlu dilakukan. Ini adalah tautan / ID untuk beberapa komponen.

Dan mari kita lihat situasinya. Sistem komponen Anda akan memperbarui perilaku setiap objek dalam game setiap frame. Ini jelas merupakan sistem kritis mesin Anda. Kinerja sangat penting di sini!

Jika Anda terbiasa dengan arsitektur komputer atau Desain Berorientasi Data, Anda tahu bagaimana kinerja terbaik dicapai: memori penuh sesak dan dengan mengelompokkan eksekusi kode. Jika Anda mengeksekusi cuplikan kode A, B dan C seperti ini: ABCABCABC, Anda tidak akan mendapatkan kinerja yang sama seperti ketika Anda mengeksekusinya seperti ini: AAABBBCCC. Ini bukan hanya karena instruksi dan cache data akan digunakan lebih efisien, tetapi juga karena jika Anda mengeksekusi semua "A" satu sama lain, ada banyak ruang untuk optimasi: menghapus kode duplikat, data prakalkulasi yang digunakan oleh semua "A", dll.

Jadi jika kita ingin memperbarui semua komponen, jangan buat kelas / objek dengan fungsi pembaruan. Jangan panggil fungsi pembaruan itu untuk setiap komponen di setiap entitas. Itulah solusi "ABCABCABC". Mari kita kelompokkan semua pembaruan komponen identik secara bersamaan. Kemudian kita dapat memperbarui semua komponen-A, diikuti oleh B, dll. Apa yang kita butuhkan untuk membuat ini?

Pertama, kita membutuhkan Manajer Komponen. Untuk setiap jenis komponen dalam game, kita membutuhkan kelas manajer. Ini memiliki fungsi pembaruan yang akan memperbarui semua komponen jenis itu. Ini memiliki fungsi buat yang akan menambahkan komponen baru dari jenis itu dan fungsi hapus yang akan menghancurkan komponen yang ditentukan. Mungkin ada fungsi pembantu lain untuk mendapatkan dan mengatur data khusus untuk komponen itu (misalnya: mengatur model 3D untuk Komponen Model). Perhatikan bahwa dalam beberapa hal manajer adalah kotak hitam bagi dunia luar. Kami tidak tahu bagaimana data masing-masing komponen disimpan. Kami tidak tahu bagaimana setiap komponen diperbarui. Kami tidak peduli, selama komponen berperilaku sebagaimana mestinya.

Selanjutnya kita membutuhkan entitas. Anda bisa menjadikan ini kelas, tapi itu tidak perlu. Entitas bisa tidak lebih dari ID integer unik atau string hash (demikian juga integer). Saat Anda membuat komponen untuk Entitas, Anda meneruskan ID sebagai argumen kepada Manajer. Saat Anda ingin menghapus komponen, Anda meneruskan ID lagi. Mungkin ada beberapa keuntungan untuk menambahkan sedikit lebih banyak data ke Entity daripada hanya menjadikannya ID, tetapi itu hanya akan menjadi fungsi pembantu karena seperti yang saya sebutkan dalam persyaratan, semua perilaku entitas ditentukan oleh komponen itu sendiri. Ini mesin Anda, jadi lakukan apa yang masuk akal untuk Anda.

Yang kami butuhkan adalah Manajer Entitas. Kelas ini akan menghasilkan ID unik jika Anda menggunakan solusi ID-saja, atau dapat digunakan untuk membuat / mengelola objek Entitas. Itu juga dapat menyimpan daftar semua entitas dalam game jika Anda membutuhkannya. Entity Manager bisa menjadi kelas pusat dari sistem komponen Anda, menyimpan referensi ke semua Manajer Komponen dalam game Anda dan memanggil fungsi pembaruan mereka dalam urutan yang benar. Dengan cara itu semua permainan harus dilakukan adalah memanggil EntityManager.update () dan seluruh sistem dipisahkan dengan baik dari sisa mesin Anda.

Itulah pandangan mata burung, mari kita lihat bagaimana manajer komponen bekerja. Inilah yang Anda butuhkan:

  • Buat data komponen saat buat (entityID) dipanggil
  • Hapus data komponen saat menghapus (entityID) dipanggil
  • Perbarui semua (komponen) data komponen ketika pembaruan () dipanggil (yaitu tidak semua komponen perlu memperbarui setiap frame)

Yang terakhir adalah di mana Anda mendefinisikan perilaku komponen / logika dan sepenuhnya bergantung pada jenis komponen yang Anda tulis. AnimationComponent akan memperbarui data Animasi berdasarkan bingkai yang ada di. DragableComponent hanya akan memperbarui komponen yang diseret oleh mouse. PhysicsComponent akan memperbarui data dalam sistem fisika. Namun, karena Anda memperbarui semua komponen dari tipe yang sama dalam sekali jalan, Anda dapat melakukan beberapa optimasi yang tidak mungkin dilakukan ketika setiap komponen adalah objek yang terpisah dengan fungsi pembaruan yang dapat dipanggil kapan saja.

Perhatikan bahwa saya masih belum pernah meminta untuk membuat kelas XxxComponent untuk menyimpan data komponen. Terserah kamu. Anda suka Desain Berorientasi Data? Kemudian susun data dalam array terpisah untuk setiap variabel. Anda suka Object Oriented Design? (Saya tidak akan merekomendasikannya, itu masih akan membunuh kinerja Anda di banyak tempat) Kemudian buat objek XxxComponent yang akan menyimpan data masing-masing komponen.

Hal hebat tentang manajer adalah enkapsulasi. Sekarang enkapsulasi adalah salah satu filosofi yang paling banyak disalahgunakan dalam dunia pemrograman. Inilah yang harus digunakan. Hanya manajer yang tahu data komponen apa yang disimpan di mana, bagaimana logika komponen bekerja. Ada beberapa fungsi untuk mendapatkan / mengatur data tetapi hanya itu. Anda dapat menulis ulang seluruh manajer dan kelas-kelas yang mendasarinya dan jika Anda tidak mengubah antarmuka publik, tidak seorang pun memperhatikan. Mesin fisika berubah? Tulis ulang Manajer FisikaComponent dan selesai.

Lalu ada satu hal terakhir: komunikasi dan berbagi data antar komponen. Sekarang ini rumit dan tidak ada solusi satu ukuran untuk semua. Anda bisa membuat fungsi get / set di manajer untuk memungkinkan misalnya komponen tumbukan untuk mendapatkan posisi dari komponen posisi (yaitu PositionManager.getPosition (entityID)). Anda dapat menggunakan sistem acara. Anda dapat menyimpan beberapa data bersama dalam entitas (solusi paling jelek menurut saya). Anda dapat menggunakan (ini sering digunakan) sistem pesan. Atau gunakan kombinasi beberapa sistem! Saya tidak punya waktu atau pengalaman untuk masuk ke masing-masing sistem ini, tetapi pencarian google dan stackoverflow adalah teman Anda.


Saya menemukan jawaban ini sangat menarik. Hanya satu pertanyaan (harap Anda atau seseorang dapat menanggapi saya). Bagaimana Anda mengelola untuk menghilangkan entitas pada sistem berbasis komponen DOD? Bahkan Artemis menggunakan Entity sebagai kelas, saya tidak yakin itu sangat bodoh.
Wolfrevo Kcats

1
Apa yang Anda maksud dengan menghilangkannya? Apakah maksud Anda sistem entitas tanpa kelas Entitas? Alasan Artemis memiliki Entitas adalah karena dalam Artemis, kelas Entitas mengelola komponennya sendiri. Dalam sistem yang saya usulkan, kelas ComponentManager mengelola komponen. Jadi, alih-alih membutuhkan kelas Entity, Anda bisa memiliki ID integer yang unik. Jadi katakanlah Anda memiliki entitas 254, yang memiliki komponen posisi. Ketika Anda ingin mengubah posisi, Anda dapat memanggil PositionCompMgr.setPosition (int id, Vector3 newPos), dengan 254 sebagai parameter id.
Mart

Tetapi bagaimana Anda mengelola ID? Bagaimana jika Anda ingin menghapus komponen dari suatu entitas untuk menetapkannya nanti ke yang lain? Bagaimana jika Anda ingin menghapus suatu entitas dan menambahkan yang baru? Bagaimana jika Anda ingin satu komponen dibagi antara dua entitas atau lebih? Saya sangat tertarik dengan ini.
Wolfrevo Kcats

1
EntityManager dapat digunakan untuk memberikan ID baru. Ini juga dapat digunakan untuk membuat entitas lengkap berdasarkan templat yang telah ditentukan sebelumnya (misalnya membuat "EnemyNinja" yang menghasilkan ID baru dan membuat semua komponen yang membentuk ninja musuh seperti yang dapat diurai, collission, AI, mungkin beberapa komponen untuk pertempuran jarak dekat , dll). Itu juga dapat memiliki fungsi removeEntity yang secara otomatis memanggil semua fungsi hapus ComponentManager. ComponentManager dapat memeriksa apakah memiliki data komponen untuk Entitas yang diberikan dan jika demikian, hapus data itu.
Mart

1
Memindahkan komponen dari satu entitas ke entitas lainnya? Cukup tambahkan fungsi swapComponentOwner (int oldEntity, int newEntity) ke setiap ComponentManager. Semua data ada di ComponentManager, yang Anda butuhkan hanyalah fungsi untuk mengubah pemiliknya. Setiap ComponentManager akan memiliki sesuatu seperti indeks atau peta untuk menyimpan data milik ID entitas mana. Cukup ganti ID entitas dari yang lama ke yang baru. Saya tidak yakin apakah berbagi komponen mudah dalam sistem yang saya pikirkan, tetapi seberapa sulitkah itu? Alih-alih satu Entitas ID <-> Tautan Data Komponen dalam tabel indeks ada beberapa.
Mart

3

Agar komponen ini berfungsi, mereka harus memperbarui setiap frame, cara termudah untuk melakukannya adalah dengan berjalan di atas pohon adegan dan kemudian untuk setiap entitas memperbarui setiap komponen.

Ini adalah pendekatan naif khas untuk pembaruan komponen (dan tidak ada yang salah dengan itu menjadi naif, jika berhasil untuk Anda). Salah satu masalah besar yang benar-benar Anda sentuh - Anda beroperasi melalui antarmuka komponen (misalnya IComponent) sehingga Anda tidak tahu apa yang baru saja Anda perbarui. Anda mungkin juga tidak tahu apa-apa tentang pemesanan komponen dalam entitas, jadi

  1. Anda cenderung sering memperbarui komponen dari berbagai jenis (kode lokal yang buruk, pada dasarnya)
  2. sistem ini tidak cocok untuk pembaruan bersamaan karena Anda tidak berada dalam posisi untuk mengidentifikasi dependensi data dan dengan demikian membagi pembaruan menjadi grup lokal dari objek yang tidak terkait.

Saya telah berpikir tentang menggunakan objek manajer singleton untuk masing-masing jenis komponen tetapi yang memiliki kelemahan biasa menggunakan singleton, cara untuk meringankan ini sedikit adalah dengan menggunakan injeksi ketergantungan tetapi kedengarannya seperti kerja keras yang berlebihan untuk masalah ini.

Singleton tidak benar-benar diperlukan di sini, jadi Anda harus menghindarinya karena itu membawa kerugian yang Anda sebutkan. Ketergantungan injeksi tidak berlebihan - inti dari konsep ini adalah bahwa Anda melewatkan hal-hal yang dibutuhkan suatu objek ke objek itu, idealnya dalam konstruktor. Anda tidak perlu kerangka DI kelas berat (seperti Ninject ) untuk ini - cukup berikan parameter tambahan ke konstruktor di suatu tempat.

Penyaji adalah sistem yang mendasar dan mungkin mendukung pembuatan dan pengelolaan masa pakai sekelompok objek yang dapat diurai yang berhubungan dengan hal-hal visual dalam gim Anda (sprite atau model, kemungkinan). Demikian pula, mesin fisika kemungkinan memiliki kontrol seumur hidup atas hal-hal yang mewakili entitas yang dapat bergerak dalam simulasi fisika (benda tegar). Masing-masing sistem yang relevan harus memiliki, dalam beberapa kapasitas, objek-objek tersebut dan bertanggung jawab untuk memperbaruinya.

Komponen yang Anda gunakan dalam sistem komposisi entitas permainan Anda seharusnya hanya menjadi pembungkus di sekitar contoh dari sistem tingkat yang lebih rendah - komponen posisi Anda hanya dapat membungkus tubuh yang kaku, komponen visual Anda hanya membungkus sprite atau model yang dapat diulang, dan lain-lain.

Kemudian sistem itu sendiri yang memiliki objek tingkat rendah bertanggung jawab untuk memperbarui mereka, dan dapat melakukannya secara massal dan dengan cara yang memungkinkan untuk melakukan multithread pembaruan itu jika sesuai. Loop game utama Anda mengontrol urutan kasar di mana sistem tersebut diperbarui (pertama-tama fisika, kemudian penyaji, atau apa pun). Jika Anda memiliki subsistem yang tidak memiliki masa pakai atau memperbarui kontrol atas instance yang dibagikan, Anda dapat membangun pembungkus sederhana untuk menangani pembaruan semua komponen yang relevan dengan sistem itu secara massal juga, dan memutuskan di mana harus menempatkan pembaruan relatif terhadap sisa pembaruan sistem Anda (ini sering terjadi, saya temukan, dengan komponen "skrip").

Pendekatan ini kadang-kadang dikenal sebagai pendekatan komponen tempel , jika Anda mencari lebih detail.

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.