Komponen Game, Manajer Game, dan Properti Obyek


15

Saya mencoba untuk mendapatkan kepala saya di sekitar desain entitas berbasis komponen.

Langkah pertama saya adalah membuat berbagai komponen yang bisa ditambahkan ke objek. Untuk setiap jenis komponen saya memiliki manajer, yang akan memanggil fungsi pembaruan setiap komponen, melewati hal-hal seperti keadaan keyboard dll sesuai kebutuhan.

Hal berikutnya yang saya lakukan adalah menghapus objek, dan hanya memiliki setiap komponen dengan ID. Jadi suatu objek didefinisikan oleh komponen yang memiliki Id yang sama.

Sekarang, saya berpikir bahwa saya tidak memerlukan manajer untuk semua komponen saya, misalnya saya punya SizeComponent, yang hanya memiliki Sizeproperti). Akibatnya SizeComponenttidak ada metode pembaruan, dan metode pembaruan manajer tidak melakukan apa pun.

Pikiran pertama saya adalah memiliki ObjectPropertykelas yang dapat ditanyakan oleh komponen, alih-alih menjadikannya sebagai properti komponen. Jadi suatu objek akan memiliki sejumlah ObjectPropertydan ObjectComponent. Komponen akan memiliki logika pembaruan yang menanyakan objek untuk properti. Manajer akan mengelola memanggil metode pembaruan komponen.

Ini kelihatannya seperti over-engineering bagi saya, tetapi saya tidak berpikir saya bisa menyingkirkan komponen, karena saya memerlukan cara bagi para manajer untuk mengetahui objek apa yang perlu logika komponen apa yang harus dijalankan (jika tidak saya hanya akan menghapus komponen sepenuhnya dan dorong logikanya pembaruan ke manajer).

  1. Apakah ini (memiliki ObjectProperty, ObjectComponentdan ComponentManagerkelas) over-engineering?
  2. Apa yang akan menjadi alternatif yang baik?

1
Anda memiliki ide yang tepat dengan mencoba mempelajari model komponen, tetapi Anda perlu mendapatkan pemahaman yang lebih baik tentang apa yang perlu dilakukan - dan satu-satunya cara untuk melakukannya adalah [kebanyakan] menyelesaikan permainan tanpa menggunakannya. Saya pikir membuat terlalu SizeComponentbanyak - Anda dapat mengasumsikan bahwa sebagian besar objek memiliki ukuran - itu hal-hal seperti rendering, AI dan fisika di mana model komponen digunakan; Ukuran akan selalu berperilaku sama - sehingga Anda dapat membagikan kode itu.
Jonathan Dickinson


@JonathanDickinson, @Den: Saya pikir, maka masalah saya adalah di mana saya menyimpan properti umum. Misalnya objek sebagai posisi, yang digunakan oleh a RenderingComponentdan a PhysicsComponent. Apakah saya terlalu memikirkan keputusan untuk meletakkan properti itu? Haruskah saya hanya memasukkannya ke dalam salah satu, lalu minta objek lain untuk komponen yang memiliki properti yang dibutuhkan?
George Duckett

Komentar saya sebelumnya, dan proses pemikiran di baliknya adalah yang mendorong saya untuk memiliki kelas terpisah untuk properti (atau sekelompok properti terkait mungkin) yang dapat ditanyakan oleh komponen.
George Duckett

1
Saya sangat menyukai ide itu - mungkin patut dicoba; tetapi memiliki objek untuk menggambarkan masing-masing properti sangat mahal. Anda dapat mencoba PhysicalStateInstance(satu per objek) di samping GravityPhysicsShared(satu per game); namun saya tergoda untuk mengatakan ini merambah ke ranah euforia arsitek, jangan membuat diri Anda berlubang (persis seperti yang saya lakukan dengan sistem komponen pertama saya). CIUMAN.
Jonathan Dickinson

Jawaban:


6

Jawaban sederhana untuk pertanyaan pertama Anda adalah Ya, Anda sudah selesai merancang desain. 'Seberapa jauh saya memecah hal-hal?' pertanyaan sangat umum ketika langkah berikutnya diambil dan objek pusat (biasanya disebut Entitas) dihapus.

Ketika Anda mendobrak objek ke tingkat terperinci sehingga memiliki ukuran sendiri maka desainnya sudah terlalu jauh. Nilai data sendiri bukan komponen. Ini adalah tipe data bawaan dan sering dapat disebut dengan tepat apa yang Anda sebut sebagai properti. Properti bukan komponen, tetapi komponen yang mengandung properti.

Jadi, berikut adalah beberapa garis panduan yang saya coba dan ikuti ketika mengembangkan dalam sistem komponen:

  • Tidak ada sendok.
    • Ini adalah langkah yang telah Anda ambil untuk menyingkirkan objek utama. Ini menghapus seluruh perdebatan tentang apa yang masuk ke objek Entity dan apa yang masuk ke dalam komponen karena sekarang semua yang Anda miliki adalah komponen.
  • Komponen bukan struktur
    • Jika Anda memecah sesuatu ke tempat itu hanya berisi data maka itu bukan komponen lagi, itu hanya struktur data.
    • Sebuah komponen harus mengandung semua fungsi yang diperlukan untuk menyelesaikan tugas yang sangat spesifik dengan cara tertentu.
    • Antarmuka IRenderable memberikan solusi umum untuk menampilkan apa pun secara visual dalam game. CRenderableSprite dan CRenderableModel adalah implementasi komponen dari antarmuka yang menyediakan spesifikasi untuk di-render masing-masing dalam 2D ​​dan 3D.
    • IUseable adalah antarmuka untuk sesuatu yang dapat berinteraksi dengan pemain. CUseableItem akan menjadi komponen yang menembakkan pistol aktif atau meminum ramuan yang dipilih sedangkan CUseableTrigger bisa menjadi tempat pemain untuk melompat ke menara atau melemparkan tuas untuk menjatuhkan jembatan gantung.

Jadi dengan pedoman komponen tidak menjadi struktur, SizeComponent telah dipecah terlalu jauh. Ini hanya berisi data dan apa yang menentukan ukuran sesuatu dapat bervariasi. Misalnya, dalam komponen rendering itu bisa berupa skalar 1d atau vektor 2 / 3d. Dalam komponen fisika, itu bisa menjadi volume pembatas objek. Dalam item inventaris, bisa jadi berapa banyak ruang yang digunakan pada kisi 2D.

Coba dan tarik garis yang baik antara teori dan kepraktisan.

Semoga ini membantu.


Jangan lupa bahwa pada beberapa platform, memanggil fungsi dari Antarmuka lebih lama daripada memanggil satu dari Kelas induk (karena jawaban Anda termasuk menyebutkan antarmuka dan kelas)
ADB

Poin yang baik untuk diingat, tetapi saya mencoba untuk tetap agnostik bahasa dan hanya menggunakannya dalam istilah desain umum.
James

"Jika Anda memecah sesuatu ke tempat itu hanya berisi data maka itu bukan komponen lagi, itu hanya struktur data." - Kenapa? "Komponen" adalah kata umum yang dapat berarti struktur data juga.
Paul Manta

@ PaulManta Ya itu adalah istilah umum, tetapi inti dari pertanyaan dan jawaban ini adalah di mana harus menarik garis. Jawaban saya, seperti yang Anda kutip, hanyalah saran saya untuk aturan praktis untuk melakukan hal itu. Seperti biasa saya menganjurkan tidak pernah membiarkan pertimbangan teori atau desain apa yang mendorong pembangunan, itu dimaksudkan untuk membantunya.
James

1
@ James Itu diskusi yang menarik. :) chat.stackexchange.com/rooms/2175 Keluhan terbesar saya jika implementasi Anda adalah bahwa komponen tahu terlalu banyak tentang komponen lain yang tertarik. Saya ingin melanjutkan diskusi di waktu mendatang.
Paul Manta

14

Anda sudah menerima jawaban, tetapi inilah tikaman saya di CBS. Saya menemukan bahwa Componentkelas generik memiliki beberapa keterbatasan, jadi saya pergi dengan desain yang dijelaskan oleh Radical Entertainment di GDC 2009, yang menyarankan pemisahan komponen menjadi Attributesdan Behaviors. (" Teori dan Praktek Arsitektur Komponen Objek Permainan ", Marcin Chady)

Saya menjelaskan keputusan desain saya dalam dokumen dua halaman. Saya hanya akan memposting tautan karena terlalu lama untuk menempelkan semuanya di sini. Saat ini hanya mencakup komponen logika (bukan komponen rendering dan fisika juga), tetapi harus memberi Anda gambaran tentang apa yang saya coba lakukan:

http://www.pdf-archive.com/2012/01/08/entity-component-system/preview/page/1

Berikut kutipan dari dokumen:

Atribut dan Perilaku Singkatnya

Attributesmengelola satu kategori data dan logika apa pun yang mereka miliki terbatas dalam ruang lingkup. Misalnya, Healthmungkin memastikan bahwa nilai saat ini tidak pernah lebih besar dari nilai maksimumnya, dan bahkan dapat memberi tahu komponen lain ketika nilai saat ini turun di bawah tingkat kritis tertentu, tetapi tidak mengandung logika yang lebih rumit. Attributestidak tergantung pada yang lain Attributesatau Behaviors.

Behaviorsmengendalikan bagaimana entitas bereaksi terhadap peristiwa permainan, membuat keputusan, dan mengubah nilai-nilai yang Attributesdiperlukan. Behaviorstergantung pada beberapa dari Attributes, tetapi mereka tidak bisa langsung berinteraksi dengan satu sama lain - mereka hanya bereaksi terhadap bagaimana Attributes’nilai-nilai yang diubah oleh yang lain Behaviorsdan untuk acara mereka dikirim.


Sunting: Dan inilah diagram hubungan yang menunjukkan bagaimana komponen berkomunikasi satu sama lain:

Diagram komunikasi antara Atribut dan Perilaku

Detail implementasi: level entitas EventManagerhanya dibuat jika digunakan. The Entitykelas hanya menyimpan pointer ke EventManageryang diinisialisasi hanya jika beberapa permintaan komponen itu.


Sunting: Pada pertanyaan lain saya memberikan jawaban yang mirip dengan yang ini. Anda dapat menemukannya di sini untuk penjelasan yang lebih baik tentang sistem:
/gamedev//a/23759/6188


2

Ini benar-benar tergantung pada properti yang Anda butuhkan dan di mana Anda membutuhkannya. Jumlah memori yang akan Anda miliki dan kekuatan / jenis pemrosesan yang akan Anda gunakan. Saya telah melihat dan mencoba melakukan hal berikut:

  • Properti yang digunakan oleh banyak komponen tetapi hanya dimodifikasi oleh satu disimpan dalam komponen itu. Bentuk adalah contoh yang baik dalam permainan di mana sistem AI, sistem fisika serta sistem rendering memerlukan akses ke bentuk dasar, itu adalah properti yang berat dan harus tetap hanya di satu tempat jika memungkinkan.
  • Properti seperti posisi terkadang perlu diduplikasi. Misalnya jika Anda menjalankan beberapa sistem secara paralel, Anda ingin menghindari mengintip di seluruh sistem dan lebih suka menyinkronkan posisi (menyalin dari komponen master atau menyinkronkan melalui delta atau dengan collision pass jika diperlukan).
  • Properti yang berasal dari kontrol atau "niat" AI dapat disimpan dalam sistem khusus karena dapat diterapkan ke sistem lain tanpa terlihat dari luar.
  • Properti sederhana bisa menjadi rumit. Terkadang posisi Anda akan memerlukan sistem khusus jika Anda perlu berbagi banyak data (posisi, orientasi, frame delta, total pergerakan delta saat ini, gerakan delta untuk frame saat ini dan untuk frame sebelumnya, rotasi ...). Dalam hal ini Anda harus pergi dengan sistem dan mengakses data terbaru dari komponen khusus dan Anda mungkin harus mengubahnya melalui akumulator (delta).
  • Kadang-kadang properti Anda dapat disimpan dalam array mentah (ganda *) dan komponen Anda hanya akan memiliki pointer ke array yang memegang properti yang berbeda. Contoh paling jelas adalah ketika Anda membutuhkan perhitungan paralel besar-besaran (CUDA, OpenCL). Jadi memiliki satu sistem untuk mengelola pointer dengan benar mungkin mudah.

Prinsip-prinsip ini memiliki keterbatasan. Tentu saja Anda harus mendorong geometri ke renderer tetapi Anda mungkin tidak ingin mengambilnya dari sana. Master geometri akan disimpan dalam mesin fisika dalam kasus di mana deformasi terjadi di sana dan disinkronkan dengan renderer (dari waktu ke waktu tergantung pada jarak objek). Jadi dengan cara apa pun Anda akan menduplikasinya.

Tidak ada sistem yang sempurna. Dan beberapa permainan akan lebih baik dengan sistem yang lebih sederhana sementara yang lain akan membutuhkan sinkronisasi yang lebih kompleks di seluruh sistem.

Pada awalnya pastikan semua properti dapat diakses secara sederhana dari komponen Anda sehingga Anda dapat mengubah cara Anda menyimpan properti secara transparan setelah Anda mulai memperbaiki sistem Anda.

Tidak ada salahnya menyalin beberapa properti. Jika beberapa komponen perlu memiliki salinan lokal, terkadang lebih efisien untuk menyalin dan menyinkronkan daripada mengakses nilai "eksternal"

Sinkronisasi juga tidak harus terjadi setiap frame. Beberapa komponen dapat disinkronkan lebih jarang daripada yang lain. Komponen render seringkali merupakan contoh yang baik. Yang tidak berinteraksi dengan pemain dapat disinkronkan lebih jarang, sama seperti yang jauh. Yang jauh dan di luar bidang kamera dapat disinkronkan bahkan lebih jarang.


Ketika datang ke komponen ukuran Anda mungkin dapat dibundel dalam komponen posisi Anda:

  • tidak semua entitas dengan ukuran memiliki komponen fisika, area misalnya, jadi menggabungkannya dengan fisika bukanlah yang terbaik bagi Anda.
  • Ukuran mungkin tidak akan menjadi masalah tanpa posisi
  • semua objek dengan posisi mungkin akan memiliki ukuran (yang dapat digunakan untuk skrip, fisika, AI, render ...).
  • ukurannya mungkin tidak diperbarui setiap siklus tetapi posisinya mungkin.

Alih-alih ukuran Anda bahkan bisa menggunakan pengubah ukuran, itu mungkin datang lebih mudah.

Untuk menyimpan semua properti dalam sistem penyimpanan properti generik ... Saya tidak yakin Anda menuju ke arah yang benar ... Fokus pada properti yang merupakan inti permainan Anda dan buat komponen yang menggabungkan sebanyak mungkin properti terkait. Selama Anda abstrak akses ke properti ini dengan benar (melalui getter dalam komponen yang membutuhkannya misalnya), Anda harus dapat memindahkan, menyalin, dan menyinkronkannya nanti, tanpa melanggar terlalu banyak logika.


2
BTW +1 atau -1 saya karena perwakilan saya saat ini adalah 666 sejak tanggal 9 November ... Ini menyeramkan.
Coyote

1

Jika komponen dapat ditambahkan secara sewenang-wenang ke entitas, maka Anda perlu cara untuk menanyakan apakah komponen tertentu ada dalam suatu entitas dan untuk mendapatkan referensi. Jadi, Anda mungkin mengulangi daftar objek yang berasal dari ObjectComponent hingga Anda menemukan yang Anda inginkan, dan mengembalikannya. Tetapi Anda akan mengembalikan objek dengan tipe yang benar.

Dalam C ++ atau C # ini biasanya berarti Anda akan memiliki metode templat pada entitas seperti T GetComponent<T>(). Dan begitu Anda memiliki referensi itu, Anda tahu persis data anggota apa yang dimilikinya, jadi akses saja secara langsung.

Dalam sesuatu seperti Lua atau Python Anda tidak perlu memiliki jenis objek yang eksplisit, dan mungkin juga tidak peduli. Tetapi sekali lagi, Anda bisa mengakses variabel anggota dan menangani pengecualian yang muncul karena mencoba mengakses sesuatu yang tidak ada.

Menanyakan properti objek secara eksplisit terdengar seperti menduplikasi pekerjaan yang dapat dilakukan bahasa untuk Anda, baik pada waktu kompilasi untuk bahasa yang diketik secara statis atau pada saat dijalankan untuk yang diketik secara dinamis.


Saya mengerti tentang mendapatkan komponen yang sangat diketik dari suatu entitas (menggunakan obat generik atau semacamnya), pertanyaan saya adalah lebih lanjut tentang ke mana properti itu harus pergi, terutama di mana properti digunakan oleh banyak komponen dan tidak ada komponen tunggal yang dapat dikatakan memilikinya. Lihat komentar saya ke-3 dan ke-4 tentang pertanyaan itu.
George Duckett

Pilih saja yang sudah ada jika cocok, atau faktor properti menjadi komponen baru jika tidak. Misalnya Unity memiliki komponen 'Transform' yang hanya posisi, dan jika ada yang perlu mengubah posisi objek, mereka melakukannya melalui komponen itu.
Kylotan

1

"Saya pikir, maka masalah saya adalah di mana saya menyimpan properti umum. Misalnya objek sebagai posisi, yang digunakan oleh Komponen Rendering dan Komponen Fisika. Apakah saya terlalu memikirkan keputusan tempat meletakkan properti? Haruskah saya tetap menempel di salah satu, lalu memiliki permintaan lain objek untuk komponen yang memiliki properti yang dibutuhkan? "

Masalahnya adalah bahwa RenderingComponent menggunakan posisi, tetapi PhysicsComponent menyediakannya . Anda hanya perlu cara untuk memberi tahu setiap komponen pengguna penyedia mana yang digunakan. Idealnya dengan cara agnostik, kalau tidak akan ada ketergantungan.

"... pertanyaan saya adalah lebih lanjut tentang ke mana properti-properti itu seharusnya pergi, khususnya di mana sebuah properti digunakan oleh banyak komponen dan tidak ada komponen tunggal yang bisa dikatakan memilikinya. Lihat komentar saya yang ketiga dan keempat pada pertanyaan itu."

Tidak ada aturan umum. Tergantung pada properti tertentu (lihat di atas).

Buat game dengan arsitektur berbasis komponen yang jelek tapi kemudian refactor.


Saya pikir saya tidak mengerti apa yang PhysicsComponentharus saya lakukan. Saya melihatnya mengelola simulasi objek dalam lingkungan fisik, yang membawa saya ke kebingungan ini: Tidak semua hal yang perlu dirender perlu disimulasikan, jadi sepertinya salah jika saya menambahkan PhysicsComponentketika saya menambahkan RenderingComponentkarena mengandung posisi yang RenderingComponentmenggunakan. Saya dapat dengan mudah melihat diri saya berakhir dengan web komponen yang saling terhubung, artinya semua / sebagian besar perlu ditambahkan ke setiap entitas.
George Duckett

Sebenarnya saya punya situasi yang sama :). Saya memiliki PhysicsBodyComponent dan SimpleSpatialComponent. Keduanya menyediakan Posisi, Sudut dan Ukuran. Tapi yang pertama berpartisipasi dalam simulasi fisika dan memiliki sifat relevan tambahan, dan yang kedua hanya memegang data spasial itu. Jika Anda memiliki mesin fisik sendiri, Anda bahkan dapat mewarisi yang pertama dari yang terakhir.
Den

"Saya dapat dengan mudah melihat diri saya berakhir dengan web komponen yang saling terhubung, artinya semua / sebagian besar perlu ditambahkan ke setiap entitas." Itu karena Anda tidak memiliki prototipe permainan yang sebenarnya. Kita berbicara tentang beberapa komponen dasar di sini. Tidak heran mereka akan digunakan di mana-mana.
Den

1

Usus Anda yang memberitahu Anda bahwa memiliki ThingProperty, ThingComponent, dan ThingManageruntuk setiap Thingjenis komponen sedikit berlebihan. Saya pikir itu benar.

Tapi, Anda perlu beberapa cara untuk melacak komponen terkait dalam hal sistem apa yang menggunakannya, entitas apa yang mereka miliki, dll.

TransformPropertyakan menjadi yang sangat umum. Tapi siapa yang bertanggung jawab atas itu, sistem rendering? Sistem fisika? Sistem suara? Mengapa suatu Transformkomponen bahkan perlu memperbarui sendiri?

Solusinya adalah menghapus segala jenis kode dari properti Anda di luar getter, setter, dan inisialisasi. Komponen adalah data, yang digunakan oleh sistem dalam game untuk melakukan berbagai tugas seperti rendering, AI, pemutaran suara, pergerakan, dll.

Baca tentang Artemis: http://piemaster.net/2011/07/07/entity-component-artemis/

Lihatlah kodenya dan Anda akan melihat bahwa itu didasarkan pada Sistem yang menyatakan dependensi mereka sebagai daftar ComponentTypes. Anda menulis masing-masing Systemkelas Anda dan dalam metode konstruktor / init mendeklarasikan tipe yang bergantung pada sistem.

Selama pengaturan untuk level atau yang lainnya, Anda membuat entitas Anda dan menambahkan komponen ke dalamnya. Setelah itu Anda memberi tahu entitas tersebut untuk melaporkan kembali ke Artemis, dan Artemis kemudian mencari tahu berdasarkan komposisi entitas yang sistem mana yang akan tertarik mengetahui tentang entitas itu.

Kemudian selama fase pembaruan dari loop Anda System, sekarang Anda memiliki daftar entitas apa yang akan diperbarui. Sekarang Anda dapat memiliki rincian komponen sehingga Anda dapat merancang sistem gila, entitas membangun keluar dari ModelComponent, TransformComponent,FliesLikeSupermanComponent , dan SocketInfoComponent, dan melakukan sesuatu yang aneh seperti make piring terbang yang lalat antara klien terhubung ke permainan multiplayer. Oke, mungkin bukan itu, tetapi idenya adalah membuat semuanya terpisah dan fleksibel.

Artemis tidak sempurna, dan contoh-contoh di situs ini sedikit mendasar, tetapi pemisahan kode dan data sangat kuat. Ini juga baik untuk cache Anda jika Anda melakukannya dengan benar. Artemis mungkin tidak melakukannya dengan benar di depan itu, tapi itu baik untuk dipelajari.

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.