Bagaimana cara mengembangkan gamestate entitas-komponen dalam game berbasis giliran?


9

Sejauh ini sistem komponen entitas yang saya gunakan sebagian besar bekerja seperti artemis Java:

  • Semua data dalam komponen
  • Sistem independen tanpa kewarganegaraan (setidaknya pada tingkat yang mereka tidak memerlukan input pada inisialisasi) iterasi atas setiap entitas yang hanya berisi komponen yang tertarik sistem tertentu
  • Semua sistem memproses entitas mereka satu centang, kemudian semuanya dimulai kembali.

Sekarang saya mencoba menerapkan ini pada permainan berbasis giliran untuk pertama kalinya, dengan banyak acara dan respons yang harus terjadi dalam urutan yang diatur relatif terhadap satu sama lain, sebelum permainan dapat melanjutkan. Sebuah contoh:

Pemain A menerima kerusakan dari pedang. Menanggapi ini, baju besi A menendang dan menurunkan kerusakan yang terjadi. Kecepatan gerakan A juga diturunkan karena semakin lemah.

  • Kerusakan yang terjadi adalah yang memicu keseluruhan interaksi
  • Armor harus dihitung dan diterapkan pada kerusakan yang masuk sebelum kerusakan diterapkan pada pemain
  • Pengurangan kecepatan gerakan tidak dapat diterapkan ke unit sampai setelah kerusakan benar-benar ditangani, karena itu tergantung pada jumlah kerusakan akhir.

Acara juga dapat memicu acara lainnya. Mengurangi kerusakan pedang menggunakan baju besi dapat menyebabkan pedang hancur (ini harus terjadi sebelum pengurangan kerusakan selesai), yang pada gilirannya dapat menyebabkan peristiwa tambahan sebagai tanggapan terhadapnya, pada dasarnya evaluasi peristiwa secara rekursif.

Secara keseluruhan, ini tampaknya mengarah pada beberapa masalah:

  1. Banyak siklus pemrosesan yang terbuang: Sebagian besar sistem (simpan untuk hal-hal yang selalu berjalan, seperti rendering) sama sekali tidak memiliki sesuatu yang layak dilakukan ketika itu bukan "giliran mereka" untuk bekerja, dan menghabiskan sebagian besar waktu menunggu permainan untuk masuk kondisi kerja yang valid. Ini mengotori setiap sistem dengan cek yang terus bertambah besar semakin banyak status ditambahkan ke dalam game.
  2. Untuk mengetahui apakah suatu sistem dapat memproses entitas yang ada dalam game, mereka memerlukan beberapa cara untuk memantau status entitas / sistem yang tidak terkait (sistem yang bertanggung jawab untuk menangani kerusakan perlu mengetahui apakah armor telah diterapkan atau tidak). Ini bisa mengacaukan sistem dengan banyak tanggung jawab, atau menciptakan kebutuhan akan sistem tambahan tanpa tujuan lain selain memindai koleksi entitas setelah setiap siklus pemrosesan dan berkomunikasi dengan satu set pendengar dengan memberi tahu mereka kapan boleh melakukan sesuatu.

Dua poin di atas mengasumsikan bahwa sistem bekerja pada himpunan entitas yang sama, yang pada akhirnya mengubah status menggunakan flag dalam komponen mereka.

Cara lain untuk menyelesaikannya adalah dengan menambah / menghapus komponen (atau membuat entitas yang sama sekali baru) sebagai hasil kerja sistem tunggal untuk memajukan status permainan. Ini berarti bahwa setiap kali suatu sistem benar-benar memiliki entitas yang cocok, ia tahu bahwa ia diizinkan untuk memprosesnya.

Namun ini membuat sistem bertanggung jawab untuk memicu sistem selanjutnya, sehingga sulit untuk mempertimbangkan perilaku program karena bug tidak akan muncul sebagai hasil dari interaksi sistem tunggal. Menambahkan sistem baru juga semakin sulit karena mereka tidak dapat diimplementasikan tanpa mengetahui persis bagaimana mereka mempengaruhi sistem lain (dan sistem sebelumnya mungkin harus dimodifikasi untuk memicu keadaan sistem baru tertarik), agak mengalahkan tujuan memiliki sistem yang terpisah dengan satu tugas.

Apakah ini sesuatu yang harus saya jalani? Setiap contoh ECS tunggal yang saya lihat adalah waktu nyata, dan sangat mudah untuk melihat bagaimana loop satu iterasi per game ini bekerja dalam kasus seperti itu. Dan saya masih membutuhkannya untuk rendering, sepertinya benar-benar tidak cocok untuk sistem yang menghentikan sebagian besar aspek itu sendiri setiap kali sesuatu terjadi.

Apakah ada beberapa pola desain untuk memajukan kondisi permainan yang cocok untuk ini, atau haruskah saya memindahkan semua logika dari loop dan alih-alih memicunya hanya saat diperlukan?


Anda tidak benar-benar ingin melakukan jajak pendapat untuk suatu peristiwa terjadi. Suatu peristiwa hanya terjadi ketika itu terjadi. Tidakkah Artemis memungkinkan sistem untuk saling berkomunikasi?
Sidar

Memang benar, tetapi hanya dengan menggabungkannya menggunakan metode.
Aeris130

Jawaban:


3

Saran saya di sini berasal dari pengalaman masa lalu tentang proyek RPG di mana kami menggunakan sistem komponen. Saya akan mengatakan bahwa saya benci bekerja di kode permainan karena itu adalah kode spaghetti. Jadi saya tidak menawarkan banyak jawaban di sini, hanya sebuah perspektif:

Logika yang Anda gambarkan untuk menangani kerusakan pedang pada pemain ... sepertinya satu sistem harus bertanggung jawab atas semua itu.

Di suatu tempat, ada fungsi HandleWeaponHit (). Itu akan mengakses ArmorComponent entitas pemain untuk mendapatkan baju besi yang relevan. Itu akan mengakses WeaponComponent entitas serangan itu untuk mungkin menghancurkan senjata itu. Setelah menghitung kerusakan akhir, itu akan menyentuh MovementComponent bagi pemain untuk mencapai pengurangan kecepatan.

Sedangkan untuk siklus pemrosesan yang terbuang ... HandleWeaponHit () hanya boleh dipicu saat diperlukan (setelah mendeteksi serangan pedang).

Mungkin yang ingin saya sampaikan adalah: pasti Anda menginginkan tempat dalam kode tempat Anda dapat meletakkan breakpoint, memukulnya, dan kemudian melanjutkan untuk menelusuri semua logika yang seharusnya dijalankan ketika serangan pedang terjadi. Dengan kata lain, logika tidak boleh tersebar di seluruh fungsi tick () dari beberapa sistem.


Melakukannya dengan cara ini akan membuat fungsi tab () berfungsi sebagai lebih banyak perilaku ditambahkan. Katakanlah ada musuh yang jatuh tertawa setiap kali pedang mengenai sasaran (sasaran apa pun) dalam garis pandangnya. Haruskah HandleWeaponHit benar-benar bertanggung jawab untuk memicu itu?
Aeris130

1
Anda memiliki urutan pertempuran yang terjalin erat jadi ya, hit bertanggung jawab untuk memicu efek. Tidak semuanya harus dipecah menjadi sistem kecil, biarkan sistem yang satu ini menangani ini karena itu benar-benar adalah "Sistem Combat" Anda dan itu menangani ... Combat ...
Patrick Hughes

3

Ini pertanyaan lama, tapi sekarang saya menghadapi masalah yang sama dengan game buatan saya saat belajar ECS, jadi beberapa necromany. Semoga akan berakhir dalam diskusi atau setidaknya beberapa komentar.

Saya tidak yakin apakah itu melanggar konsep ECS, tetapi bagaimana jika:

  • Tambahkan EventBus untuk membiarkan Sistem mengeluarkan / berlangganan objek acara (data murni sebenarnya, tapi bukan komponen yang saya kira)
  • Buat Komponen untuk setiap kondisi perantara

Contoh:

  • UserInputSystem menembakkan acara Serangan dengan [DamageDealerEntity, DamageReceiverEntity, Skill / Senjata menggunakan info]
  • CombatSystem berlangganan dan menghitung peluang penghindaran untuk DamageReceiver. Jika penghindaran gagal maka memicu event Damage dengan parameter yang sama
  • DamageSystem berlangganan acara tersebut dan karenanya dipicu
  • DamageSystem menggunakan Strength, BaseWeapon damage, type etc, dan menulisnya ke IncomingDamageComponent baru dengan [DamageDealerEntity, FinalOutgoingDamage, DamageType] dan memasangnya ke Entitas / Entitas penerima kerusakan
  • DamageSystem menjalankan OutgoingDamageCalculated
  • ArmorSystem dipicu olehnya, mengambil Entitas penerima atau mencari dengan aspek IncomingDamage di Entitas untuk mengambil IncomingDamageComponent (yang terakhir mungkin lebih baik untuk beberapa serangan dengan penyebaran) dan menghitung baju besi dan kerusakan yang diterapkan padanya. Opsional memicu peristiwa untuk menghancurkan pedang
  • ArmorSystems menghapus IncomingDamageComponent di setiap entitas dan menggantinya dengan DamageReceivedComponent dengan angka-angka akhir yang akan mempengaruhi HP dan Pengurangan kecepatan dari luka.
  • ArmorSystems mengirimkan acara IncomingDamageCalculated
  • Sistem kecepatan berlangganan dan menghitung kembali kecepatan
  • HealthSystem berlangganan dan mengurangi HP yang sebenarnya
  • dll
  • Entah bagaimana bersihkan

Pro:

  • Sistem saling memicu satu sama lain menyediakan data antara untuk kejadian berantai yang kompleks
  • Decoupling oleh EventBus

Cons:

  • Saya merasa bahwa saya mencampur dua cara melewati hal-hal: dalam parameers acara dan dalam Komponen sementara. itu mungkin tempat yang lemah. Dalam teori untuk menjaga hal-hal tetap homogen, saya dapat memecat hanya dengan peristiwa saja tanpa data sehingga Sistem akan menemukan parameter tersirat dalam komponen Entitas berdasarkan aspek ... Tidak yakin apakah itu baik-baik saja
  • Tidak yakin bagaimana cara mengetahui jika semua Sistem yang berpotensi tertarik telah memproses IncomingDamageCalculated sehingga dapat dibersihkan dan biarkan giliran berikutnya terjadi. Mungkin semacam pemeriksaan kembali di CombatSystem ...

2

Posting solusi yang akhirnya saya setujui, mirip dengan Yakovlev.

Pada dasarnya, saya akhirnya menggunakan sistem acara karena saya merasa sangat intuitif untuk mengikuti logikanya secara bergantian. Sistem akhirnya bertanggung jawab untuk unit dalam game yang berpegang pada logika berbasis giliran (pemain, monster, dan apa pun yang dapat berinteraksi dengan mereka), tugas waktu nyata seperti rendering dan polling input ditempatkan di tempat lain.

Sistem menerapkan metode onEvent yang mengambil peristiwa dan entitas sebagai input, menandakan bahwa entitas telah menerima acara tersebut. Setiap sistem juga berlangganan acara dan entitas dengan serangkaian komponen tertentu. Satu-satunya titik interaksi yang tersedia untuk sistem adalah singleton manajer entitas, yang digunakan untuk mengirim peristiwa ke entitas dan untuk mengambil komponen dari entitas tertentu.

Ketika manajer entitas menerima acara yang digabungkan dengan entitas yang dikirimnya, itu menempatkan acara di belakang antrian. Meskipun ada peristiwa dalam antrian, acara yang paling penting diambil dan dikirim ke setiap sistem yang berlangganan acara tersebut dan tertarik pada rangkaian komponen entitas yang menerima acara tersebut. Sistem-sistem itu pada gilirannya dapat memproses komponen-komponen entitas, serta mengirim acara tambahan kepada manajer.

Contoh: Pemain mengalami kerusakan, sehingga entitas pemain dikirim acara kerusakan. DamageSystem berlangganan kerusakan acara yang dikirim ke entitas dengan komponen kesehatan dan memiliki metode onEvent (entitas, peristiwa) yang mengurangi kesehatan di komponen entitas dengan jumlah yang ditentukan dalam acara tersebut.

Ini membuatnya mudah untuk memasukkan sistem baju besi yang berlangganan untuk merusak acara yang dikirim ke entitas dengan komponen baju besi. Metode onEvent-nya mengurangi kerusakan pada kejadian dengan jumlah pelindung pada komponen. Ini berarti bahwa menentukan urutan bahwa sistem menerima peristiwa berdampak pada logika permainan, karena sistem pelindung harus memproses peristiwa kerusakan sebelum sistem kerusakan agar dapat bekerja.

Kadang-kadang suatu sistem harus melangkah keluar dari entitas penerima. Untuk melanjutkan tanggapan saya kepada Eric Undersander, akan sangat sepele untuk menambahkan sistem yang mengakses peta permainan dan mencari entitas dengan FallsDownLaughingComponent dalam x ruang entitas yang menerima kerusakan, dan kemudian mengirim FallDownLaughingEvent kepada mereka. Sistem ini harus dijadwalkan untuk menerima acara setelah sistem kerusakan, jika acara kerusakan belum dibatalkan pada saat itu, kerusakan telah ditangani.

Salah satu masalah yang muncul adalah bagaimana memastikan bahwa respons-peristiwa diproses sesuai urutan pengirimannya, mengingat beberapa respons mungkin memunculkan respons tambahan. Contoh:

Pemain bergerak, mendorong suatu event pergerakan dikirim ke entitas pemain dan diambil oleh sistem pergerakan.

Dalam antrian: Gerakan

Jika gerakan diizinkan, sistem akan menyesuaikan posisi pemain. Jika tidak (pemain mencoba bergerak ke rintangan), itu menandai acara sebagai dibatalkan, menyebabkan manajer entitas membuangnya alih-alih mengirimnya ke sistem berikutnya. Pada akhir daftar sistem yang tertarik pada acara tersebut adalah TurnFinishedSystem, yang mengkonfirmasi bahwa pemain telah menghabiskan gilirannya untuk memindahkan karakter, dan bahwa gilirannya sudah berakhir. Ini menghasilkan acara TurnOver yang dikirim ke entitas pemain dan menempatkan antrian.

Dalam antrian: TurnOver

Sekarang katakan bahwa pemain menginjak jebakan, menyebabkan kerusakan. TrapSystem mendapatkan pesan perpindahan sebelum TurnFinishedSystem, sehingga acara kerusakan dikirim terlebih dahulu. Sekarang antriannya terlihat seperti ini:

Dalam antrian: Kerusakan, TurnOver

Semua baik-baik saja sejauh ini, acara kerusakan akan diproses, dan kemudian belokan berakhir. Namun, bagaimana jika acara tambahan dikirim sebagai respons terhadap kerusakan? Sekarang antrian acara akan terlihat seperti:

Dalam antrian: Kerusakan, TurnOver, ResponseToDamage

Dengan kata lain, belokan akan berakhir sebelum segala respons terhadap kerusakan diproses.

Untuk menyelesaikan ini saya akhirnya menggunakan dua metode pengiriman acara: kirim (event, entitas) dan merespon (event, eventToRespondTo, entitas).

Setiap peristiwa menyimpan catatan peristiwa sebelumnya dalam rantai respons, dan kapan pun metode respons () digunakan, peristiwa yang ditanggapi (dan setiap peristiwa dalam rantai responsnya) berakhir di kepala rantai di acara yang digunakan untuk menanggapi dengan. Acara gerakan awal tidak memiliki acara seperti itu. Respons kerusakan berikutnya memiliki peristiwa gerakan dalam daftar.

Selain itu, array panjang variabel digunakan untuk memuat banyak antrian acara. Setiap kali suatu peristiwa diterima oleh manajer, acara tersebut ditambahkan ke antrian pada indeks dalam larik yang cocok dengan jumlah peristiwa dalam rantai respons. Dengan demikian acara gerakan awal ditambahkan ke antrian di [0], dan kerusakan, serta acara TurnOver ditambahkan ke antrian terpisah di [1] karena keduanya dikirim sebagai respons terhadap gerakan.

Ketika respons terhadap peristiwa kerusakan dikirim, peristiwa-peristiwa itu akan berisi peristiwa kerusakan itu sendiri, serta gerakan, menempatkan mereka dalam antrian di indeks [2]. Selama indeks [n] memiliki peristiwa dalam antriannya, peristiwa tersebut akan diproses sebelum beralih ke [n-1]. Ini memberikan urutan pemrosesan:

Gerakan -> Kerusakan [1] -> ResponseToDamage [2] -> [2] kosong -> TurnOver [1] -> [1] kosong -> [0] kosong

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.