Desain Sistem Pesan Game


8

Saya membuat game sederhana, dan telah memutuskan untuk mencoba menerapkan sistem pesan.

Sistem pada dasarnya terlihat seperti ini:

Entity menghasilkan pesan -> pesan diposting ke antrian pesan global -> messageManager memberitahukan setiap objek pesan baru melalui onMessageReceived (Pesan Pesan) -> jika objek ingin, ia bertindak pada pesan.

Cara saya membuat objek pesan adalah seperti ini:

//base message class, never actually instantiated
abstract class Message{
  Entity sender;
}

PlayerDiedMessage extends Message{
  int livesLeft;
}

Sekarang SoundManagerEntity saya dapat melakukan sesuatu seperti ini dalam metode onMessageReceived ()

public void messageReceived(Message msg){
  if(msg instanceof PlayerDiedMessage){
     PlayerDiedMessage diedMessage = (PlayerDiedMessage) msg;
     if(diedMessage.livesLeft == 0)
       playSound(SOUND_DEATH);
  }
}

Pro untuk pendekatan ini:

  1. Sangat sederhana dan mudah diimplementasikan
  2. Pesan dapat berisi informasi sebanyak yang Anda inginkan, karena Anda dapat membuat subkelas pesan baru yang memiliki informasi apa pun yang diperlukan.

Kontra:

  1. Saya tidak tahu bagaimana saya bisa mendaur ulang objek Pesan ke kumpulan objek , kecuali jika saya memiliki kumpulan yang berbeda untuk setiap subkelas Pesan. Jadi saya punya banyak dan banyak objek penciptaan / alokasi memori dari waktu ke waktu.
  2. Tidak dapat mengirim pesan ke penerima tertentu, tetapi saya belum membutuhkannya di game saya jadi saya tidak terlalu keberatan.

Apa yang kulewatkan di sini? Harus ada implementasi yang lebih baik atau beberapa ide yang saya lewatkan.


1) Mengapa Anda perlu memanfaatkan kumpulan objek itu, apa pun itu? (Di samping catatan, apa yang mereka dan tujuan apa yang mereka layani?) Tidak perlu merancang di luar apa yang berhasil. Dan apa yang Anda miliki saat ini tampaknya tidak memiliki masalah dengan itu. 2) Jika Anda perlu mengirim pesan ke penerima tertentu, Anda menemukannya dan Anda memanggilnya onMessageReceived, tidak diperlukan sistem mewah.
snake5

Yah, maksudku bahwa aku tidak bisa mendaur ulang objek saat ini menjadi kolam objek tujuan umum. Jadi jika saya ingin mengurangi alokasi memori yang diperlukan, saya tidak bisa. Saat ini ini bukan masalah karena permainan saya sangat sederhana sehingga berjalan dengan sangat baik.
you786

Saya berasumsi ini Java? Sepertinya Anda sedang mencoba mengembangkan sistem acara.
Justin Skiles

@JustinSkiles Benar, ini Java. Saya kira Anda benar, itu benar-benar digunakan untuk bertindak sebagai sistem acara. Cara lain apa yang bisa Anda gunakan sistem pesan?
you786

Jawaban:


11

Jawaban sederhana adalah Anda tidak ingin mendaur ulang pesan. Satu mendaur ulang objek yang dibuat (dan dihilangkan) oleh ribuan setiap frame, seperti partikel, atau sangat berat (puluhan properti, proses inisialisasi yang panjang).

Jika Anda memiliki partikel salju dengan bitmap, kecepatan, atribut fisik yang terkait yang baru saja turun di bawah tampilan Anda, Anda mungkin ingin mendaur ulangnya alih-alih membuat partikel baru dan menginisialisasi bitmap dan mengacak atribut lagi. Dalam contoh Anda, tidak ada yang berguna di dalam Pesan untuk menyimpannya, dan Anda akan membuang lebih banyak sumber daya untuk menghapus properti spesifik-instansinya, selain Anda hanya akan membuat objek Pesan baru.


3
Jawaban yang bagus. Saya menggunakan kolam untuk pesan saya, dan saya tidak pernah benar-benar menyelidiki apakah itu berguna atau tidak. (Past-) waktu untuk profil!
Raptormeat

Hmm. Bagaimana jika saya memiliki pesan yang dipancarkan mungkin setiap frame? Saya kira jawabannya adalah memiliki kumpulan spesifik untuk objek semacam itu, yang masuk akal.
you786

@ you786 Saya baru saja membuat tes kotor cepat di Actionscript 3. Membuat Array dan mengisinya dengan 1000 Pesan membutuhkan 1ms dalam lingkungan Debug yang lambat. Saya harap itu beres.
Markus von Broady

@ you786 Dia benar, pertama-tama kenali ini sebagai hambatan, jika Anda ingin menjadi pengembang game indie, Anda perlu belajar membuat segalanya dengan cepat dan efektif. Investasikan waktu dalam fine tuning kode Anda hanya layak jika kode itu saat ini memperlambat permainan Anda dan membuat perubahan ini dapat meningkatkan kinerja secara signifikan. Misalnya, membuat instance Sprtie di AS3 akan sangat memakan waktu, jauh lebih baik untuk mendaur ulangnya. Menciptakan objek Point bukan, mendaur ulang mereka akan membuang-buang waktu pengkodean dan keterbacaan.
AturSams

Yah, seharusnya saya membuatnya lebih jelas, tapi saya tidak khawatir tentang alokasi memori. Saya khawatir tentang pemungut sampah Jawa berjalan di tengah permainan dan menyebabkan pelambatan. Tetapi poin Anda tentang optimasi prematur masih valid.
you786

7

Dalam permainan berbasis Java saya, saya datang dengan solusi yang sangat mirip, kecuali kode penanganan pesan saya terlihat seperti ini:

@EventHandler
public void messageReceived(PlayerDiedMessage msg){
  if(diedMessage.livesLeft == 0)
     playSound(SOUND_DEATH);
}

Saya menggunakan anotasi untuk menandai metode sebagai pengendali acara. Kemudian, pada permulaan gim, saya menggunakan refleksi untuk menghitung pemetaan (atau, seperti saya menyebutnya, "perpipaan") dari pesan - metode mana yang digunakan untuk memanggil objek mana saat suatu peristiwa dari kelas tertentu dikirim. Ini bekerja ... luar biasa.

Perhitungan perpipaan bisa menjadi sedikit rumit jika Anda ingin menambahkan antarmuka dan subkelas ke dalam campuran, tapi tetap saja cukup mudah. Ada sedikit perlambatan saat memuat game ketika semua kelas perlu dipindai, tetapi hanya dilakukan sekali (dan mungkin dapat dipindahkan ke utas terpisah). Apa yang didapat sebagai gantinya adalah pengiriman pesan yang sebenarnya lebih murah - saya tidak perlu memanggil setiap metode "messageReceived" dalam basis kode hanya untuk memeriksanya apakah acara tersebut dari kelas yang baik.


3
Jadi pada dasarnya Anda menemukan cara agar permainan Anda berjalan lebih lambat? : D
Markus von Broady

3
@ MarkusvonBroady Jika sekali dimuat, benar-benar layak. Bandingkan dengan monstrositas dalam jawaban saya.
michael.bartnett

1
@MarkusvonBroady Doa melalui refleksi hanya sedikit lebih lambat daripada doa normal, jika Anda memiliki semua bagian di tempat (setidaknya profil saya tidak menunjukkan masalah di sana). Penemuan itu mahal, pasti.
Liosan

@ michael.bartnett Saya membuat kode untuk pengguna, bukan untuk saya sendiri. Saya dapat hidup dengan kode saya menjadi lebih besar untuk eksekusi lebih cepat. Tapi itu hanya ucapan subjektif saya.
Markus von Broady

+1, jalur anotasi adalah sesuatu yang tidak pernah saya pikirkan. Jadi hanya untuk memperjelas, pada dasarnya Anda akan memiliki banyak metode kelebihan beban yang disebut messageReceived dengan subclass yang berbeda dari Message. Kemudian pada saat runtime Anda menggunakan refleksi untuk membuat semacam peta yang menghitung MessageSubclass => OverloadedMethod?
you786

5

EDIT Jawaban Liosan lebih seksi. Lihatlah ke dalamnya.


Seperti kata Markus, pesan bukanlah kandidat umum untuk kumpulan objek. Jangan repot-repot karena alasan yang disebutkannya. Jika gim Anda mengirim berton-ton pesan jenis tertentu, maka mungkin itu akan bermanfaat. Tetapi kemudian pada saat itu saya sarankan Anda beralih ke panggilan metode langsung.

Ngomong-ngomong soal,

Tidak dapat mengirim pesan ke penerima tertentu, tetapi saya belum membutuhkannya di game saya jadi saya tidak terlalu keberatan.

Jika Anda tahu penerima tertentu yang ingin Anda kirimi, bukankah cukup mudah untuk mendapatkan referensi ke objek itu dan melakukan panggilan metode langsung ke sana? Anda juga bisa menyembunyikan objek tertentu di belakang kelas manajer untuk akses yang lebih mudah di seluruh sistem.

Ada satu tipuan untuk implementasi Anda, dan ada potensi banyak objek untuk mendapatkan pesan yang tidak terlalu mereka pedulikan. Idealnya kelas Anda hanya akan menerima pesan yang benar-benar mereka pedulikan.

Anda dapat menggunakan a HashMap<Class, LinkedList<Messagable>>untuk mengaitkan jenis pesan dengan daftar objek yang ingin menerima jenis pesan itu.

Berlangganan ke jenis pesan akan terlihat seperti ini:

globalMessageQueue.subscribe(PlayerDiedMessage.getClass(), this);

Anda dapat menerapkan subscribeseperti itu (maafkan saya beberapa kesalahan, belum menulis java dalam beberapa tahun):

private HashMap<Class, LinkedList<? extends Messagable>> subscriberMap;

void subscribe(Class messageType, Messagable receiver) {
    LinkedList<Messagable> subscriberList = subscriberMap.get(messageType);
    if (subscriberList == null) {
        subscriberList = new LinkedList<? extends Messagable>();
        subscriberMap.put(messageType, subscriberList);
    }

    subscriberList.add(receiver);
}

Dan kemudian Anda bisa menyiarkan pesan seperti ini:

globalMessageQueue.broadcastMessage(new PlayerDiedMessage(42));

Dan broadcastMessagedapat diimplementasikan seperti ini:

void broadcastMessage(Message message) {
    Class messageType = message.getClass();
    LinkedList<? extends Messagable> subscribers = subscriberMap.get(messageType);

    for (Messagable receiver : subscribers) {
        receiver.onMessage(message);
    }
}

Anda juga dapat berlangganan pesan sedemikian rupa sehingga Anda dapat membagi penanganan pesan menjadi fungsi anonim yang berbeda:

void setupMessageSubscribers() {
    globalMessageQueue.subscribe(PlayerDiedMessage.getClass(), new Messagable() {
        public void onMessage(Message message) {
            PlayerDiedMessage msg = (PlayerDiedMessage)message;
            if (msg.livesLeft <= 0) {
                doSomething();
            }
        }
    });

    globalMessageQueue.subscriber(EnemySpawnedMessage.getClass(), new Messagable() {
        public void onMessage(Message message) {
            EnemySpawnedMessage msg = (EnemySpawnedMessage)message;
            if (msg.isBoss) {
                freakOut();
            }
        }
    });
}

Ini sedikit bertele-tele, tetapi membantu dengan organisasi. Anda juga dapat menerapkan fungsi kedua, subscribeAllyang akan menyimpan daftar Messagables terpisah yang ingin mendengar tentang semuanya.


1

1 . Jangan.

Pesan berbiaya rendah. Saya setuju dengan Markus von Broady. GC akan mengumpulkannya dengan cepat. Cobalah untuk tidak melampirkan banyak info tambahan untuk pesan. Itu hanya pesan. Menemukan instance dari adalah info dengan sendirinya. Gunakan itu (seperti yang Anda lakukan dalam contoh Anda).

2 . Anda dapat mencoba menggunakan MessageDispatcher .

Berbeda dengan solusi pertama, Anda mungkin memiliki pengirim pesan terpusat yang memproses pesan dan meneruskannya ke objek yang dituju.

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.