OOP ECS vs ECS Murni


11

Pertama, saya sadar bahwa pertanyaan ini terkait dengan topik pengembangan game, tetapi saya telah memutuskan untuk menanyakannya di sini karena pertanyaan tersebut benar-benar mengarah ke masalah pembuatan perangkat lunak yang lebih umum.

Selama sebulan terakhir, saya telah membaca banyak tentang Entity-Component-Systems dan sekarang cukup nyaman dengan konsepnya. Namun, ada satu aspek yang tampaknya tidak memiliki 'definisi' yang jelas dan artikel yang berbeda menyarankan solusi yang sangat berbeda:

Ini adalah pertanyaan apakah ECS harus memecah enkapsulasi atau tidak. Dengan kata lain ini adalah OOP style ECS (komponen adalah objek dengan status dan behaivour yang merangkum data khusus untuk mereka) vs ECS murni (komponen adalah struct style c yang hanya memiliki data publik dan sistem menyediakan fungsionalitas).

Perhatikan bahwa saya mengembangkan Framework / API / Engine. Jadi tujuannya adalah agar dapat dengan mudah diperpanjang oleh siapa pun yang menggunakannya. Ini termasuk hal-hal seperti menambahkan tipe baru render atau komponen collision.

Masalah dengan pendekatan OOP

  • Komponen harus mengakses data komponen lainnya. Misalnya metode draw komponen render harus mengakses posisi komponen transform. Ini menciptakan dependensi dalam kode.

  • Komponen dapat berupa polimorfik yang lebih jauh memperkenalkan kompleksitas. Misalnya Mungkin ada komponen render sprite yang menimpa metode menggambar virtual komponen render.

Masalah dengan pendekatan murni

  • Karena behaivour polimorfik (misalnya untuk rendering) harus diimplementasikan di suatu tempat, ia hanya di-outsource ke dalam sistem. (misalnya sistem render sprite membuat simpul render sprite yang mewarisi simpul render dan menambahkannya ke mesin render)

  • Komunikasi antar sistem bisa sulit dihindari. Misalnya sistem tumbukan mungkin memerlukan kotak pembatas yang dihitung dari komponen render beton apa pun yang ada. Ini dapat diatasi dengan membiarkan mereka berkomunikasi melalui data. Namun, ini menghapus pembaruan instan karena sistem render akan memperbarui komponen kotak pembatas dan sistem tumbukan kemudian akan menggunakannya. Ini dapat menyebabkan masalah jika urutan fungsi fungsi pembaruan sistem tidak ditentukan. Ada sistem acara di tempat yang memungkinkan sistem untuk meningkatkan acara dimana sistem lain dapat berlangganan penangan mereka. Namun, ini hanya berfungsi untuk memberi tahu sistem apa yang harus dilakukan yaitu fungsi batal.

  • Ada beberapa bendera tambahan yang dibutuhkan. Ambil komponen peta ubin misalnya. Itu akan memiliki ukuran, ukuran ubin dan bidang daftar indeks. Sistem peta petak akan menangani masing-masing array vertex dan menetapkan koordinat tekstur berdasarkan data komponen. Namun, menghitung ulang seluruh tilemap setiap frame mahal. Oleh karena itu, daftar akan diperlukan untuk melacak semua perubahan yang dilakukan untuk kemudian memperbaruinya di sistem. Dengan cara OOP ini bisa dienkapsulasi oleh komponen peta ubin. Misalnya metode SetTile () akan memperbarui array vertex setiap kali dipanggil.

Meskipun saya melihat keindahan dari pendekatan yang murni, saya tidak benar-benar mengerti apa manfaat konkret yang akan didapat dibandingkan OOP yang lebih tradisional. Ketergantungan antara komponen masih ada meskipun disembunyikan dalam sistem. Saya juga membutuhkan lebih banyak kelas untuk mencapai tujuan yang sama. Bagi saya ini seperti solusi yang agak over-engineering yang tidak pernah merupakan hal yang baik.

Selain itu, saya tidak terlalu terpikat dalam kinerja sehingga seluruh gagasan desain berorientasi data dan kehilangan uang tunai ini tidak terlalu penting bagi saya. Saya hanya ingin arsitektur yang bagus ^^

Namun, sebagian besar artikel dan diskusi yang saya baca menyarankan pendekatan kedua. MENGAPA?

Animasi

Terakhir, saya ingin mengajukan pertanyaan tentang bagaimana saya akan menangani animasi dalam ECS murni. Saat ini saya telah mendefinisikan animasi sebagai functor yang memanipulasi entitas berdasarkan beberapa kemajuan antara 0 dan 1. Komponen animasi memiliki daftar animator yang memiliki daftar animasi. Dalam fungsi pembaruannya itu berlaku animasi apa pun yang sedang aktif ke entitas.

catatan:

Saya baru saja membaca posting ini Apakah objek Arsitektur Sistem Entitas Komponen berorientasi pada definisi? yang menjelaskan masalah sedikit lebih baik daripada yang saya lakukan. Meskipun pada dasarnya berada di topik yang sama masih tidak memberikan jawaban mengapa pendekatan data murni lebih baik.


1
Mungkin pertanyaan sederhana namun serius: apakah Anda tahu kelebihan / kekurangan ECS? Itu kebanyakan menjelaskan 'mengapa'.
Caramiriel

Yah, saya mengerti keuntungan menggunakan komponen yaitu komposisi daripada pewarisan untuk menghindari berlian kematian melalui beberapa pewarisan dll. Menggunakan komponen juga memungkinkan untuk memanipulasi behaivour saat runtime. Dan mereka modular. Yang tidak saya mengerti adalah mengapa membagi data dan fungsi dibutuhkan. Implementasi saya saat ini ada di github github.com/AdrianKoch3010/MarsBaseProject
Adrian Koch

Yah saya belum punya pengalaman yang cukup dengan ECS untuk menambahkan jawaban lengkap. Tetapi komposisi tidak hanya digunakan untuk menghindari DoD; Anda juga bisa membuat entitas (unik) saat runtime yang sulit (er) untuk dihasilkan menggunakan pendekatan OO. Yang mengatakan, pemisahan data / prosedur memungkinkan data menjadi lebih mudah untuk dipikirkan. Anda dapat mengimplementasikan serialisasi, menyimpan keadaan, membatalkan / mengulang, dan hal-hal seperti itu dengan cara yang mudah. Karena alasannya yang mudah tentang data, juga lebih mudah untuk mengoptimalkannya. Anda kemungkinan besar dapat membagi entitas dalam batch (multithreading) atau bahkan melepasnya ke perangkat keras lain untuk mendapatkan potensi penuhnya.
Caramiriel

"Mungkin ada komponen render sprite yang menimpa metode draw virtual komponen render." Saya berpendapat bahwa ini bukan ECS lagi jika Anda / memerlukannya.
wonderra

Jawaban:


10

Ini yang sulit. Saya hanya akan mencoba menangani beberapa pertanyaan berdasarkan pengalaman khusus saya (YMMV):

Komponen harus mengakses data komponen lainnya. Misalnya metode draw komponen render harus mengakses posisi komponen transform. Ini menciptakan dependensi dalam kode.

Jangan meremehkan jumlah dan kompleksitas (bukan derajat) dari kopling / dependensi di sini. Anda bisa melihat perbedaan antara ini (dan diagram ini sudah sangat disederhanakan menjadi level seperti mainan, dan contoh dunia nyata akan memiliki antarmuka di antara untuk melonggarkan kopling):

masukkan deskripsi gambar di sini

... dan ini:

masukkan deskripsi gambar di sini

... atau ini:

masukkan deskripsi gambar di sini

Komponen dapat berupa polimorfik yang lebih jauh memperkenalkan kompleksitas. Misalnya Mungkin ada komponen render sprite yang menimpa metode menggambar virtual komponen render.

Begitu? Setara analog (atau literal) dari pengiriman vtable dan virtual dapat dipanggil melalui sistem daripada objek yang menyembunyikan keadaan / data yang mendasarinya. Polimorfisme masih sangat praktis dan layak dengan implementasi ECS "murni" ketika vtable analog atau penunjuk fungsi berubah menjadi semacam "data" untuk dipanggil oleh sistem.

Karena behaivour polimorfik (misalnya untuk rendering) harus diimplementasikan di suatu tempat, ia hanya di-outsource ke dalam sistem. (misalnya sistem render sprite membuat simpul render sprite yang mewarisi simpul render dan menambahkannya ke mesin render)

Begitu? Saya harap ini tidak muncul sebagai sarkasme (bukan maksud saya meskipun saya sering dituduh melakukannya tetapi saya berharap saya dapat berkomunikasi emosi lebih baik melalui teks), tetapi "outsourcing" perilaku polimorfik dalam kasus ini tidak selalu menimbulkan tambahan biaya untuk produktivitas.

Komunikasi antar sistem bisa sulit dihindari. Misalnya sistem tumbukan mungkin memerlukan kotak pembatas yang dihitung dari komponen render beton apa pun yang ada.

Contoh ini tampaknya sangat aneh bagi saya. Saya tidak tahu mengapa penyaji akan mengeluarkan data kembali ke tempat kejadian (saya biasanya menganggap penyaji hanya baca dalam konteks ini), atau agar penyaji mencari tahu AABB alih-alih sistem lain untuk melakukan ini untuk penyaji dan tabrakan / fisika (saya mungkin akan terpaku pada nama "komponen render" di sini). Namun saya tidak ingin terlalu terpaku pada contoh ini karena saya sadar bukan itu yang ingin Anda sampaikan. Tetap saja komunikasi antar sistem (bahkan dalam bentuk tidak langsung membaca / menulis ke basis data ECS pusat dengan sistem yang tergantung langsung pada transformasi yang dibuat oleh orang lain) tidak perlu sering, jika perlu. Bahwa'

Ini dapat menyebabkan masalah jika urutan fungsi fungsi pembaruan sistem tidak ditentukan.

Ini mutlak harus didefinisikan. ECS bukan solusi akhir semua untuk mengatur ulang urutan evaluasi pemrosesan sistem dari setiap sistem yang mungkin dalam basis kode dan mendapatkan kembali hasil yang persis sama dengan pengguna akhir yang berurusan dengan frame dan FPS. Ini adalah salah satu hal, ketika merancang ECS, yang setidaknya saya sarankan sangat harus diantisipasi agak awal (meskipun dengan banyak ruang pernapasan memaafkan untuk mengubah pikiran kemudian asalkan tidak mengubah aspek paling kritis dari pemesanan doa / evaluasi sistem).

Namun, menghitung ulang seluruh tilemap setiap frame mahal. Oleh karena itu, daftar akan diperlukan untuk melacak semua perubahan yang dilakukan untuk kemudian memperbaruinya di sistem. Dengan cara OOP ini bisa dienkapsulasi oleh komponen peta ubin. Misalnya metode SetTile () akan memperbarui array vertex setiap kali dipanggil.

Saya tidak begitu mengerti hal ini kecuali bahwa ini adalah masalah yang berorientasi pada data. Dan tidak ada jebakan untuk merepresentasikan dan menyimpan data dalam ECS, termasuk memoisasi, untuk menghindari jebakan kinerja seperti itu (yang terbesar dengan ECS cenderung berhubungan dengan hal-hal seperti permintaan sistem untuk contoh yang tersedia dari jenis komponen tertentu yang merupakan salah satu aspek yang paling menantang dalam mengoptimalkan ECS umum). Fakta bahwa logika dan data dipisahkan dalam ECS "murni" tidak berarti Anda tiba-tiba harus menghitung ulang hal-hal yang seharusnya bisa di-cache / memo dalam representasi OOP. Itu poin yang bisa diperdebatkan / tidak relevan kecuali saya membahas sesuatu yang sangat penting.

Dengan ECS "murni" Anda masih dapat menyimpan data ini di komponen peta ubin. Satu-satunya perbedaan utama adalah bahwa logika untuk memperbarui array vertex ini akan pindah ke sistem di suatu tempat.

Anda bahkan dapat bersandar pada ECS untuk menyederhanakan pembatalan dan penghapusan cache ini dari entitas jika Anda membuat komponen terpisah seperti TileMapCache. Pada titik itu ketika cache diinginkan tetapi tidak tersedia dalam suatu entitas dengan TileMapkomponen, Anda dapat menghitung dan menambahkannya. Ketika tidak valid atau tidak lagi diperlukan, Anda dapat menghapusnya melalui ECS tanpa harus menulis kode lebih khusus untuk pembatalan dan penghapusan tersebut.

Ketergantungan antara komponen masih ada meskipun disembunyikan dalam sistem

Tidak ada ketergantungan antara komponen dalam rep "murni" (saya tidak berpikir itu benar untuk mengatakan bahwa dependensi disembunyikan di sini oleh sistem). Data tidak tergantung pada data, jadi untuk berbicara. Logika tergantung pada logika. Dan ECS "murni" cenderung mempromosikan logika yang akan ditulis sedemikian rupa sehingga bergantung pada subset minimal absolut dari data dan logika (seringkali tidak ada) yang dibutuhkan sistem untuk bekerja, yang tidak seperti banyak alternatif yang sering mendorong bergantung pada jauh lebih banyak fungsi daripada yang dibutuhkan untuk tugas yang sebenarnya. Jika Anda menggunakan ECS benar murni, salah satu hal pertama yang harus Anda hargai adalah manfaat decoupling sambil secara bersamaan mempertanyakan semua yang pernah Anda pelajari untuk menghargai dalam OOP tentang enkapsulasi dan secara khusus menyembunyikan informasi.

Dengan decoupling secara khusus saya maksudkan betapa sedikitnya informasi yang dibutuhkan sistem Anda untuk bekerja. Sistem gerak Anda bahkan tidak perlu tahu tentang sesuatu yang jauh lebih kompleks seperti Particleatau Character(pengembang sistem bahkan tidak perlu tahu ide entitas seperti itu bahkan ada di sistem). Itu hanya perlu tahu tentang data minimum telanjang seperti komponen posisi yang bisa sesederhana beberapa mengapung dalam sebuah struct. Ini lebih sedikit informasi dan lebih sedikit ketergantungan eksternal daripada apa yang IMotioncenderung dibawa oleh antarmuka murni . Ini terutama karena pengetahuan minimal ini bahwa setiap sistem mengharuskan untuk bekerja yang membuat ECS sering jadi pemaaf untuk menangani perubahan desain yang sangat tak terduga dalam belakang tanpa menghadapi kerusakan antarmuka cascading di semua tempat.

Pendekatan "tidak murni" yang Anda sarankan agak mengurangi manfaat itu karena sekarang logika Anda tidak dilokalisasi secara ketat ke sistem di mana perubahan tidak menyebabkan kerusakan berjenjang. Logikanya sekarang akan terpusat pada tingkat tertentu dalam komponen yang diakses oleh beberapa sistem yang sekarang harus memenuhi persyaratan antarmuka dari semua berbagai sistem yang dapat menggunakannya, dan sekarang itu seperti setiap sistem kemudian perlu memiliki pengetahuan tentang (tergantung pada) lebih informasi yang dibutuhkan untuk bekerja dengan komponen itu.

Ketergantungan pada Data

Salah satu hal yang kontroversial tentang ECS ​​adalah bahwa ia cenderung untuk menggantikan apa yang seharusnya menjadi dependensi untuk antarmuka abstrak hanya dengan data mentah, dan itu umumnya dianggap sebagai bentuk kopling yang kurang diinginkan dan lebih ketat. Tetapi dalam jenis domain seperti game di mana ECS bisa sangat bermanfaat, seringkali lebih mudah untuk merancang representasi data di muka dan menjaganya tetap stabil daripada mendesain apa yang dapat Anda lakukan dengan data itu di beberapa tingkat pusat sistem. Itu sesuatu yang saya amati dengan menyakitkan bahkan di antara para veteran berpengalaman dalam basis kode yang menggunakan lebih dari pendekatan antarmuka murni gaya COM dengan hal-hal seperti IMotion.

Para pengembang terus menemukan alasan untuk menambah, menghapus, atau mengubah fungsi ke antarmuka pusat ini, dan setiap perubahan itu mengerikan dan mahal karena akan cenderung memecah setiap kelas yang diimplementasikan IMotionbersama dengan setiap tempat sejak dalam sistem yang digunakan IMotion. Sementara itu sepanjang waktu dengan begitu banyak perubahan yang menyakitkan dan mengalir, objek yang diimplementasikan IMotionsemuanya hanya menyimpan matriks 4x4 float dan seluruh antarmuka hanya peduli dengan bagaimana mengubah dan mengakses float tersebut; representasi data stabil sepanjang jalan dari awal, dan banyak rasa sakit bisa dihindari jika antarmuka terpusat ini, sehingga cenderung berubah dengan kebutuhan desain yang tidak terduga, bahkan tidak ada di tempat pertama.

Ini semua bisa terdengar hampir sama menjijikkannya seperti variabel global tetapi sifat bagaimana ECS mengatur data ini menjadi komponen yang diambil secara eksplisit berdasarkan jenis melalui sistem membuatnya begitu, sementara kompiler tidak dapat menegakkan apa pun seperti menyembunyikan informasi, tempat-tempat yang mengakses dan bermutasi data umumnya sangat eksplisit dan cukup jelas untuk tetap mempertahankan invarian secara efektif dan memprediksi jenis transformasi dan efek samping apa yang terjadi dari satu sistem ke sistem berikutnya (sebenarnya dengan cara yang bisa dibilang lebih sederhana dan lebih dapat diprediksi daripada OOP dalam domain tertentu mengingat bagaimana sistem berubah menjadi semacam pipeline datar).

masukkan deskripsi gambar di sini

Terakhir, saya ingin mengajukan pertanyaan tentang bagaimana saya akan menangani animasi dalam ECS murni. Saat ini saya telah mendefinisikan animasi sebagai functor yang memanipulasi entitas berdasarkan beberapa kemajuan antara 0 dan 1. Komponen animasi memiliki daftar animator yang memiliki daftar animasi. Dalam fungsi pembaruannya itu berlaku animasi apa pun yang sedang aktif ke entitas.

Kita semua pragmatis di sini. Bahkan di gamedev Anda mungkin akan mendapatkan ide / jawaban yang bertentangan. Bahkan ECS murni adalah fenomena yang relatif baru, wilayah perintis, di mana orang belum tentu merumuskan pendapat terkuat tentang cara merawat kulit kucing. Reaksi usus saya adalah sistem animasi yang meningkatkan kemajuan animasi semacam ini dalam komponen animasi untuk ditampilkan oleh sistem rendering, tetapi itu mengabaikan begitu banyak nuansa untuk aplikasi dan konteks tertentu.

Dengan ECS itu bukan peluru perak dan saya masih menemukan kecenderungan untuk masuk dan menambahkan sistem baru, menghapus beberapa, menambahkan komponen baru, mengubah sistem yang ada untuk mengambil jenis komponen baru, dll. Saya tidak mendapatkan hal yang benar sama sekali pertama kali masih ada. Tetapi perbedaan dalam kasus saya adalah bahwa saya tidak mengubah apa pun yang penting ketika saya gagal mengantisipasi kebutuhan desain tertentu di muka. Saya tidak mendapatkan efek riak dari kerusakan berjenjang yang mengharuskan saya untuk pergi ke mana-mana dan mengubah begitu banyak kode untuk menangani beberapa kebutuhan baru yang muncul, dan itu cukup menghemat waktu. Saya juga merasa lebih mudah di otak saya karena ketika saya duduk dengan sistem tertentu, saya tidak perlu tahu / ingat banyak tentang hal lain selain komponen yang relevan (yang hanya data) untuk mengerjakannya.

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.