Hero background image
Verwenden Sie das Befehlsmuster für flexible und erweiterbare Spielsysteme
Diese Seite wurde maschinell übersetzt. Um die Originalversion zu sehen, damit Sie die Genauigkeit anhand der Quelle prüfen können,

Die Implementierung gängiger Entwurfsmuster in der Spielprogrammierung in Ihrem Unity-Projekt kann Ihnen helfen, effizient eine saubere, organisierte und lesbare Codebasis zu erstellen und zu pflegen. Entwurfsmuster reduzieren die Refactoring- und Testzeit, beschleunigen die Entwicklungsprozesse und tragen zu einer soliden Grundlage für das Wachstum Ihres Spiels, Ihres Teams und Ihres Unternehmens bei.

Denken Sie an Entwurfsmuster nicht als fertige Lösungen, die Sie in Ihren Code kopieren und einfügen können, sondern als zusätzliche Werkzeuge, die Ihnen helfen können, größere, skalierbare Anwendungen zu erstellen.

Diese Seite erklärt das Befehls-Designmuster.

Der Inhalt hier basiert auf dem kostenlosen E-Book, Steigere deinen Code mit Spielprogrammiermustern.

Schau dir weitere Artikel in der Unity-Spielprogrammierung Designmuster-Serie im Unity Best Practices Hub oder über diese Links an:

Das Verständnis des Befehlsmusters

Das Befehls-Programmier-Designmuster ist eines der ursprünglichen Gang of Four und ist nützlich, wann immer Sie eine bestimmte Reihe von Aktionen verfolgen möchten. Sie haben wahrscheinlich das Befehlsmuster in Aktion gesehen, wenn Sie ein Spiel gespielt haben, das die Rückgängig/Wiederherstellen-Funktionalität verwendet oder Ihre Eingabeverlauf in einer Liste speichert. Stellen Sie sich ein Strategiespiel vor, in dem der Benutzer mehrere Züge planen kann, bevor er sie tatsächlich ausführt. Das ist das Befehlsmuster.

Das Befehlsmuster ermöglicht es, Aktionen als Objekte darzustellen. Das Kapseln von Aktionen als Objekte ermöglicht es Ihnen, ein flexibles und erweiterbares System zur Steuerung des Verhaltens von GameObjects als Reaktion auf Benutzereingaben zu erstellen. Dies funktioniert, indem ein oder mehrere Methodenaufrufe als ein "Befehlsobjekt" gekapselt werden, anstatt eine Methode direkt aufzurufen. Dann können Sie diese Befehlsobjekte in einer Sammlung speichern, wie einer Warteschlange oder einem Stapel, der als kleiner Puffer fungiert.

Das Speichern von Befehlsobjekten auf diese Weise ermöglicht es Ihnen, den Zeitpunkt ihrer Ausführung zu steuern, indem Sie eine Reihe von Aktionen möglicherweise für eine spätere Wiedergabe verzögern. Ebenso sind Sie in der Lage, diese erneut auszuführen oder rückgängig zu machen und zusätzliche Flexibilität hinzuzufügen, um die Ausführung jedes Befehlsobjekts zu steuern.

Hier sind einige gängige Anwendungen des Musters in verschiedenen Spielgenres:

  • In einem Echtzeit-Strategiespiel könnte das Befehlsmuster verwendet werden, um Einheiten- und Gebäudeaktionen in eine Warteschlange zu stellen. Das Spiel würde dann jeden Befehl ausführen, sobald Ressourcen verfügbar sind.
  • In einem rundenbasierten Strategiespiel könnte der Spieler eine Einheit auswählen und deren Bewegungen oder Aktionen in einer Warteschlange oder einer anderen Sammlung speichern. Am Ende des Zuges würde das Spiel alle Befehle in der Warteschlange des Spielers ausführen.
  • In einem Puzzlespiel könnte das Befehlsmuster dem Spieler erlauben, Aktionen rückgängig zu machen und wiederherzustellen.
  • In einem Kampfspiel kann das Lesen von Tasteneingaben oder Gamepad-Bewegungen in einer bestimmten Befehlsliste zu Kombos und Spezialbewegungen führen.
Befehlsmuster in einem Musterprojekt
Befehlsmuster in einem Musterprojekt

Probieren Sie das Musterprojekt auf GitHub aus, das verschiedene Programmierdesignmuster im Kontext der Spieleentwicklung demonstriert, einschließlich des Befehlsmusters.

In diesem Beispiel kann der Spieler sich durch ein Labyrinth bewegen, indem er die Tasten auf der linken Seite klickt. Wenn sich Ihr Spieler bewegt, können Sie eine Spur der Bewegung sehen. Aber noch wichtiger ist, dass Sie Ihre vorherigen Aktionen rückgängig machen und wiederholen können.

Um die entsprechende Szene im Projekt zu finden, gehen Sie in den Ordner mit dem Namen „9 Command.“

Das Befehlsobjekt und der Befehlsaufrufer

Um das Befehlsmuster zu implementieren, benötigen Sie ein allgemeines Objekt, das Ihre Aktion enthält. Dieses Befehlsobjekt wird festhalten, welche Logik ausgeführt werden soll und wie man sie rückgängig macht.

Es gibt eine Reihe von Möglichkeiten, dies zu implementieren, aber hier ist eine einfache Version mit einer Schnittstelle namens ICommand:

öffentliche Schnittstelle ICommand
{
void Ausführen();
void Rückgängig();
}

In diesem Fall wird jede Spielaktion die ICommand Schnittstelle anwenden (Sie könnten dies auch mit einer abstrakten Klasse implementieren).

Jedes Befehlsobjekt ist verantwortlich für seine eigenen Ausführen und Rückgängig machen Methoden. Das Hinzufügen weiterer Befehle zu Ihrem Spiel wird keine Auswirkungen auf bestehende Befehle haben.

Die CommandInvoker Klasse ist dann verantwortlich für die Ausführung und das Rückgängigmachen von Befehlen. Zusätzlich zu den ExecuteCommand und UndoCommand Methoden verfügt es über einen Rückgängig-Stack, um die Reihenfolge der Befehlsobjekte zu halten.

Beispiel: Rückgängig machbare Bewegung

Im Musterprojekt können Sie Ihren Spieler durch ein kleines Labyrinth bewegen. Eine einfache Möglichkeit, die Position des Spielers zu verschieben, besteht darin, einen PlayerMover zu erstellen.

Um dies zu tun, müssen Sie einen Vector3 in die Move-Methode übergeben, um den Spieler in die vier Himmelsrichtungen zu führen. Sie können auch einen Raycast verwenden, um die Wände in der entsprechenden LayerMask zu erkennen. Natürlich ist die Implementierung dessen, was Sie auf das Befehlsmuster anwenden möchten, von dem Muster selbst getrennt.

Der MoveCommand
DER BEFEHLSAUFRUFER, ICOMMAND UND MOVECOMMAND
Der MoveCommand

Um dem Befehlsmuster zu folgen, erfassen Sie die PlayerMover’s Move Methode als Objekt. Anstatt Move direkt aufzurufen, erstellen Sie eine neue Klasse, MoveCommand, die das ICommand Interface implementiert.

public class MoveCommand : ICommand
{
SpielerBeweger spielerBeweger;
Vektor3 Bewegung;
öffentliche MoveCommand(PlayerMover Spieler, Vector3 Bewegungsvektor)
{
this.playerMover = player;
this.movement = moveVector;
}
öffentlich void Ausführen()
{
SpielerBeweger.Bewege(Bewegung);
}
öffentlich void Rückgängig machen()
{
SpielerBeweger.Bewege(-bewegung);
}
}

Was auch immer Sie erreichen möchten, kommt hier hinein, also rufen Sie Move mit dem Bewegungsvektor auf.

ICommand benötigt auch eine Undo Methode, um die Szene in ihren vorherigen Zustand zurückzusetzen. In diesem Fall subtrahiert die Rückgängig Logik den Bewegungsvektor und schiebt den Spieler im Wesentlichen in die entgegengesetzte Richtung.

Der MoveCommand speichert alle Parameter, die er zur Ausführung benötigt. Richten Sie diese mit einem Konstruktor ein. In diesem Fall speichern Sie die entsprechende PlayerMover Komponente und den Bewegungsvektor.

Sobald Sie das Befehlsobjekt erstellt und die benötigten Parameter gespeichert haben, verwenden Sie die CommandInvoker’s statische ExecuteCommand und UndoCommand Methoden, um Ihren MoveCommand zu übergeben. Dies führt den MoveCommand’s Execute oder Undo aus und verfolgt das Befehlsobjekt im Rückgängig-Stack.

Der InputManager ruft die Move-Methode des PlayerMover’s nicht direkt auf. Stattdessen fügen Sie eine zusätzliche Methode hinzu, RunMoveCommand, um einen neuen MoveCommand zu erstellen und ihn an den CommandInvoker zu senden.

Dann richten Sie die verschiedenen onClick-Ereignisse der UI-Buttons ein, um RunPlayerCommand mit den vier Bewegungsvektoren aufzurufen.

Schau dir das Beispielprojekt für Implementierungsdetails für den InputManager an. Sie können auch Ihre eigenen Eingaben über die Tastatur oder das Gamepad einrichten. Ihr Spieler kann jetzt das Labyrinth navigieren. Klicken Sie auf die Rückgängig-Schaltfläche, um zum Anfangsfeld zurückzukehren.

Vor- und Nachteile

Die Implementierung von Wiederspielbarkeit oder Rückgängig machen ist so einfach wie das Erzeugen einer Sammlung von Befehlsobjekten. Sie können auch den Befehlsbuffer verwenden, um Aktionen in einer bestimmten Reihenfolge mit spezifischen Steuerungen abzuspielen.

Zum Beispiel, denken Sie an ein Kampfspiel, bei dem eine Reihe spezifischer Tastenkombinationen einen Kombinationsangriff oder -zug auslöst. Das Speichern von Spieleraktionen mit dem Befehlsmuster macht das Einrichten dieser Kombinationen viel einfacher.

Auf der anderen Seite führt das Befehlsmuster mehr Struktur ein, genau wie die anderen Entwurfsmuster. Sie müssen entscheiden, wo diese zusätzlichen Klassen und Schnittstellen genügend Nutzen bieten, um Befehlsobjekte in Ihrer Anwendung bereitzustellen.

Sobald Sie die Grundlagen gelernt haben, können Sie das Timing von Befehlen beeinflussen und sie je nach Kontext nacheinander oder umgekehrt abspielen.

Berücksichtigen Sie Folgendes, wenn Sie das Befehlsmuster einbeziehen:

  • Erstelle mehr Befehle: Das Musterprojekt umfasst nur eine Art von Befehlsobjekt, den MoveCommand. Sie können beliebig viele Befehlsobjekte erstellen, die ICommand implementieren, und sie mit dem CommandInvoker verfolgen.
  • Die Hinzufügung einer Rückgängig-Funktionalität besteht darin, einen weiteren Stapel hinzuzufügen: Wenn Sie ein Befehlsobjekt rückgängig machen, schieben Sie es auf einen separaten Stapel, der Wiederherstellungsoperationen verfolgt. Auf diese Weise können Sie schnell durch die Rückgängig-Historie blättern oder diese Aktionen wiederholen. Leeren Sie den Wiederholungsstapel, wenn der Benutzer eine völlig neue Bewegung aufruft (eine Implementierung finden Sie im Musterprojekt).
  • Verwenden Sie eine andere Sammlung für Ihren Puffer von Befehlsobjekten: Eine Warteschlange könnte praktischer sein, wenn Sie ein First-In-First-Out (FIFO)-Verhalten wünschen. Wenn Sie eine Liste verwenden, verfolgen Sie den derzeit aktiven Index; Befehle vor dem aktiven Index sind rückgängig zu machen. Befehle nach dem Index sind wiederholbar.
  • Begrenzen Sie die Größe der Stapel: Rückgängig- und Wiederherstellungsoperationen können schnell außer Kontrolle geraten. Begrenzen Sie die Stapel auf die geringste Anzahl von Befehlen.
  • Geben Sie alle erforderlichen Parameter in den Konstruktor ein: Dies hilft, die Logik zu kapseln, wie im Beispiel des MoveCommand zu sehen ist.
  • Der CommandInvoker, wie andere externe Objekte, sieht nicht die inneren Abläufe des Befehlsobjekts, sondern ruft nur Ausführen oder Rückgängig auf. Geben Sie dem Befehlsobjekt alle benötigten Daten, um beim Aufrufen des Konstruktors zu arbeiten.
E-Book blau
Weitere Ressourcen

Finden Sie weitere Tipps zur Verwendung von Entwurfsmustern in Ihren Unity-Anwendungen sowie zu den SOLID-Prinzipien im kostenlosen E-Book Level up your code with game programming patterns.

Sie finden alle fortgeschrittenen technischen E-Books und Artikel zu Unity im best practices Hub. Die E-Books sind auch auf der fortgeschrittenen Best Practices Seite in der Dokumentation verfügbar.

Haben Ihnen diese Inhalte gefallen?