Wie man eine Nachrichtenwarteschlange in Ihrem Spiel implementieren und verwenden kann
German (Deutsch) translation by Nikol Angelowa (you can also view the original English article)
Ein Spiel besteht normalerweise aus mehreren verschiedenen Einheiten, die miteinander interagieren. Diese Interaktionen sind in der Regel sehr dynamisch und eng mit dem Gameplay verbunden. Dieses Tutorial behandelt das Konzept und die Implementierung eines Nachrichtenwarteschlangensystems, das die Interaktionen von Entitäten vereinheitlichen kann, sodass Ihr Code bei zunehmender Komplexität verwaltbar und einfach zu warten ist.
Einführung
Eine Bombe kann mit einem Charakter interagieren, indem sie explodiert und Schaden anrichtet, ein Sanitätskit kann ein Wesen heilen, ein Schlüssel kann eine Tür öffnen und so weiter. Interaktionen in einem Spiel sind endlos, aber wie können wir den Spielcode überschaubar halten und trotzdem all diese Interaktionen bewältigen? Wie stellen wir sicher, dass sich der Code ändern und weiterhin funktionieren kann, wenn neue und unerwartete Interaktionen auftreten?



Wenn Interaktionen hinzugefügt werden (insbesondere die unerwarteten), wird Ihr Code immer unübersichtlicher. Eine naive Implementierung führt schnell dazu, dass Sie Fragen stellen wie:
"Das ist Entity A, also sollte ich die Methode damage() darauf aufrufen, oder? Oder ist es damageByItem()? Vielleicht ist diese damageByWeapon() Methode die richtige?"
Stellen Sie sich vor, dass sich dieses überladene Chaos auf alle Ihre Spieleinheiten ausbreitet, weil sie alle auf unterschiedliche und eigentümliche Weise miteinander interagieren. Zum Glück gibt es eine bessere, einfachere und überschaubarere Methode.
Nachrichtenwarteschlange
Geben Sie die Nachrichtenwarteschlange ein. Die Grundidee dieses Konzepts besteht darin, alle Spielinteraktionen als Kommunikationssystem (das heute noch verwendet wird) umzusetzen: Nachrichtenaustausch. Menschen kommunizieren seit Jahrhunderten über Nachrichten (Briefe), weil es ein effektives und einfaches System ist.
In unseren realen Postdiensten kann sich der Inhalt jeder Nachricht unterscheiden, aber die Art und Weise, wie sie physisch gesendet und empfangen werden, bleibt gleich. Ein Absender steckt die Informationen in einen Umschlag und adressiert ihn an ein Ziel. Das Ziel kann antworten (oder nicht), indem es dem gleichen Mechanismus folgt, indem es einfach die "von/bis"-Felder auf dem Umschlag ändert.



Wenn Sie diese Idee auf Ihr Spiel anwenden, können alle Interaktionen zwischen Entitäten als Nachrichten angesehen werden. Wenn eine Spielinstanz mit einer anderen (oder einer Gruppe von ihnen) interagieren möchte, muss sie lediglich eine Nachricht senden. Das Ziel verarbeitet oder reagiert auf die Nachricht basierend auf ihrem Inhalt und darauf, wer der Absender ist.
Bei diesem Ansatz wird die Kommunikation zwischen Spieleinheiten vereinheitlicht. Alle Entitäten können Nachrichten senden und empfangen. Egal wie komplex oder eigenartig die Interaktion oder Botschaft ist, der Kommunikationskanal bleibt immer derselbe.
In den nächsten Abschnitten werde ich beschreiben, wie Sie diesen Message Queue-Ansatz tatsächlich in Ihrem Spiel implementieren können.
Entwerfen eines Umschlags (Nachricht)
Beginnen wir damit, den Umschlag zu entwerfen, der das grundlegendste Element im Nachrichtenwarteschlangensystem ist.
Ein Umschlag kann wie in der folgenden Abbildung beschrieben werden:



Die ersten beiden Felder (sender und destination) sind Verweise auf die Entität, die diese Nachricht erstellt hat, bzw. die Entität, die diese Nachricht empfängt. Anhand dieser Felder können sowohl der Absender als auch der Empfänger erkennen, wohin die Nachricht geht und woher sie stammt.
Die anderen beiden Felder (type und data) arbeiten zusammen, um sicherzustellen, dass die Nachricht richtig verarbeitet wird. Das Feld type beschreibt, worum es in dieser Nachricht geht; Wenn der Typ beispielsweise "damage" ist, wird der Empfänger diese Nachricht als Befehl behandeln, seine Gesundheitspunkte zu verringern; wenn der Typ "pursue" ist, nimmt der Empfänger es als Anweisung, etwas zu verfolgen - und so weiter.
Das data-Feld ist direkt mit dem type-Feld verbunden. Wenn der Nachrichtentyp in den vorherigen Beispielen "damage" lautet, enthält das data-Feld eine Zahl - sagen wir 10 -, die den Schaden beschreibt, den der Empfänger seinen Gesundheitspunkten zufügen sollte. Wenn der Nachrichtentyp "pursue" ist, enthalten die data ein Objekt, das das zu verfolgende Ziel beschreibt.
Das data-Feld kann beliebige Informationen enthalten, was den Umschlag zu einem vielseitigen Kommunikationsmittel macht. In diesem Feld kann alles platziert werden: Ganzzahlen, Floats, Strings und sogar andere Objekte. Als Faustregel gilt, dass der Empfänger basierend auf dem type-feld wissen muss, was im data-Feld steht.
All diese Theorie kann in eine sehr einfache Klasse namens Message übersetzt werden. Es enthält vier Eigenschaften, eine für jedes Feld:
1 |
Message = function (to, from, type, data) { |
2 |
// Properties
|
3 |
this.to = to; // a reference to the entity that will receive this message |
4 |
this.from = from; // a reference to the entity that sent this message |
5 |
this.type = type; // the type of this message |
6 |
this.data = data; // the content/data of this message |
7 |
};
|
Als Beispiel dafür, wenn eine Entität A eine "damage"-Nachricht an Entität B senden möchte, muss sie lediglich ein Objekt der Klasse Message instanziieren, die Eigenschaft to B setzen, die Eigenschaft auf from setzen selbst (Entität A), setze type auf "damage" und setze schließlich data auf eine Zahl (zum Beispiel 10):
1 |
// Instantiate the two entities
|
2 |
var entityA = new Entity(); |
3 |
var entityB = new Entity(); |
4 |
|
5 |
// Create a message to entityB, from entityA,
|
6 |
// with type "damage" and data/value 10.
|
7 |
var msg = new Message(); |
8 |
|
9 |
msg.to = entityB; |
10 |
msg.from = entityA; |
11 |
msg.type = "damage"; |
12 |
msg.data = 10; |
13 |
|
14 |
// You can also instantiate the message directly
|
15 |
// passing the information it requires, like this:
|
16 |
var msg = new Message(entityB, entityA, "damage", 10); |
Da wir nun eine Möglichkeit haben, Nachrichten zu erstellen, ist es an der Zeit, über die Klasse nachzudenken, die sie speichert und übermittelt.
Implementieren einer Warteschlange
Die für das Speichern und Zustellen der Nachrichten verantwortliche Klasse wird MessageQueue genannt. Es wird als Postamt funktionieren: Alle Nachrichten werden an diese Klasse übergeben, die sicherstellt, dass sie an ihr Ziel gesendet werden.
Im Moment hat die MessageQueue-Klasse eine sehr einfache Struktur:
1 |
/**
|
2 |
* This class is responsible for receiving messages and
|
3 |
* dispatching them to the destination.
|
4 |
*/
|
5 |
MessageQueue = function () { |
6 |
this.messages = []; // list of messages to be dispatched |
7 |
};
|
8 |
|
9 |
// Add a new message to the queue. The message must be an
|
10 |
// instance of the class Message.
|
11 |
MessageQueue.prototype.add = function(message) { |
12 |
this.messages.push(message); |
13 |
};
|
Die Eigenschaft messages ist ein Array. Es speichert alle Nachrichten, die von der MessageQueue zugestellt werden. Die Methode add() erhält als Parameter ein Objekt der Klasse Message und fügt dieses Objekt der Liste der Nachrichten hinzu.
So würde unser vorheriges Beispiel für die Nachricht von Entität A, die Entität B über Schäden informiert, mit der MessageQueue-Klasse funktionieren:
1 |
// Instantiate the two entities and the message queue
|
2 |
var entityA = new Entity(); |
3 |
var entityB = new Entity(); |
4 |
var messageQueue = new MessageQueue(); |
5 |
|
6 |
// Create a message to entityB, from entityA,
|
7 |
// with type "damage" and data/value 10.
|
8 |
var msg = new Message(entityB, entityA, "damage", 10); |
9 |
|
10 |
// Add the message to the queue
|
11 |
messageQueue.add(msg); |
Wir haben jetzt eine Möglichkeit, Nachrichten in einer Warteschlange zu erstellen und zu speichern. Es ist an der Zeit, dass sie ihr Ziel erreichen.
Zustellung von Nachrichten
Damit die MessageQueue-Klasse die geposteten Nachrichten tatsächlich versendet, müssen wir zunächst definieren, wie Entitäten Nachrichten behandeln und empfangen. Der einfachste Weg besteht darin, jeder Entität, die Nachrichten empfangen kann, eine Methode namens onMessage() hinzuzufügen:
1 |
/**
|
2 |
* This class describes a generic entity.
|
3 |
*/
|
4 |
Entity = function () { |
5 |
// Initialize anything here, e.g. Phaser stuff
|
6 |
};
|
7 |
|
8 |
// This method is invoked by the MessageQueue
|
9 |
// when there is a message to this entity.
|
10 |
Entity.prototype.onMessage = function(message) { |
11 |
// Handle new message here
|
12 |
};
|
Die MessageQueue-Klasse ruft die onMessage()-Methode jeder Entität auf, die eine Nachricht empfangen muss. Der an diese Methode übergebene Parameter ist die Nachricht, die vom Warteschlangensystem zugestellt wird (und vom Ziel empfangen wird).
Die MessageQueue-Klasse verteilt die Nachrichten in ihrer Warteschlange alle auf einmal in der Methode "dispatch()":
1 |
/**
|
2 |
* This class is responsible for receiving messages and
|
3 |
* dispatching them to the destination.
|
4 |
*/
|
5 |
MessageQueue = function () { |
6 |
this.messages = []; // list of messages to be dispatched |
7 |
};
|
8 |
|
9 |
MessageQueue.prototype.add = function(message) { |
10 |
this.messages.push(message); |
11 |
};
|
12 |
|
13 |
// Dispatch all messages in the queue to their destination.
|
14 |
MessageQueue.prototype.dispatch = function() { |
15 |
var i, entity, msg; |
16 |
|
17 |
// Iterave over the list of messages
|
18 |
for(i = 0; this.messages.length; i++) { |
19 |
// Get the message of the current iteration
|
20 |
msg = this.messages[i]; |
21 |
|
22 |
// Is it valid?
|
23 |
if(msg) { |
24 |
// Fetch the entity that should receive this message
|
25 |
// (the one in the 'to' field)
|
26 |
entity = msg.to; |
27 |
|
28 |
// If that entity exists, deliver the message.
|
29 |
if(entity) { |
30 |
entity.onMessage(msg); |
31 |
}
|
32 |
|
33 |
// Delete the message from the queue
|
34 |
this.messages.splice(i, 1); |
35 |
i--; |
36 |
}
|
37 |
}
|
38 |
};
|
Diese Methode durchläuft alle Nachrichten in der Warteschlange und für jede Nachricht wird das Feld to verwendet, um eine Referenz auf den Empfänger abzurufen. Anschließend wird die Methode onMessage() des Empfängers mit der aktuellen Nachricht als Parameter aufgerufen und die zugestellte Nachricht anschließend aus der MessageQueue-Liste entfernt. Dieser Vorgang wird wiederholt, bis alle Nachrichten versendet sind.
Verwenden einer Nachrichtenwarteschlange
Es ist an der Zeit, alle Details dieser Implementierung gemeinsam zu sehen. Lassen Sie uns unser Nachrichtenwarteschlangensystem in einer sehr einfachen Demo verwenden, die aus wenigen beweglichen Entitäten besteht, die miteinander interagieren. Der Einfachheit halber arbeiten wir mit drei Entitäten: Healer, Runner und Hunter.
Der Runner hat eine Gesundheitsleiste und bewegt sich zufällig. Der Healer heilt jeden Runner, der in der Nähe vorbeikommt; Auf der anderen Seite fügt der Hunter jedem Runner in der Nähe Schaden zu. Alle Interaktionen werden über das Nachrichtenwarteschlangensystem abgewickelt.
Hinzufügen der Nachrichtenwarteschlange
Beginnen wir mit dem Erstellen des PlayState, der eine Liste von Entitäten (Heiler, Läufer und Jäger) und eine Instanz der MessageQueue-Klasse enthält:
1 |
var PlayState = function() { |
2 |
var entities; // list of entities in the game |
3 |
var messageQueue; // the message queue (dispatcher) |
4 |
|
5 |
this.create = function() { |
6 |
// Initialize the message queue
|
7 |
messageQueue = new MessageQueue(); |
8 |
|
9 |
// Create a group of entities.
|
10 |
entities = this.game.add.group(); |
11 |
};
|
12 |
|
13 |
this.update = function() { |
14 |
// Make all messages in the message queue
|
15 |
// reach their destination.
|
16 |
messageQueue.dispatch(); |
17 |
};
|
18 |
};
|
In der Spielschleife, die durch die Methode update() repräsentiert wird, wird die Methode dispatch() der Nachrichtenwarteschlange aufgerufen, sodass alle Nachrichten am Ende jedes Spielrahmens zugestellt werden.
Hinzufügen der Läufer
Die Runner-Klasse hat die folgende Struktur:
1 |
/**
|
2 |
* This class describes an entity that just
|
3 |
* wanders around.
|
4 |
*/
|
5 |
Runner = function () { |
6 |
// initialize Phaser stuff here...
|
7 |
};
|
8 |
|
9 |
// Invoked by the game on each frame
|
10 |
Runner.prototype.update = function() { |
11 |
// Make things move here...
|
12 |
}
|
13 |
|
14 |
// This method is invoked by the message queue
|
15 |
// to make the runner deal with incoming messages.
|
16 |
Runner.prototype.onMessage = function(message) { |
17 |
var amount; |
18 |
|
19 |
// Check the message type so it's possible to
|
20 |
// decide if this message should be ignored or not.
|
21 |
if(message.type == "damage") { |
22 |
// The message is about damage.
|
23 |
// We must decrease our health points. The amount of
|
24 |
// this decrease was informed by the message sender
|
25 |
// in the 'data' field.
|
26 |
amount = message.data; |
27 |
this.addHealth(-amount); |
28 |
|
29 |
} else if (message.type == "heal") { |
30 |
// The message is about healing.
|
31 |
// We must increase our health points. Again the amount of
|
32 |
// health points to increase was informed by the message sender
|
33 |
// in the 'data' field.
|
34 |
amount = message.data; |
35 |
this.addHealth(amount); |
36 |
|
37 |
} else { |
38 |
// Here we deal with messages we are not able to process.
|
39 |
// Probably just ignore them :)
|
40 |
}
|
41 |
};
|
Der wichtigste Teil ist die Methode onMessage(), die von der Nachrichtenwarteschlange jedes Mal aufgerufen wird, wenn eine neue Nachricht für diese Instanz vorliegt. Wie bereits erläutert, wird anhand des Feld type in der Nachricht entschieden, worum es bei dieser Kommunikation geht.
Basierend auf dem Nachrichtentyp wird die richtige Aktion ausgeführt: Wenn der Nachrichtentyp "damage" lautet, werden die Gesundheitspunkte verringert; wenn der Nachrichtentyp "heal" ist, werden die Lebenspunkte erhöht. Die Anzahl der Gesundheitspunkte, um die erhöht oder verringert werden soll, wird vom Absender im data-Feld der Nachricht definiert.
Im PlayState fügen wir der Liste der Entitäten einige Läufer hinzu:
1 |
var PlayState = function() { |
2 |
// (...)
|
3 |
|
4 |
this.create = function() { |
5 |
// (...)
|
6 |
|
7 |
// Add runners
|
8 |
for(i = 0; i < 4; i++) { |
9 |
entities.add(new Runner(this.game, this.game.world.width * Math.random(), this.game.world.height * Math.random())); |
10 |
}
|
11 |
};
|
12 |
|
13 |
// (...)
|
14 |
};
|
Das Ergebnis sind vier Läufer, die sich zufällig bewegen:
Hinzufügen des Jägers
Die Hunter-Klasse hat folgende Struktur:
1 |
/**
|
2 |
* This class describes an entity that just
|
3 |
* wanders around hurting the runners that pass by.
|
4 |
*/
|
5 |
Hunter = function (game, x, y) { |
6 |
// initialize Phaser stuff here
|
7 |
};
|
8 |
|
9 |
// Check if the entity is valid, is a runner, and is within the attack range.
|
10 |
Hunter.prototype.canEntityBeAttacked = function(entity) { |
11 |
return entity && entity != this && |
12 |
(entity instanceof Runner) && |
13 |
!(entity instanceof Hunter) && |
14 |
entity.position.distance(this.position) <= 150; |
15 |
};
|
16 |
|
17 |
// Invoked by the game during the game loop.
|
18 |
Hunter.prototype.update = function() { |
19 |
var entities, i, size, entity, msg; |
20 |
|
21 |
// Get a list of entities
|
22 |
entities = this.getPlayState().getEntities(); |
23 |
|
24 |
for(i = 0, size = entities.length; i < size; i++) { |
25 |
entity = entities.getChildAt(i); |
26 |
|
27 |
// Is this entity a runner and is it close?
|
28 |
if(this.canEntityBeAttacked(entity)) { |
29 |
// Yeah, so it's time to cause some damage!
|
30 |
msg = new Message(entity, this, "damage", 2); |
31 |
|
32 |
// Send the message away!
|
33 |
this.getMessageQueue().add(msg); // or just entity.onMessage(msg); if you want to bypass the message queue for some reasong. |
34 |
}
|
35 |
}
|
36 |
};
|
37 |
|
38 |
// Get a reference to the game's PlayState
|
39 |
Hunter.prototype.getPlayState = function() { |
40 |
return this.game.state.states[this.game.state.current]; |
41 |
};
|
42 |
|
43 |
// Get a reference to the game's message queue.
|
44 |
Hunter.prototype.getMessageQueue = function() { |
45 |
return this.getPlayState().getMessageQueue(); |
46 |
};
|
Die Jäger werden sich auch bewegen, aber sie werden allen Läufern, die sich in der Nähe befinden, Schaden zufügen. Dieses Verhalten ist in der Methode update() implementiert, bei der alle Entitäten des Spiels überprüft werden und die Läufer über Schäden informiert werden.
Die Schadensmeldung wird wie folgt erstellt:
1 |
msg = new Message(entity, this, "damage", 2); |
Die Nachricht enthält die Informationen über das Ziel (in diesem Fall die entity, die in der aktuellen Iteration analysiert wird), den Absender (this repräsentiert den Jäger, der den Angriff durchführt), den Typ der Nachricht ("damage") und die Schadenshöhe (hier 2 dem data-Feld der Nachricht zugeordnet).
Die Nachricht wird dann über den Befehl this.getMessageQueue().add(msg) an das Ziel gesendet, der die neu erstellte Nachricht der Nachrichtenwarteschlange hinzufügt.
Schließlich fügen wir den Hunter der Liste der Entitäten im PlayState hinzu:
1 |
var PlayState = function() { |
2 |
// (...)
|
3 |
|
4 |
this.create = function() { |
5 |
// (...)
|
6 |
|
7 |
// Add hunter at position (20, 30)
|
8 |
entities.add(new Hunter(this.game, 20, 30)); |
9 |
};
|
10 |
|
11 |
// (...)
|
12 |
};
|
Das Ergebnis sind einige Läufer, die sich bewegen und Nachrichten vom Jäger erhalten, wenn sie sich einander nähern:
Ich habe die fliegenden Umschläge als visuelle Hilfe hinzugefügt, um zu zeigen, was vor sich geht.
Hinzufügen des Heilers
Die Healer-Klasse hat folgende Struktur:
1 |
/**
|
2 |
* This class describes an entity that is
|
3 |
* able to heal any runner that passes nearby.
|
4 |
*/
|
5 |
Healer = function (game, x, y) { |
6 |
// Initializer Phaser stuff here
|
7 |
};
|
8 |
|
9 |
Healer.prototype.update = function() { |
10 |
var entities, i, size, entity, msg; |
11 |
|
12 |
// The the list of entities in the game
|
13 |
entities = this.getPlayState().getEntities(); |
14 |
|
15 |
for(i = 0, size = entities.length; i < size; i++) { |
16 |
entity = entities.getChildAt(i); |
17 |
|
18 |
// Is it a valid entity?
|
19 |
if(entity) { |
20 |
// Check if the entity is within the healing radius
|
21 |
if(this.isEntityWithinReach(entity)) { |
22 |
// The entity can be healed!
|
23 |
// First of all, create a new message regaring the healing
|
24 |
msg = new Message(entity, this, "heal", 2); |
25 |
|
26 |
// Send the message away!
|
27 |
this.getMessageQueue().add(msg); // or just entity.onMessage(msg); if you want to bypass the message queue for some reasong. |
28 |
}
|
29 |
}
|
30 |
}
|
31 |
};
|
32 |
|
33 |
// Check if the entity is neither a healer nor a hunter and is within the healing radius.
|
34 |
Healer.prototype.isEntityWithinReach = function(entity) { |
35 |
return !(entity instanceof Healer) && !(entity instanceof Hunter) && entity.position.distance(this.position) <= 200; |
36 |
};
|
37 |
|
38 |
// Get a reference to the game's PlayState
|
39 |
Healer.prototype.getPlayState = function() { |
40 |
return this.game.state.states[this.game.state.current]; |
41 |
};
|
42 |
|
43 |
// Get a reference to the game's message queue.
|
44 |
Healer.prototype.getMessageQueue = function() { |
45 |
return this.getPlayState().getMessageQueue(); |
46 |
};
|
Der Code und die Struktur sind der Hunter-Klasse sehr ähnlich, mit Ausnahme einiger Unterschiede. Ähnlich wie bei der Implementierung des Jägers durchläuft die update()-Methode des Heilers die Liste der Entitäten im Spiel und sendet eine Nachricht an jede Entität innerhalb ihrer Heilungsreichweite:
1 |
msg = new Message(entity, this, "heal", 2); |
Die Nachricht hat auch ein Ziel (entity), einen Absender (das ist der Heiler, this die Aktion ausführt), einen Nachrichtentyp ("heal") und die Anzahl der Heilpunkte (2, zugewiesen im data-Feld der Nachricht) .
Wir fügen den Healer der Liste der Entitäten im PlayState hinzu, wie wir es beim Hunter getan haben, und das Ergebnis ist eine Szene mit Läufern, einem Jäger und einem Heiler:
Und das ist es! Wir haben drei verschiedene Entitäten, die miteinander interagieren, indem sie Nachrichten austauschen.
Diskussion über Flexibilität
Dieses Nachrichtenwarteschlangensystem ist eine vielseitige Möglichkeit, Interaktionen in einem Spiel zu verwalten. Die Interaktionen erfolgen über einen einheitlichen Kommunikationskanal mit einer einzigen Schnittstelle, die einfach zu bedienen und zu implementieren ist.
Wenn Ihr Spiel komplexer wird, sind möglicherweise neue Interaktionen erforderlich. Einige von ihnen können völlig unerwartet sein, daher müssen Sie Ihren Code anpassen, um mit ihnen umzugehen. Wenn Sie ein Nachrichtenwarteschlangensystem verwenden, müssen Sie irgendwo eine neue Nachricht hinzufügen und in einer anderen verarbeiten.
Stellen Sie sich zum Beispiel vor, Sie möchten, dass der Hunter mit dem Healer interagiert; Sie müssen den Hunter nur dazu bringen, eine Nachricht mit der neuen Interaktion zu senden – zum Beispiel "flee" – und sicherstellen, dass der Healer damit in der onMessage-Methode umgehen kann:
1 |
// In the Hunter class:
|
2 |
Hunter.prototype.someMethod = function() { |
3 |
// Get a reference to a nearby healer
|
4 |
var healer = this.getNearbyHealer(); |
5 |
|
6 |
// Create message about fleeing a place
|
7 |
var place = {x: 30, y: 40}; |
8 |
var msg = new Message(entity, this, "flee", place); |
9 |
|
10 |
// Send the message away!
|
11 |
this.getMessageQueue().add(msg); |
12 |
};
|
13 |
|
14 |
// In the Healer class:
|
15 |
Healer.prototype.onMessage = function(message) { |
16 |
if(message.type == "flee") { |
17 |
// Get the place to flee from the data field in the message
|
18 |
var place = message.data; |
19 |
|
20 |
// Use the place information
|
21 |
flee(place.x, place.y); |
22 |
}
|
23 |
};
|
Warum nicht einfach Nachrichten direkt senden?
Obwohl der Austausch von Nachrichten zwischen Entitäten nützlich sein kann, denken Sie vielleicht, warum die MessageQueue überhaupt benötigt wird. Können Sie die onMessage()-Methode des Empfängers nicht einfach selbst aufrufen, anstatt sich auf die MessageQueue zu verlassen, wie im folgenden Code?
1 |
Hunter.prototype.someMethod = function() { |
2 |
// Get a reference to a nearby healer
|
3 |
var healer = this.getNearbyHealer(); |
4 |
|
5 |
// Create message about fleeing a place
|
6 |
var place = {x: 30, y: 40}; |
7 |
var msg = new Message(entity, this, "flee", place); |
8 |
|
9 |
// Bypass the MessageQueue and directly deliver
|
10 |
// the message to the healer.
|
11 |
healer.onMessage(msg); |
12 |
};
|
Ein solches Nachrichtensystem könnte man durchaus implementieren, aber der Einsatz einer MessageQueue hat einige Vorteile.
Durch die Zentralisierung des Versands von Nachrichten können Sie beispielsweise einige coole Funktionen wie verzögerte Nachrichten, die Möglichkeit, eine Gruppe von Entitäten zu benachrichtigen, und visuelle Debug-Informationen (wie die in diesem Tutorial verwendeten fliegenden Umschläge) implementieren.
In der MessageQueue-Klasse ist Raum für Kreativität, es liegt an Ihnen und den Anforderungen Ihres Spiels.
Abschluss
Die Handhabung von Interaktionen zwischen Spielentitäten mithilfe eines Nachrichtenwarteschlangensystems ist eine Möglichkeit, Ihren Code organisiert und für die Zukunft bereit zu halten. Neue Interaktionen können einfach und schnell hinzugefügt werden, selbst Ihre komplexesten Ideen, solange sie als Nachrichten gekapselt sind.
Wie im Tutorial beschrieben, können Sie die Verwendung einer zentralen Nachrichtenwarteschlange ignorieren und Nachrichten einfach direkt an die Entitäten senden. Sie können die Kommunikation auch über einen Dispatch (in unserem Fall die MessageQueue-Klasse) zentralisieren, um in Zukunft Platz für neue Funktionen wie verzögerte Nachrichten zu schaffen.
Ich hoffe, Sie finden diesen Ansatz nützlich und fügen ihn Ihrem Dienstprogramm für Spieleentwickler hinzu. Die Methode mag für kleine Projekte übertrieben erscheinen, wird Ihnen aber auf lange Sicht bei größeren Spielen sicherlich einige Kopfschmerzen ersparen.



