Pertanyaan bagus! Sebelum saya sampai pada pertanyaan spesifik yang Anda ajukan, saya akan mengatakan: jangan meremehkan kekuatan kesederhanaan. Tenpn benar. Ingatlah bahwa semua yang Anda coba lakukan dengan pendekatan ini adalah menemukan cara yang elegan untuk menunda panggilan fungsi atau memisahkan penelepon dari callee. Saya dapat merekomendasikan coroutine sebagai cara intuitif yang mengejutkan untuk mengatasi beberapa masalah tersebut, tapi itu sedikit di luar topik. Terkadang, Anda lebih baik hanya memanggil fungsi dan hidup dengan fakta bahwa entitas A digabungkan langsung ke entitas B. Lihat YAGNI.
Yang mengatakan, saya telah menggunakan dan senang dengan model sinyal / slot dikombinasikan dengan pesan sederhana yang lewat. Saya menggunakannya di C ++ dan Lua untuk judul iPhone yang cukup sukses yang memiliki jadwal yang sangat ketat.
Untuk kasus sinyal / slot, jika saya ingin entitas A melakukan sesuatu sebagai respons terhadap sesuatu yang dilakukan entitas B (mis. Membuka kunci pintu ketika sesuatu mati) Saya mungkin memiliki entitas A yang berlangganan langsung ke peristiwa kematian entitas B. Atau mungkin entitas A akan berlangganan masing-masing grup entitas, menambah penghitung pada setiap peristiwa yang dipecat, dan membuka kunci pintu setelah N dari mereka telah meninggal. Juga, "grup entitas" dan "N dari mereka" biasanya akan menjadi desainer yang didefinisikan dalam data level. (Sebagai tambahan, ini adalah satu area di mana coroutine benar-benar dapat bersinar, misalnya, WaitForMultiple ("Dying", entA, entB, entC); door.Unlock ();)
Tapi itu bisa menjadi rumit ketika datang ke reaksi yang tergabung erat dengan kode C ++, atau kejadian game yang secara inheren sesaat: menangani kerusakan, memuat ulang senjata, men-debug, umpan balik AI berbasis lokasi yang digerakkan oleh pemain. Di sinilah pesan lewat dapat mengisi celah. Ini pada dasarnya bermuara pada sesuatu seperti, "beri tahu semua entitas di area ini untuk menerima kerusakan dalam 3 detik," atau "setiap kali Anda menyelesaikan fisika untuk mencari tahu siapa yang saya tembak, minta mereka untuk menjalankan fungsi skrip ini." Sulit untuk mengetahui bagaimana melakukannya dengan baik menggunakan terbitkan / berlangganan atau sinyal / slot.
Ini bisa dengan mudah berlebihan (dibandingkan contoh tenpn). Ini juga bisa menjadi mengasapi yang tidak efisien jika Anda memiliki banyak tindakan. Namun terlepas dari kekurangannya, pendekatan "pesan dan acara" ini sangat cocok dengan kode permainan yang ditulis (misalnya dalam Lua). Kode skrip dapat mendefinisikan dan bereaksi terhadap pesan dan acara sendiri tanpa kode C ++ peduli sama sekali. Dan kode skrip dapat dengan mudah mengirim pesan yang memicu kode C ++, seperti mengubah level, memutar suara, atau bahkan hanya membiarkan senjata mengatur berapa banyak kerusakan yang diberikan pesan TakeDamage. Ini menghemat banyak waktu karena saya tidak harus terus-menerus bermain-main dengan luabind. Dan itu membiarkan saya menyimpan semua kode luabind saya di satu tempat, karena tidak banyak. Bila digabungkan dengan benar,
Juga, pengalaman saya dengan use case # 2 adalah bahwa Anda lebih baik menanganinya sebagai acara di arah lain. Alih-alih bertanya apa kesehatan entitas, nyalakan acara / kirim pesan setiap kali kesehatan membuat perubahan signifikan.
Dalam hal antarmuka, btw, saya berakhir dengan tiga kelas untuk mengimplementasikan semua ini: EventHost, EventClient, dan MessageClient. EventHosts membuat slot, EventClients berlangganan / sambungkan ke dalamnya, dan MessageClients mengaitkan delegasi dengan pesan. Perhatikan bahwa target delegasi MessageClient tidak harus berupa objek yang sama yang memiliki asosiasi. Dengan kata lain, MessageClients dapat ada hanya untuk meneruskan pesan ke objek lain. FWIW, metafora host / klien agak tidak pantas. Sumber / Sink mungkin konsep yang lebih baik.
Maaf, saya agak mengoceh di sana. Ini jawaban pertama saya :) Saya harap ini masuk akal.
Anda bertanya bagaimana permainan komersial melakukannya. ;)
sumber
Jawaban yang lebih serius:
Saya telah melihat papan tulis banyak digunakan. Versi sederhana tidak lebih dari struts yang diperbarui dengan hal-hal seperti HP entitas, yang kemudian dapat dicari entitas.
Papan tulis Anda bisa menjadi pandangan dunia dari entitas ini (tanyakan papan tulis B apa HP-nya), atau pandangan entitas tentang dunia (A menanyakan papan tulisnya untuk melihat apa target HP dari A).
Jika Anda hanya memperbarui papan tulis pada titik sinkronisasi dalam bingkai, Anda kemudian dapat membacanya dari titik lain di utas mana pun, membuat multithreading cukup mudah diterapkan.
Papan tulis yang lebih maju mungkin lebih seperti hashtable, memetakan string ke nilai. Ini lebih dapat dipelihara tetapi jelas memiliki biaya run-time.
Papan tulis secara tradisional hanya komunikasi satu arah - tidak akan menangani kerusakan akibat kerusakan.
sumber
long long int
s atau serupa, dalam sistem ECS murni.)Saya telah mempelajari masalah ini sedikit dan saya telah melihat solusi yang bagus.
Pada dasarnya ini semua tentang subsistem. Ini mirip dengan ide papan tulis yang disebutkan oleh tenpn.
Entitas terbuat dari komponen, tetapi mereka hanya tas properti. Tidak ada perilaku yang diterapkan dalam entitas itu sendiri.
Katakanlah, entitas memiliki komponen Kesehatan dan komponen Kerusakan.
Kemudian Anda memiliki beberapa MessageManager dan tiga subsistem: ActionSystem, DamageSystem, HealthSystem. Pada satu titik ActionSystem melakukan perhitungannya pada dunia game dan menghasilkan suatu peristiwa:
Acara ini dipublikasikan ke MessageManager. Sekarang pada satu titik waktu MessageManager melewati pesan yang tertunda dan menemukan bahwa DamageSystem telah berlangganan pesan HIT. Sekarang MessageManager mengirimkan pesan HIT ke DamageSystem. DamageSystem menelusuri daftar entitas yang memiliki komponen Kerusakan, menghitung titik kerusakan tergantung pada daya hantam atau keadaan lain dari kedua entitas, dll, dan menerbitkan acara
HealthSystem telah berlangganan pesan DAMAGE dan sekarang ketika MessageManager menerbitkan pesan DAMAGE ke HealthSystem, HealthSystem memiliki akses ke entitas entitas_A dan entitas_B dengan komponen Kesehatannya, jadi sekali lagi HealthSystem dapat melakukan perhitungannya (dan mungkin menerbitkan acara yang sesuai) ke MessageManager).
Dalam mesin permainan seperti itu, format pesan adalah satu-satunya penghubung antara semua komponen dan subsistem. Subsistem dan entitas sepenuhnya independen dan tidak mengetahui satu sama lain.
Saya tidak tahu apakah beberapa mesin game nyata telah mengimplementasikan ide ini atau tidak, tetapi sepertinya cukup solid dan bersih dan saya berharap suatu hari dapat menerapkannya sendiri untuk mesin game tingkat hobi saya.
sumber
entity_b->takeDamage();
)Mengapa tidak memiliki antrian pesan global, seperti:
Dengan:
Dan di akhir game loop / event handling:
Saya pikir ini adalah pola Command. Dan
Execute()
merupakan virtual murniEvent
, yang turunannya mendefinisikan dan melakukan hal-hal. Jadi disini:sumber
Jika game Anda adalah pemain tunggal, cukup gunakan metode objek sasaran (seperti yang disarankan tenpn).
Jika Anda (atau ingin mendukung) multipemain (lebih tepatnya multiclient), gunakan antrian perintah.
sumber
Saya akan mengatakan: Jangan gunakan, selama Anda tidak secara eksplisit membutuhkan umpan balik instan dari kerusakan.
Entitas / komponen pengambil kerusakan / apa pun yang harus mendorong acara ke antrian acara lokal atau sistem pada tingkat yang sama yang menyimpan kerusakan-peristiwa.
Maka harus ada sistem overlay dengan akses ke kedua entitas yang meminta acara dari entitas a dan meneruskannya ke entitas b. Dengan tidak membuat sistem acara umum yang dapat digunakan oleh apa saja dari mana saja untuk meneruskan acara ke apa saja kapan saja, Anda membuat aliran data eksplisit yang selalu membuat kode lebih mudah untuk di-debug, lebih mudah untuk mengukur kinerja, lebih mudah untuk dipahami dan dibaca dan sering mengarah ke sistem yang dirancang lebih baik secara umum.
sumber
Telepon saja. Jangan lakukan permintaan-hp diikuti oleh permintaan-hp - jika Anda mengikuti model itu Anda akan berada di dunia yang terluka.
Anda mungkin ingin melihat Mono Continuations juga. Saya pikir itu akan ideal untuk NPC.
sumber
Jadi apa yang terjadi jika kita memiliki pemain A dan B yang mencoba menekan satu sama lain dalam siklus pembaruan yang sama ()? Misalkan Pembaruan () untuk pemain A terjadi sebelum Pembaruan () untuk pemain B dalam Siklus 1 (atau centang, atau apa pun sebutannya). Ada dua skenario yang dapat saya pikirkan:
Pemrosesan langsung melalui pesan:
Ini tidak adil, pemain A dan B harus memukul satu sama lain, pemain B meninggal sebelum mengenai A hanya karena entitas / gim tersebut mendapat pembaruan () nanti.
Mengantri pesan
Sekali lagi ini tidak adil .. pemain A seharusnya mengambil hitpoints dalam belokan / siklus / tik yang sama!
sumber
pEntity->Flush( pMessages );
. Ketika entitas_A menghasilkan acara baru, itu tidak dibaca oleh entitas_B dalam bingkai itu (ia memiliki kesempatan untuk mengambil ramuan juga) kemudian keduanya menerima kerusakan dan setelah itu mereka memproses pesan penyembuhan ramuan yang akan menjadi yang terakhir dalam antrian . Pemain B tetap mati karena pesan ramuan adalah yang terakhir dalam antrian: P tetapi mungkin berguna untuk jenis pesan lain seperti membersihkan pointer ke entitas yang mati.