Saya menerapkan varian sistem entitas yang memiliki:
Sebuah kelas Entity yang sedikit lebih dari satu ID yang mengikat komponen bersama-sama
Sekelompok kelas komponen yang tidak memiliki "komponen logika", hanya data
Sekelompok kelas sistem (alias "subsistem", "manajer"). Ini melakukan semua pemrosesan logika entitas. Dalam kebanyakan kasus dasar, sistem hanya beralih melalui daftar entitas yang mereka minati dan melakukan tindakan pada masing-masing
Sebuah MessageChannel objek kelas yang dimiliki oleh semua sistem permainan. Setiap sistem dapat berlangganan jenis pesan tertentu untuk didengarkan dan juga dapat menggunakan saluran untuk menyiarkan pesan ke sistem lain
Varian awal penanganan pesan sistem adalah sesuatu seperti ini:
- Jalankan pembaruan pada setiap sistem game secara berurutan
Jika suatu sistem melakukan sesuatu pada suatu komponen dan tindakan itu mungkin menarik bagi sistem lain, sistem akan mengirimkan pesan yang sesuai (misalnya, suatu sistem memanggil
messageChannel.Broadcast(new EntityMovedMessage(entity, oldPosition, newPosition))
setiap kali entitas dipindahkan)
Setiap sistem yang berlangganan pesan tertentu akan dipanggil metode penanganan pesan
Jika suatu sistem menangani suatu peristiwa, dan logika pemrosesan peristiwa membutuhkan pesan lain untuk disiarkan, pesan tersebut segera disiarkan dan rangkaian metode pemrosesan pesan lainnya dipanggil
Varian ini OK sampai saya mulai mengoptimalkan sistem deteksi tabrakan (itu menjadi sangat lambat karena jumlah entitas meningkat). Pada awalnya itu hanya akan mengulangi setiap pasangan entitas menggunakan algoritma brute force sederhana. Lalu saya menambahkan "indeks spasial" yang memiliki kisi sel yang menyimpan entitas yang berada di dalam area sel tertentu, sehingga memungkinkan untuk melakukan pemeriksaan hanya pada entitas di sel tetangga.
Setiap kali entitas bergerak, sistem tumbukan memeriksa apakah entitas bertabrakan dengan sesuatu di posisi baru. Jika ya, tabrakan terdeteksi. Dan jika kedua entitas yang bertabrakan adalah "objek fisik" (mereka berdua memiliki komponen RigidBody dan dimaksudkan untuk mendorong satu sama lain agar tidak menempati ruang yang sama), sistem pemisahan tubuh kaku khusus meminta sistem pergerakan untuk memindahkan entitas ke beberapa posisi tertentu yang akan memisahkan mereka. Ini pada gilirannya menyebabkan sistem pergerakan untuk mengirim pesan memberitahukan tentang posisi entitas yang berubah. Sistem deteksi tabrakan dimaksudkan untuk bereaksi karena perlu memperbarui indeks spasial itu.
Dalam beberapa kasus itu menyebabkan masalah karena isi sel (daftar objek Entitas generik dalam C #) bisa dimodifikasi saat sedang diiterasi, sehingga menyebabkan pengecualian untuk dilempar oleh iterator.
Jadi ... bagaimana saya bisa mencegah sistem tabrakan terganggu sementara memeriksa tabrakan?
Tentu saja saya dapat menambahkan beberapa logika "pintar" / "rumit" yang memastikan konten sel diulangi dengan benar, tetapi saya pikir masalahnya bukan terletak pada sistem tabrakan itu sendiri (saya juga memiliki masalah serupa di sistem lain), tetapi caranya pesan ditangani saat mereka melakukan perjalanan dari sistem ke sistem. Yang saya butuhkan adalah beberapa cara untuk memastikan bahwa metode penanganan peristiwa tertentu dapat melakukan pekerjaannya tanpa gangguan.
Apa yang saya coba:
- Antrian pesan masuk . Setiap kali beberapa sistem menyiarkan pesan, pesan tersebut akan ditambahkan ke antrian pesan sistem yang tertarik dengannya. Pesan-pesan ini diproses ketika pembaruan sistem disebut setiap frame. Masalahnya : jika sistem A menambahkan pesan ke antrian B sistem, ia berfungsi dengan baik jika sistem B dimaksudkan untuk diperbarui lebih baru daripada sistem A (dalam kerangka permainan yang sama); jika tidak maka pesan akan diproses ke frame permainan berikutnya (tidak diinginkan untuk beberapa sistem)
- Antrian pesan keluar . Ketika suatu sistem menangani suatu peristiwa, pesan apa pun yang disiarkannya ditambahkan ke antrian pesan keluar. Pesan tidak perlu menunggu pembaruan sistem diproses: pesan akan ditangani "segera" setelah penangan pesan awal selesai berfungsi. Jika penanganan pesan menyebabkan pesan lain disiarkan, mereka juga ditambahkan ke antrian keluar, sehingga semua pesan ditangani dengan bingkai yang sama. Masalah: jika sistem entitas seumur hidup (Saya menerapkan manajemen seumur hidup entitas dengan suatu sistem) membuat entitas, ia memberi tahu beberapa sistem A dan B tentang hal itu. Sementara sistem A memproses pesan, itu menyebabkan rantai pesan yang akhirnya menyebabkan entitas yang dibuat dihancurkan (misalnya, entitas peluru dibuat tepat di mana ia bertabrakan dengan beberapa kendala, yang menyebabkan peluru hancur sendiri). Sementara rantai pesan sedang diselesaikan, sistem B tidak mendapatkan pesan pembuatan entitas. Jadi, jika sistem B juga tertarik pada pesan penghancuran entitas, ia mendapatkannya, dan hanya setelah "rantai" selesai diselesaikan, apakah ia mendapatkan pesan pembuatan entitas awal. Ini menyebabkan pesan penghancuran diabaikan, pesan penciptaan menjadi "diterima",
EDIT - JAWABAN UNTUK PERTANYAAN, KOMENTAR:
- Siapa yang memodifikasi isi sel sementara sistem tumbukan beralih di atasnya?
Sementara sistem tumbukan sedang melakukan pemeriksaan tumbukan pada beberapa entitas dan tetangga itu, tumbukan mungkin terdeteksi dan sistem entitas akan mengirim pesan yang akan langsung bereaksi setelah oleh sistem lain. Reaksi terhadap pesan dapat menyebabkan pesan lain dibuat dan juga ditangani segera. Jadi beberapa sistem lain mungkin membuat pesan bahwa sistem tumbukan kemudian perlu diproses segera (misalnya, entitas bergerak sehingga sistem tumbukan perlu memperbarui indeks spasial itu), meskipun pemeriksaan tumbukan sebelumnya belum selesai.
- Tidak bisakah Anda bekerja dengan antrian pesan keluar global?
Saya mencoba satu antrian global baru-baru ini. Itu menyebabkan masalah baru. Masalah: Saya memindahkan entitas tangki ke entitas dinding (tangki dikontrol dengan keyboard). Lalu saya memutuskan untuk mengubah arah tangki. Untuk memisahkan tangki dan dinding setiap bingkai, CollidingRigidBodySeparationSystem memindahkan tangki dari dinding dengan jumlah sekecil mungkin. Arah pemisahan harus berlawanan dengan arah pergerakan tangki (ketika gambar permainan dimulai, tangki harus terlihat seolah-olah tidak pernah bergerak ke dinding). Tapi arahnya menjadi kebalikan dari arah BARU, sehingga memindahkan tangki ke sisi dinding yang berbeda dari sebelumnya. Mengapa masalah terjadi: Inilah cara penanganan pesan sekarang (kode sederhana):
public void Update(int deltaTime)
{
m_messageQueue.Enqueue(new TimePassedMessage(deltaTime));
while (m_messageQueue.Count > 0)
{
Message message = m_messageQueue.Dequeue();
this.Broadcast(message);
}
}
private void Broadcast(Message message)
{
if (m_messageListenersByMessageType.ContainsKey(message.GetType()))
{
// NOTE: all IMessageListener objects here are systems.
List<IMessageListener> messageListeners = m_messageListenersByMessageType[message.GetType()];
foreach (IMessageListener listener in messageListeners)
{
listener.ReceiveMessage(message);
}
}
}
Kode mengalir seperti ini (mari kita asumsikan itu bukan bingkai game pertama):
- Sistem mulai memproses TimePassedMessage
- InputHandingSystem mengonversi penekanan tombol ke tindakan entitas (dalam hal ini, panah kiri berubah menjadi tindakan MoveWest). Tindakan entitas disimpan dalam komponen ActionExecutor
- ActionExecutionSystem , sebagai reaksi terhadap tindakan entitas, menambahkan pesan MovementDirectionChangeRequestedMessage ke akhir antrian pesan
- MovementSystem memindahkan posisi entitas berdasarkan data komponen Velocity dan menambahkan pesan PositionChangedMessage ke akhir antrian. Gerakan ini dilakukan dengan menggunakan arah gerakan / kecepatan frame sebelumnya (katakanlah utara)
- Sistem berhenti memproses TimePassedMessage
- Sistem mulai memproses MovementDirectionChangeRequestedMessage
- MovementSystem mengubah kecepatan entitas / arah gerakan seperti yang diminta
- Sistem berhenti memproses MovementDirectionChangeRequestedMessage
- Sistem mulai memproses PositionChangedMessage
- CollisionDetectionSystem mendeteksi bahwa karena suatu entitas bergerak, ia berlari ke entitas lain (tangki masuk ke dalam dinding). Itu menambahkan CollisionOccuredMessage ke antrian
- Sistem berhenti memproses PositionChangedMessage
- Sistem mulai memproses CollisionOccuredMessage
- CollidingRigidBodySeparationSystem bereaksi terhadap tabrakan dengan memisahkan tangki dan dinding. Karena dindingnya statis, hanya tangki yang dipindahkan. Arah pergerakan tank digunakan sebagai indikator dari mana tangki berasal. Itu diimbangi dalam arah yang berlawanan
BUG: Ketika tangki memindahkan frame ini, ia bergerak menggunakan arah gerakan dari frame sebelumnya, tetapi ketika dipisahkan, arah gerakan dari frame INI digunakan, meskipun sudah berbeda. Itu tidak seharusnya bekerja!
Untuk mencegah bug ini, arah gerakan lama perlu disimpan di suatu tempat. Saya bisa menambahkannya ke beberapa komponen hanya untuk memperbaiki bug khusus ini, tetapi tidakkah kasus ini menunjukkan beberapa cara yang salah secara mendasar dalam menangani pesan? Mengapa sistem pemisahan harus memperhatikan arah gerakan mana yang digunakannya? Bagaimana saya bisa menyelesaikan masalah ini dengan elegan?
- Anda mungkin ingin membaca gamadu.com/artemis untuk melihat apa yang mereka lakukan dengan Aspek, yang sisi langkah beberapa masalah yang Anda lihat.
Sebenarnya, saya sudah akrab dengan Artemis cukup lama sekarang. Menyelidiki kode sumbernya, membaca forum, dll. Tetapi saya telah melihat "Aspek" disebutkan hanya di beberapa tempat dan, sejauh yang saya mengerti, mereka pada dasarnya berarti "Sistem". Tapi aku tidak bisa melihat bagaimana sisi Artemis melangkah beberapa masalah saya. Bahkan tidak menggunakan pesan.
- Lihat juga: "Komunikasi entitas: Antrian pesan vs Terbitkan / Berlangganan vs. Sinyal / Slot"
Saya sudah membaca semua pertanyaan gamedev.stackexchange mengenai sistem entitas. Yang ini sepertinya tidak membahas masalah yang saya hadapi. Apakah saya melewatkan sesuatu?
- Tangani kedua kasus secara berbeda, memperbarui kisi tidak perlu bergantung pada pesan gerakan karena merupakan bagian dari sistem tumbukan
Saya tidak yakin apa yang Anda maksud. Implementasi CollisionDetectionSystem yang lebih lama hanya akan memeriksa tabrakan pada pembaruan (ketika TimePassedMessage ditangani), tetapi saya harus meminimalkan pemeriksaan sebanyak yang saya bisa karena kinerja. Jadi saya beralih ke pengecekan tabrakan ketika suatu entitas bergerak (sebagian besar entitas dalam game saya statis).