Haben Sie sich jemals gefragt, wie in Spielen wie
Super Meat Boy die Wiedergabefunktion implementiert ist? Eine Möglichkeit, dies zu implementieren, besteht darin, die Eingabe auf die gleiche Weise wie der Player auszuführen, was wiederum bedeutet, dass die Eingabe irgendwie gespeichert werden muss. Sie können
das Befehlsmuster für dieses und vieles mehr verwenden.
Die Befehlsvorlage ist auch nützlich, um Rückgängig- und Wiederherstellungsfunktionen in einem Strategiespiel zu erstellen.
In diesem Tutorial implementieren wir die Befehlsvorlage in C # und führen damit den Bot-Charakter durch ein dreidimensionales Labyrinth. Aus dem Tutorial lernen Sie:
- Die Grundlagen des Befehlsmusters.
- So implementieren Sie das Befehlsmuster
- So erstellen Sie eine Warteschlange mit Eingabebefehlen und verzögern deren Ausführung.
Hinweis : Es wird davon ausgegangen, dass Sie bereits mit Unity vertraut sind und über durchschnittliche Kenntnisse in C # verfügen. In diesem Tutorial werden wir mit Unity 2019.1 und C # 7 arbeiten .
An die Arbeit gehen
Laden Sie zunächst die
Projektmaterialien herunter. Entpacken Sie die Datei und öffnen Sie das
Starter- Projekt in Unity.
Gehen Sie zu
RW / Szenen und öffnen Sie die Hauptszene. Die Szene besteht aus einem Bot und einem Labyrinth sowie einer Terminal-Benutzeroberfläche, die Anweisungen anzeigt. Das Level-Design wird in Form eines Gitters erstellt, was nützlich ist, wenn wir den Bot visuell durch das Labyrinth bewegen.
Wenn Sie auf
Wiedergabe klicken, werden wir feststellen, dass die Anweisungen nicht funktionieren. Dies ist normal, da wir diese Funktionalität zum Lernprogramm hinzufügen.
Der interessanteste Teil der Szene ist der GameObject
Bot . Wählen Sie es im Hierarchiefenster aus, indem Sie darauf klicken.
Im Inspektor können Sie sehen, dass es eine
Bot- Komponente gibt. Wir werden diese Komponente verwenden, indem wir Eingabebefehle ausgeben.
Wir verstehen die Logik des Bots
Gehen Sie zu
RW / Scripts und öffnen Sie das
Bot- Skript im Code-Editor. Sie müssen nicht wissen, was im
Bot- Skript passiert. Schauen Sie sich jedoch zwei Methoden an:
Move
und
Shoot
. Auch hier müssen Sie nicht herausfinden, was in diesen Methoden vor sich geht, aber Sie müssen verstehen, wie Sie sie verwenden.
Beachten Sie, dass die
Move
Methode einen Eingabeparameter
CardinalDirection
empfängt.
CardinalDirection
ist eine Aufzählung. Ein Aufzählungselement vom Typ
CardinalDirection
kann
Up
,
Down
,
Right
oder
Left
. Abhängig von der ausgewählten
CardinalDirection
bewegt sich
CardinalDirection
Bot genau ein Quadrat entlang des Gitters in die entsprechende Richtung.
Die
Shoot
Methode zwingt den Bot, Granaten abzufeuern, die die
gelben Wände zerstören, aber gegen andere Wände unbrauchbar sind.
Schauen Sie sich
ResetToLastCheckpoint
die
ResetToLastCheckpoint
Methode an. Um zu verstehen, was er tut, schauen Sie sich das Labyrinth an. Im Labyrinth befinden sich Punkte, die als
Checkpoint bezeichnet werden . Um das Labyrinth zu passieren, muss der Bot zum
grünen Kontrollpunkt gelangen.
Wenn ein Bot einen neuen Kontrollpunkt betritt, wird dies der
letzte für ihn.
ResetToLastCheckpoint
setzt die Position des Bots zurück und verschiebt ihn zum letzten Kontrollpunkt.
Obwohl wir diese Methoden nicht verwenden können, werden wir sie bald beheben. Um zu beginnen, müssen Sie sich mit dem Entwurfsmuster des
Befehls vertraut machen.
Was ist das Befehlsentwurfsmuster?
Das Befehlsmuster ist eines von 23 Entwurfsmustern, die im Buch
Entwurfsmuster: Elemente wiederverwendbarer objektorientierter Software beschrieben sind, das von der „Viererbande“ von Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides (
GoF , Viererbande) geschrieben wurde.
Die Autoren berichten, dass "das Befehlsmuster die Anforderung als Objekt kapselt, sodass wir andere Objekte mit anderen Anforderungen, Warteschlangen- oder Protokollanforderungen parametrisieren und umkehrbare Operationen unterstützen können."
Wow! Es ist wie?
Ich verstehe, dass diese Definition nicht sehr einfach ist, also lassen Sie uns sie analysieren.
Kapselung bedeutet, dass ein Methodenaufruf als Objekt gekapselt werden kann.
Die gekapselte Methode kann abhängig vom Eingabeparameter viele Objekte beeinflussen. Dies wird als
Parametrisierung anderer Objekte bezeichnet.
Der resultierende „Befehl“ kann zusammen mit anderen Teams gespeichert werden, bis sie ausgeführt werden. Dies ist die Anforderungswarteschlange.
Team-WarteschlangeUmkehrbarkeit bedeutet schließlich, dass Vorgänge mit der Rückgängig-Funktion zurückgesetzt werden können.
OK, aber wie spiegelt sich das im Code wider?
Die
Command- Klasse verfügt über eine
Execute- Methode, die als Eingabeparameter das Objekt (von dem der Befehl ausgeführt wird) namens
Receiver empfängt. Das heißt, die Execute-Methode wird tatsächlich von der Command-Klasse
gekapselt .
Viele Instanzen der Command-Klasse können als normale Objekte übergeben werden, dh sie können in Datenstrukturen wie einer Warteschlange, einem Stapel usw. gespeichert werden.
Um einen Befehl auszuführen, müssen Sie seine Execute-Methode aufrufen. Die Klasse, die die Ausführung startet, heißt
Invoker .
Das Projekt enthält derzeit eine leere Klasse namens
BotCommand
. Im nächsten Abschnitt werden wir die Implementierung des oben genannten implementieren, damit der Bot Aktionen mithilfe der Befehlsvorlage ausführen kann.
Bewegen Sie den Bot
Implementierung des Befehlsmusters
In diesem Abschnitt implementieren wir das Befehlsmuster. Es gibt viele Möglichkeiten, dies zu implementieren. In diesem Tutorial werden wir einen von ihnen behandeln.
Gehen Sie zunächst zu
RW / Scripts und öffnen Sie das
BotCommand- Skript im Editor. Die
BotCommand
Klasse
BotCommand
noch leer, aber nicht lange.
Fügen Sie den folgenden Code in die Klasse ein:
Was ist hier los?
- Die Variable
commandName
einfach zum Speichern des für Menschen lesbaren Befehlsnamens verwendet. Es ist nicht erforderlich, es in der Vorlage zu verwenden, aber wir werden es später im Tutorial benötigen. - Der Konstruktor von
BotCommand
erhält eine Funktion und eine Zeichenfolge. Dies hilft uns beim Einrichten der Execute
Methode des Command-Objekts und seines name
. - Der
ExecuteCallback
Delegat definiert den Typ der gekapselten Methode. Die gekapselte Methode gibt void zurück und akzeptiert als Eingabeparameter ein Objekt vom Typ Bot
(Komponente Bot ). - Die
Execute
Eigenschaft bezieht sich auf die gekapselte Methode. Wir werden es verwenden, um die gekapselte Methode aufzurufen. - Die
ToString
Methode wird überschrieben, um die Zeichenfolge commandName
. Dies ist beispielsweise für die Verwendung in der Benutzeroberfläche praktisch.
Speichern Sie die Änderungen und das wars! Wir haben das Befehlsmuster erfolgreich implementiert.
Es bleibt zu benutzen.
Teambuilding
Öffnen Sie den
BotInputHandler im Ordner
RW / Scripts .
Hier erstellen wir fünf Instanzen von
BotCommand
. Diese Instanzen enthalten Methoden zum Verschieben des GameObject-Bot nach oben, unten, links und rechts sowie zum Schießen.
Um dies zu implementieren, fügen Sie Folgendes in diese Klasse ein:
In jedem dieser Fälle wird
eine anonyme Methode an den Konstruktor übergeben. Diese anonyme Methode wird in das entsprechende Befehlsobjekt eingekapselt. Wie Sie sehen, erfüllt die Signatur jeder der anonymen Methoden die vom
ExecuteCallback
Delegaten angegebenen Anforderungen.
Darüber hinaus ist der zweite Parameter für den Konstruktor eine Zeichenfolge, die den Namen des Befehls angibt. Dieser Name wird von der
ToString
Methode der Befehlsinstanz zurückgegeben. Später werden wir es für die Benutzeroberfläche anwenden.
In den ersten vier Fällen rufen anonyme Methoden die
Move
Methode für das
bot
Objekt auf. Ihre Eingabeparameter sind jedoch unterschiedlich.
Die
MoveUp
,
MoveDown
,
MoveLeft
und
MoveRight
übergeben die
Move
Parameter
CardinalDirection.Up
,
CardinalDirection.Down
,
CardinalDirection.Left
und
CardinalDirection.Right
. Wie im Abschnitt
Was ist das Befehlsentwurfsmuster erwähnt, geben sie verschiedene Richtungen an, in die sich der GameObject-Bot bewegen soll.
In der fünften Instanz ruft die anonyme Methode die
Shoot
Methode für das
bot
Objekt auf. Dank dessen feuert der Bot während der Ausführung des Befehls eine Shell ab.
Nachdem wir die Befehle erstellt haben, müssen wir irgendwie darauf zugreifen, wenn der Benutzer eine Eingabe vornimmt.
BotInputHandler
unmittelbar nach den Befehlsinstanzen
BotInputHandler
folgenden Code in den
BotInputHandler
:
public static BotCommand HandleInput() { if (Input.GetKeyDown(KeyCode.W)) { return MoveUp; } else if (Input.GetKeyDown(KeyCode.S)) { return MoveDown; } else if (Input.GetKeyDown(KeyCode.D)) { return MoveRight; } else if (Input.GetKeyDown(KeyCode.A)) { return MoveLeft; } else if (Input.GetKeyDown(KeyCode.F)) { return Shoot; } return null; }
Die
HandleInput
Methode gibt abhängig von der vom Benutzer gedrückten Taste eine Instanz des Befehls zurück. Speichern Sie Ihre Änderungen, bevor Sie fortfahren.
Befehle anwenden
Großartig, jetzt ist es Zeit, die von uns erstellten Teams einzusetzen. Gehen Sie erneut zu
RW / Scripts und öffnen Sie das
SceneManager- Skript im Editor. In dieser Klasse sehen Sie einen Link zu einer
uiManager
Variablen vom Typ
UIManager
.
Die
UIManager
Klasse bietet nützliche
UIManager
für die
Terminal-Benutzeroberfläche , die wir in dieser Szene verwenden. Wenn die Methode von
UIManager
verwendet wird, wird im Lernprogramm erläutert, was sie tut. Für unsere Zwecke ist es jedoch im Allgemeinen nicht erforderlich, die interne Struktur zu kennen.
Darüber hinaus bezieht sich die
bot
Variable auf die Bot-Komponente, die an den GameObject-
Bot angehängt ist.
SceneManager
Sie nun der
SceneManager
Klasse den folgenden Code
SceneManager
und ersetzen Sie ihn durch Kommentar
//1
:
Wow, wie viel Code! Aber mach dir keine Sorgen; Wir sind endlich bereit für den ersten echten Start des Projekts im Spielfenster.
Ich werde den Code später erklären. Denken Sie daran, die Änderungen zu speichern.
Führen Sie das Spiel aus, um die Befehlsvorlage zu testen
Jetzt ist also die Zeit zu bauen; Klicken Sie im Unity-Editor auf
Wiedergabe .
Sie sollten in der Lage sein, Bewegungsbefehle mit den
WASD-Tasten einzugeben . Drücken Sie die
F- Taste, um den Aufnahmebefehl einzugeben. Drücken Sie die
Eingabetaste, um Befehle auszuführen.
Hinweis : Bis zum Abschluss des Ausführungsprozesses ist die Eingabe neuer Befehle nicht möglich.
Beachten Sie, dass der Terminal-Benutzeroberfläche Zeilen hinzugefügt werden. Teams in der Benutzeroberfläche werden durch ihre Namen angezeigt. Möglich wird dies durch die Variable
commandName
.
Beachten Sie außerdem, wie die Benutzeroberfläche vor der Ausführung einen Bildlauf durchführt und wie die Zeilen während der Ausführung gelöscht werden.
Wir studieren die Teams genauer
Es ist Zeit, den Code zu lernen, den wir im Abschnitt "Anwenden von Befehlen" hinzugefügt haben:
- In der Liste
botCommands
werden Links zu Instanzen von BotCommand
. Denken Sie daran, dass wir zum Speichern von Speicher nur fünf Instanzen von Befehlen erstellen können, es jedoch möglicherweise mehrere Verweise auf einen Befehl gibt. Darüber hinaus executeCoroutine
Variable executeCoroutine
auf ExecuteCommandsRoutine
, die die Ausführung des Befehls steuert. Update
prüft, ob der Benutzer die Eingabetaste gedrückt hat. In diesem ExecuteCommands
wird CheckForBotCommands
ExecuteCommands
, andernfalls wird CheckForBotCommands
.CheckForBotCommands
verwendet die statische HandleInput
Methode des BotInputHandler
, um zu überprüfen, ob der Benutzer die Eingabe abgeschlossen hat. In diesem BotInputHandler
wird der Befehl zurückgegeben . Der zurückgegebene Befehl wird an AddToCommands
. Wenn jedoch die Befehle ausgeführt werden, d.h. Wenn executeRoutine
nicht null ist, wird es zurückgegeben, ohne dass etwas an AddToCommands
. Das heißt, der Benutzer muss bis zum Abschluss warten.AddToCommands
fügt der zurückgegebenen Instanz des Befehls in botCommands
einen neuen Link botCommands
.- Die
InsertNewText
Methode der InsertNewText
Klasse fügt der Terminal-Benutzeroberfläche eine neue Textzeile hinzu. Eine Textzeichenfolge ist eine Zeichenfolge, die als Eingabeparameter übergeben wird. In diesem Fall übergeben wir den Befehlsnamen. - Die
ExecuteCommands
Methode startet ExecuteCommandsRoutine
. ResetScrollToTop
vom UIManager
scrollt die Benutzeroberfläche des Terminals nach oben. Dies erfolgt kurz vor Beginn der Ausführung.ExecuteCommandsRoutine
enthält eine for
Schleife, die die Befehle in der botCommands
Liste botCommands
und sie botCommands
ausführt, wobei das bot
Objekt an die von der Execute
Eigenschaft zurückgegebene Methode übergeben wird. Nach jeder Ausführung wird in CommandPauseTime
Sekunden eine Pause hinzugefügt.- Die
RemoveFirstTextLine
Methode von UIManager
löscht die allererste Textzeile in der Terminal-Benutzeroberfläche, falls vorhanden. Das heißt, wenn ein Befehl ausgeführt wird, wird sein Name von der Benutzeroberfläche entfernt. - Nachdem alle Befehle
botCommands
gelöscht und der Bot wird mit ResetToLastCheckpoint
auf den letzten Haltepunkt ResetToLastCheckpoint
. Am Ende ist executeRoutine
null
und der Benutzer kann weiterhin Befehle eingeben.
Implementieren der Funktionen "Rückgängig" und "Wiederherstellen"
Führen Sie die Szene erneut aus und versuchen Sie, zum grünen Kontrollpunkt zu gelangen.
Sie werden feststellen, dass wir den eingegebenen Befehl nicht abbrechen können. Wenn Sie also einen Fehler machen, können Sie erst zurückkehren, wenn Sie alle eingegebenen Befehle ausgeführt haben. Sie können dies beheben, indem Sie die Funktionen "
Rückgängig" und "
Wiederherstellen" hinzufügen.
Gehen Sie zurück zu
SceneManager.cs und fügen Sie unmittelbar nach der
List- Deklaration für
botCommands
die folgende Variablendeklaration
botCommands
:
private Stack<BotCommand> undoStack = new Stack<BotCommand>();
Die Variable
undoStack
ist ein
Stapel (aus der Collections-Familie), in dem alle Verweise auf Befehle gespeichert werden, die rückgängig gemacht werden können.
Jetzt fügen wir zwei Methoden
UndoCommandEntry
und
RedoCommandEntry
, die Undo und Redo ausführen.
SceneManager
in der
SceneManager
Klasse nach
ExecuteCommandsRoutine
SceneManager
folgenden Code
ExecuteCommandsRoutine
:
private void UndoCommandEntry() {
Lassen Sie uns den Code analysieren:
- Wenn Befehle ausgeführt werden oder die
botCommands
Liste leer ist, führt die UndoCommandEntry
Methode nichts aus. Andernfalls wird ein Link zum letzten auf dem undoStack
Stapel eingegebenen Befehl undoStack
. Dadurch wird auch die Verknüpfung zum Befehl aus der botCommands
Liste entfernt. - Die
RemoveLastTextLine
Methode von UIManager
entfernt die letzte Textzeile von der Terminal-Benutzeroberfläche, sodass die Benutzeroberfläche mit dem Inhalt von botCommands
. - Wenn der
undoStack
Stapel leer ist, führt RedoCommandEntry
nichts aus. Andernfalls wird der letzte Befehl aus dem oberen Bereich von undoStack
und mithilfe von AddToCommands
wieder zur botCommands
Liste AddToCommands
.
Jetzt werden wir Tastatureingaben hinzufügen, um diese Funktionen zu verwenden.
SceneManager
der
SceneManager
Klasse den Hauptteil der
Update
Methode durch den folgenden Code:
if (Input.GetKeyDown(KeyCode.Return)) { ExecuteCommands(); } else if (Input.GetKeyDown(KeyCode.U))
- Wenn Sie die U- Taste drücken, wird die
UndoCommandEntry
Methode UndoCommandEntry
. - Wenn Sie die Taste R drücken, wird die
RedoCommandEntry
Methode RedoCommandEntry
.
Edge Case Handling
Großartig, wir sind fast fertig! Aber zuerst müssen wir Folgendes tun:
- Bei der Eingabe eines neuen Befehls sollte der
undoStack
Stapel gelöscht werden. - Vor dem Ausführen von Befehlen muss der
undoStack
Stack gelöscht werden.
Um dies zu implementieren, müssen wir
SceneManager
zunächst eine neue Methode
SceneManager
. Fügen Sie nach
CheckForBotCommands
die folgende Methode
CheckForBotCommands
:
private void AddNewCommand(BotCommand botCommand) { undoStack.Clear(); AddToCommands(botCommand); }
Diese Methode löscht
undoStack
und ruft dann die
AddToCommands
Methode auf.
Ersetzen Sie nun den Aufruf von
AddToCommands
in
CheckForBotCommands
durch den folgenden Code:
AddNewCommand(botCommand);
ExecuteCommands
dann die folgende Zeile nach der
if
in die
ExecuteCommands
Methode ein, um sie zu löschen, bevor Sie
undoStack
Befehle ausführen:
undoStack.Clear();
Und wir sind endlich fertig!
Speichern Sie Ihre Arbeit. Erstellen Sie das Projekt und klicken Sie im
Play- Editor. Geben Sie die Befehle wie zuvor ein. Drücken Sie
U , um die Befehle abzubrechen. Drücken Sie
R , um die abgebrochenen Befehle zu wiederholen.
Versuchen Sie, zum grünen Kontrollpunkt zu gelangen.
Wohin als nächstes?
Um mehr über die in der Spielprogrammierung verwendeten Entwurfsmuster zu erfahren, empfehle ich Ihnen, die Spielprogrammierungsmuster von Robert Nystrom zu studieren.
Um mehr über fortgeschrittene C # -Techniken zu erfahren
, besuchen Sie den Kurs
C # -Sammlungen, Lambdas und LINQ .
Aufgabe
Versuchen Sie als Aufgabe, zum grünen Kontrollpunkt am Ende des Labyrinths zu gelangen. Ich habe eine der Lösungen unter dem Spoiler versteckt.
Lösung- moveUp × 2
- moveRight × 3
- moveUp × 2
- moveLeft
- schießen
- moveLeft × 2
- moveUp × 2
- moveLeft × 2
- moveDown × 5
- moveLeft
- schießen
- moveLeft
- moveUp × 3
- schießen × 2
- moveUp × 5
- moveRight × 3