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.
sumber
Jawaban:
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:
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):
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:
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.
sumber
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, jadiSingleton 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.
sumber