Bagaimana Melaksanakan dan Menggunakan Mesej Dihantar dalam Permainan Anda.
Malay (Melayu) translation by Urwah bin Azzubair (you can also view the original English article)
Permainan biasanya dibuat dari beberapa entiti yang berbeza yang berinteraksi antara satu sama lain. Interaksi tersebut cenderung sangat dinamik dan sangat berkaitan dengan permainan. Tutorial ini merangkumi konsep dan pelaksanaan sistem antrian mesej yang boleh menyatukan interaksi entiti, menjadikan kod anda mudah diurus dan mudah dikekalkan kerana ia semakin rumit.
Pengenalan
Bom boleh berinteraksi dengan aksara dengan meletup dan menyebabkan kerosakan, peranti perubatan boleh menyembuhkan entiti, kunci boleh membuka pintu, dan sebagainya. Interaksi dalam permainan tidak terhad, tetapi bagaimana kita dapat mengekalkan kod permainan dalam kawalan sementara masih menangani semua interaksi itu? Bagaimanakah kita memastikan kod itu berubah dan terus berfungsi apabila interaksi baru dan tidak dijangka muncul?

Apabila interaksi ditambah (terutama yang tidak dijangka), kod anda akan kelihatan lebih dan lebih berantakan. Pelaksanaan yang naif akan mendorong anda untuk bertanya soalan seperti:
"Ini entiti A, jadi saya perlu memanggil kaedah damage()
di atasnya dengan betul? Atau itu damageByItem ()
? Mungkin DamageByWeapon ()
adalah kaedah yang betul?"
Bayangkan kekacauan kekacauan merebak ke semua entiti permainan anda, kerana mereka semua berinteraksi antara satu sama lain dengan cara yang berbeza dan pelik. Nasib baik, ada cara yang lebih baik, mudah, dan lebih mudah diurus untuk melakukannya.
Perbaruan Mesej
Masukkan antrian mesej (mesej gilir). Idea asas di sebalik konsep ini adalah untuk menerapkan semua interaksi permainan sebagai sistem komunikasi (yang masih boleh digunakan hari ini): pertukaran mesej. Orang telah berkomunikasi melalui mesej (surat) selama berabad-abad kerana ini adalah sistem yang berkesan dan mudah.
Di dalam perkhidmatan pos dunia kami, kandungan setiap mesej boleh berbeza, tetapi cara mereka dihantar dan diterima secara fizikal tetap sama. Seorang pengirim memasuki maklumat tersebut ke dalam sampul surat dan menghantarnya ke destinasi. Objektif dapat menjawab (atau tidak) dengan mengikuti mekanisme yang sama, cukup ganti bidang "dari / ke" pada sampul surat.

Dengan menggunakan idea itu untuk permainan anda, semua interaksi antara entiti boleh dilihat sebagai mesej. Jika entiti permainan mahu berinteraksi dengan orang lain (atau kumpulan), yang perlu anda lakukan ialah menghantar mesej. Matlamat akan mengendalikan atau bertindak balas terhadap mesej berdasarkan kandungan mereka dan siapa penghantarnya.
Dalam pendekatan ini, komunikasi antara entiti permainan menjadi bersepadu.Semua entiti boleh menghantar dan menerima mesej. Tidak kira bagaimana kompleks atau ganjil interaksi atau mesej itu, saluran komunikasi sentiasa tetap sama.
Di bahagian seterusnya, saya akan menerangkan kepada anda bagaimana untuk melaksanakan pendekatan antrian mesej dalam permainan anda.
Merancang Sampul Surat (Mesej)
Mari kita mulakan dengan merancang sampul surat, yang merupakan elemen paling asas dalam sistem antrian mesej.
Sampul surat dapat digambarkan seperti dalam gambar di bawah:

Dua bidang pertama ( sender
dan destination
) adalah rujukan kepada entiti yang dicipta dan entiti yang akan menerima mesej ini. Dengan menggunakan bidang ini, kedua-dua penghantar dan penerima boleh mengetahui di mana mesej akan dihantar dan dari mana ia datang.
Dua bidang lain ( Type
dan Data
) berfungsi bersama untuk memastikan mesej dikendalikan dengan betul. Seksyen Type
menerangkan mesej ini; sebagai contoh, jika jenis "damage"
, penerima akan mengendalikan mesej ini sebagai arahan untuk mengurangkan titik kesihatan; jika jenis "pursue"
, penerima akan menganggapnya sebagai arahan untuk mengejar sesuatu - dan sebagainya.
Bahagian data
disambungkan terus ke medan type
. Seperti pada contoh terdahulu, jika jenis mesej adalah "damage"
, medan data
akan mengandungi sebutan, 10
- yang menerangkan jumlah kerosakan yang harus digunakan pada titik kesihatan. Jika jenis mesej "pursue"
, data
akan mengandungi objek yang menggambarkan target yang hendak dicuba.
Medan data
boleh mengandungi sebarang maklumat, yang menjadikan sampul surat cara komunikasi yang serba guna. Apa-apa pun boleh diletakkan di dalam medan tersebut: bilangan bulat, pelampung, tali, dan benda lain. Peraturan praktikal adalah bahawa penerima mesti mengetahui apa yang ada dalam medan data
berdasarkan apa yang ada di medan type
.
Semua teori ini boleh diterjemahkan ke dalam kelas yang sangat mudah dikenali sebagai Message
. Ia mengandungi empat sifat. Satu untuk setiap bidang.
Message = function (to, from, type, data) { // Properties this.to = to; // a reference to the entity that will receive this message this.from = from; // a reference to the entity that sent this message this.type = type; // the type of this message this.data = data; // the content/data of this message };
Sebagai contoh penggunaan ini, jika entiti A
mahu menghantar mesej "damage"
kepada entiti B
, perkara yang perlu dilakukan ialah memberi instantiate objek kelas Message
, tetapkan harta untuk
B
, tetapkan harta itu
sendiri (entiti A
), tetapkan type
kepada "damage"
dan akhirnya, tetapkan data
ke beberapa nombor ( 10
, sebagai contoh):
// Instantiate the two entities var entityA = new Entity(); var entityB = new Entity(); // Create a message to entityB, from entityA, // with type "damage" and data/value 10. var msg = new Message(); msg.to = entityB; msg.from = entityA; msg.type = "damage"; msg.data = 10; // You can also instantiate the message directly // passing the information it requires, like this: var msg = new Message(entityB, entityA, "damage", 10);
Sekarang kita mempunyai cara untuk membuat mesej, sudah tiba masanya untuk memikirkan kelas yang akan menyimpan dan menyampaikannya.
Melaksanakan Antrian
Kelas yang bertanggungjawab untuk menyimpan dan menyampaikan mesej akan dipanggil sebagai MessageQueue
(mesej antrian). Ini akan berfungsi sebagai pejabat pos: semua mesej dihantar ke kelas ini, yang memastikan mereka akan dihantar ke destinasi mereka.
Buat masa ini, kelas MessageQueue
akan mempunyai struktur yang sangat mudah:
/** * This class is responsible for receiving messages and * dispatching them to the destination. */ MessageQueue = function () { this.messages = []; // list of messages to be dispatched }; // Add a new message to the queue. The message must be an // instance of the class Message. MessageQueue.prototype.add = function(message) { this.messages.push(message); };
Ciri-ciri messages
adalah array. Ini akan menyimpan semua mesej yang akan dihantar oleh MessageQueue
. Kaedah add()
menerima objek kelas Message
sebagai parameter, dan menambah objek tersebut pada senarai mesej.
Begini bagaimana contoh entiti kita A
sebelum ini menghantar mesej pada entiti B
tentang kerosakan yang akan berfungsi menggunakan kelas MessageQueue
:
// Instantiate the two entities and the message queue var entityA = new Entity(); var entityB = new Entity(); var messageQueue = new MessageQueue(); // Create a message to entityB, from entityA, // with type "damage" and data/value 10. var msg = new Message(entityB, entityA, "damage", 10); // Add the message to the queue messageQueue.add(msg);
Sekarang kita mempunyai cara untuk membuat dan menyimpan mesej dalam barisan. Sekarang adalah masa untuk mendapatkan mereka untuk mencapai matlamat mereka.
Menghantar Mesej
Untuk menjadikan kelas MessageQueue
benar-benar menghantar mesej yang disiarkan, pertama kita perlu menentukan bagaimana entiti akan mengendalikan dan menerima mesej tersebut. Cara paling mudah adalah dengan menambah kaedah yang dinamakan onMessage ()
kepada mana-mana entiti yang boleh menerima mesej:
/** * This class describes a generic entity. */ Entity = function () { // Initialize anything here, e.g. Phaser stuff }; // This method is invoked by the MessageQueue // when there is a message to this entity. Entity.prototype.onMessage = function(message) { // Handle new message here };
Kelas MessageQueue
akan memanggil kaedah onMessage ()
pada setiap entiti yang seharusnya menerima mesej tersebut. Parameter yang diberikan kepada kaedah adalah mesej yang dihantar oleh sistem antrian (dan diterima oleh destinasi).
Kelas MessageQueue
akan menghantar mesej kepada giliran sekaligus, dalam kaedah dispatch()
:
/** * This class is responsible for receiving messages and * dispatching them to the destination. */ MessageQueue = function () { this.messages = []; // list of messages to be dispatched }; MessageQueue.prototype.add = function(message) { this.messages.push(message); }; // Dispatch all messages in the queue to their destination. MessageQueue.prototype.dispatch = function() { var i, entity, msg; // Iterave over the list of messages for(i = 0; this.messages.length; i++) { // Get the message of the current iteration msg = this.messages[i]; // Is it valid? if(msg) { // Fetch the entity that should receive this message // (the one in the 'to' field) entity = msg.to; // If that entity exists, deliver the message. if(entity) { entity.onMessage(msg); } // Delete the message from the queue this.messages.splice(i, 1); i--; } } };
Kaedah ini memulakan semua mesej dalam baris gilir dan, bagi setiap mesej, field to
-digunakan untuk mendapatkan rujukan kepada penerima. Kaedah onMessage ()
penerima kemudian dilancarkan, dengan mesej semasa sebagai parameter, dan mesej kemudian dipadamkan dari senarai MessageQueue
. Proses ini diulang sehingga semua mesej dihantar.
Menggunakan Mesej Dihantar
Sudah tiba masanya untuk melihat semua butiran pelaksanaan ini bekerjasama.Mari gunakan sistem antrian mesej kami dalam demo yang sangat mudah yang terdiri daripada beberapa entiti mudah alih yang berinteraksi antara satu sama lain. Untuk menjadi mudah, kami akan bekerjasama dengan tiga entiti: Healer
, Runner
dan Hunter
.
Runner
mempunyai bar kesihatan dan bergerak secara rawak. Healer
akan menyembuhkan setiap Runner
berlalu berdekatan; Sebaliknya, Hunter
akan menimbulkan kerosakan di tempat yang paling dekat dengan Runner
. Semua interaksi akan dikendalikan menggunakan sistem antrian mesej.
Tambah Mesej Dihantar
Mari kita mulakan dengan membuat PlayState
yang menyenaraikan entiti (penyembuh, pelari, dan pemburu) dan contoh kelas MessageQueue
:
var PlayState = function() { var entities; // list of entities in the game var messageQueue; // the message queue (dispatcher) this.create = function() { // Initialize the message queue messageQueue = new MessageQueue(); // Create a group of entities. entities = this.game.add.group(); }; this.update = function() { // Make all messages in the message queue // reach their destination. messageQueue.dispatch(); }; };
Dalam gelung permainan, diwakili oleh kaedah update()
, barisan pesanan dari kaedah dispatch()
dipanggil, supaya semua mesej dihantar pada akhir setiap bingkai permainan.
Ditambah Runner
Kelas Runner
mempunyai struktur berikut:
/** * This class describes an entity that just * wanders around. */ Runner = function () { // initialize Phaser stuff here... }; // Invoked by the game on each frame Runner.prototype.update = function() { // Make things move here... } // This method is invoked by the message queue // to make the runner deal with incoming messages. Runner.prototype.onMessage = function(message) { var amount; // Check the message type so it's possible to // decide if this message should be ignored or not. if(message.type == "damage") { // The message is about damage. // We must decrease our health points. The amount of // this decrease was informed by the message sender // in the 'data' field. amount = message.data; this.addHealth(-amount); } else if (message.type == "heal") { // The message is about healing. // We must increase our health points. Again the amount of // health points to increase was informed by the message sender // in the 'data' field. amount = message.data; this.addHealth(amount); } else { // Here we deal with messages we are not able to process. // Probably just ignore them :) } };
Bahagian yang paling penting ialah kaedah onMessage()
yang dipanggil oleh gilir mesej apabila terdapat mesej baru untuk contoh ini. Seperti yang disebutkan sebelumnya, bidang type
dalam mesej digunakan untuk menentukan komunikasi ini.
Berdasarkan jenis mesej, tindakan yang betul diambil: jika jenis mesej adalah "damage"
, titik kesihatan berkurangan; jika jenis mesej "heal"
, kenaikan nilai kesihatan. Bilangan titik kesihatan meningkat atau berkurang ditentukan oleh penghantar dalam bidang mesej data
.
Di PlayState
, kami menambah beberapa pelari ke senarai entiti:
var PlayState = function() { // (...) this.create = function() { // (...) // Add runners for(i = 0; i < 4; i++) { entities.add(new Runner(this.game, this.game.world.width * Math.random(), this.game.world.height * Math.random())); } }; // (...) };
Hasilnya adalah empat pelari bergerak secara rawak:
Tambah pemburu (pemburu)
Kelas Hunter
mengikut struktur berikut:
/** * This class describes an entity that just * wanders around hurting the runners that pass by. */ Hunter = function (game, x, y) { // initialize Phaser stuff here }; // Check if the entity is valid, is a runner, and is within the attack range. Hunter.prototype.canEntityBeAttacked = function(entity) { return entity && entity != this && (entity instanceof Runner) && !(entity instanceof Hunter) && entity.position.distance(this.position) <= 150; }; // Invoked by the game during the game loop. Hunter.prototype.update = function() { var entities, i, size, entity, msg; // Get a list of entities entities = this.getPlayState().getEntities(); for(i = 0, size = entities.length; i < size; i++) { entity = entities.getChildAt(i); // Is this entity a runner and is it close? if(this.canEntityBeAttacked(entity)) { // Yeah, so it's time to cause some damage! msg = new Message(entity, this, "damage", 2); // Send the message away! this.getMessageQueue().add(msg); // or just entity.onMessage(msg); if you want to bypass the message queue for some reasong. } } }; // Get a reference to the game's PlayState Hunter.prototype.getPlayState = function() { return this.game.state.states[this.game.state.current]; }; // Get a reference to the game's message queue. Hunter.prototype.getMessageQueue = function() { return this.getPlayState().getMessageQueue(); };
Hunter juga akan bergerak, tetapi akan menyebabkan kerosakan kepada semua pelari yang menghampiri. Tingkah laku ini diterapkan dalam kaedah update()
, di mana semua entiti permainan diperiksa dan pelari diberi mesej tentang kerosakan.
Mesej kerosakan dibuat seperti berikut:
msg = new Message(entity, this, "damage", 2);
Mesej ini mengandungi maklumat mengenai destinasi ( entitas
, dalam kes ini, yang merupakan entiti yang dianalisis dalam lelaran semasa), penghantar ( ini
mewakili pemburu yang melakukan serangan itu), jenis mesej ( " damage "
)dan kerosakan / kerosakan , (2
, dalam kes ini, diberikan kepada bidang mesej data
).
Mesej tersebut kemudian diposkan ke destinasi melalui this.getMessageQueue () menambahkan (msg)
, yang menambah mesej yang baru dibuat ke barisan mesej.
Akhir sekali, kami menambah Hunter
ke senarai entiti di PlayState
:
var PlayState = function() { // (...) this.create = function() { // (...) // Add hunter at position (20, 30) entities.add(new Hunter(this.game, 20, 30)); }; // (...) };
Hasilnya adalah beberapa pelari yang bergerak, menerima mesej dari pemburu ketika mereka rapat bersama:
Saya menambah sampul surat terbang sebagai bantuan visual untuk membantu menunjukkan apa yang sedang berlaku.
Craving Healer (Healer)
Kelas Healer
mempunyai struktur berikut:
/** * This class describes an entity that is * able to heal any runner that passes nearby. */ Healer = function (game, x, y) { // Initializer Phaser stuff here }; Healer.prototype.update = function() { var entities, i, size, entity, msg; // The the list of entities in the game entities = this.getPlayState().getEntities(); for(i = 0, size = entities.length; i < size; i++) { entity = entities.getChildAt(i); // Is it a valid entity? if(entity) { // Check if the entity is within the healing radius if(this.isEntityWithinReach(entity)) { // The entity can be healed! // First of all, create a new message regaring the healing msg = new Message(entity, this, "heal", 2); // Send the message away! this.getMessageQueue().add(msg); // or just entity.onMessage(msg); if you want to bypass the message queue for some reasong. } } } }; // Check if the entity is neither a healer nor a hunter and is within the healing radius. Healer.prototype.isEntityWithinReach = function(entity) { return !(entity instanceof Healer) && !(entity instanceof Hunter) && entity.position.distance(this.position) <= 200; }; // Get a reference to the game's PlayState Healer.prototype.getPlayState = function() { return this.game.state.states[this.game.state.current]; }; // Get a reference to the game's message queue. Healer.prototype.getMessageQueue = function() { return this.getPlayState().getMessageQueue(); };
Kod dan strukturnya sangat serupa dengan kelas Hunter
, kecuali beberapa perbezaan. Sama dengan pemburu yang telah dilaksanakan, kaedah pembaruan update()
penyembuh digunakan untuk senarai entiti dalam permainan, mesej setiap entiti dalam julat penyembuhannya:
msg = new Message(entity, this, "heal", 2);
Mesej ini juga mempunyai objektif ( entitas
), pengirim ( ini
, penyembuh melakukan tindakan), jenis mesej (" heal / penyembuhan"
) dan jumlah titik penyembuhan ( 2
, yang diberikan dalam bidang mesej data
).
Kami menambah healer
kepada senarai entiti di PlayState
dengan cara yang sama seperti hunter
kami dan hasilnya adalah adegan dengan pelari, pemburu dan penyembuh:
Dan itu sahaja! Kami mempunyai tiga entiti berbeza yang berinteraksi antara satu sama lain dengan menukar mesej.
Perbincangan Mengenai Fleksibiliti
Sistem antrian mesej ini adalah cara serba boleh untuk menguruskan interaksi dalam permainan. Interaksi dilakukan melalui saluran komunikasi bersatu dan mempunyai antara muka yang mudah digunakan dan dilaksanakan.
Memandangkan permainan anda berkembang dengan kerumitan, interaksi baru mungkin diperlukan. Sesetengah daripada mereka mungkin benar-benar tidak dijangka, jadi anda perlu menyesuaikan kod anda untuk menanganinya. Jika anda menggunakan sistem antrian mesej, masalahnya adalah untuk menambah mesej baru di suatu tempat dan mengendalikannya di tempat lain.
Sebagai contoh, bayangkan anda ingin membuat hunter
berinteraksi dengan healer
; Anda hanya perlu membuat hunter
menghantar mesej dengan interaksi baru-contohnya, "flee"
- dan memastikan bahawa healer
boleh mengendalikannya dalam kaedah onMessage
:
// In the Hunter class: Hunter.prototype.someMethod = function() { // Get a reference to a nearby healer var healer = this.getNearbyHealer(); // Create message about fleeing a place var place = {x: 30, y: 40}; var msg = new Message(entity, this, "flee", place); // Send the message away! this.getMessageQueue().add(msg); }; // In the Healer class: Healer.prototype.onMessage = function(message) { if(message.type == "flee") { // Get the place to flee from the data field in the message var place = message.data; // Use the place information flee(place.x, place.y); } };
Mengapa Tidak Hantar Mesej Secara Terus?
Semasa bertukar-tukar mesej antara entiti boleh berguna, anda mungkin berfikir mengapa MessageQueue
diperlukan. Tidakkah anda memanggil penerima?Kaedah onMessage()
sendiri tidak bergantung kepada MessageQueue
, seperti dalam kod di bawah?
Hunter.prototype.someMethod = function() { // Get a reference to a nearby healer var healer = this.getNearbyHealer(); // Create message about fleeing a place var place = {x: 30, y: 40}; var msg = new Message(entity, this, "flee", place); // Bypass the MessageQueue and directly deliver // the message to the healer. healer.onMessage(msg); };
Anda pasti dapat melaksanakan sistem pesanan seperti itu, tetapi penggunaan MessageQueue
mempunyai beberapa kelebihan.
Contohnya, dengan memusatkan pesanan, anda boleh menggunakan beberapa ciri yang keren seperti mesej yang tertangguh, keupayaan untuk menghantar mesej entiti kumpulan, dan maklumat debug visual (seperti sampul surat yang digunakan dalam tutorial ini).
Terdapat ruang untuk kreativiti dalam kelas MessageQueue
, sehingga anda dan keperluan permainan anda.
Kesimpulannya
Mengendalikan interaksi antara entiti permainan menggunakan sistem antrian mesej adalah cara untuk memastikan kod anda teratur dan bersedia untuk masa depan. Interaksi baru dapat dengan mudah dan cepat ditambah, bahkan ide-ide anda yang paling rumit, selagi mereka dirumuskan sebagai mesej.
Seperti yang dibincangkan dalam tutorial, anda boleh mengabaikan penggunaan baris gilir mesej utama dan hanya menghantar mesej terus ke entiti. Anda juga boleh memusatkan komunikasi menggunakan penghantaran (kelas MessageQueue
dalam kes kami) untuk memberi ruang kepada ciri-ciri baru pada masa akan datang, seperti mesej belum selesai.
Saya harap anda mendapati pendekatan ini berguna dan menambahnya kepada tali permainan pemaju permainan anda. Kaedah ini mungkin kelihatan berlebihan untuk projek kecil, tetapi ia pasti akan membuat anda sedikit sakit kepala dalam jangka masa panjang untuk permainan yang lebih besar.
Envato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post