Hero background image
Verwenden Sie das Befehlsmuster für flexible und erweiterbare Spielsysteme

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

Stellen Sie sich Entwurfsmuster nicht als fertige Lösungen vor, die Sie kopieren und in Ihren Code einfügen können, sondern als zusätzliche Tools, mit denen Sie größere, skalierbare Anwendungen erstellen können.

Auf dieser Seite wird das Befehlsentwurfsmuster erläutert.

Der Inhalt hier basiert auf dem kostenlosen E-Book " Level up your code with game programming patterns".

Weitere Artikel finden Sie in der Reihe zu Entwurfsmustern für die Unity-Spielprogrammierung im Unity-Hub für bewährte Methoden oder über diese Links:

Grundlegendes zum Befehlsmuster

Das Entwurfsmuster für die Befehlsprogrammierung gehört zu den ursprünglichen Viererbanden und ist immer dann nützlich, wenn Sie eine bestimmte Reihe von Aktionen verfolgen möchten. Sie haben das Befehlsmuster wahrscheinlich bei der Arbeit gesehen, wenn Sie ein Spiel gespielt haben, das die Rückgängig-/Wiederholungsfunktion verwendet oder Ihren Eingabeverlauf in einer Liste speichert. Stellen Sie sich ein Strategiespiel vor, bei dem der Benutzer mehrere Züge planen kann, bevor er sie tatsächlich ausführt. Das ist das Befehlsmuster.

Das Befehlsmuster ermöglicht die Darstellung von Aktionen als Objekte. Durch das Kapseln von Aktionen als Objekte können Sie ein flexibles und erweiterbares System zum Steuern des Verhaltens von GameObjects als Reaktion auf Benutzereingaben erstellen. Dies funktioniert, indem ein oder mehrere Methodenaufrufe als "Befehlsobjekt" gekapselt werden, anstatt eine Methode direkt aufzurufen. Anschließend können Sie diese Befehlsobjekte in einer Sammlung speichern, z. B. in einer Warteschlange oder einem Stapel, der als kleiner Puffer fungiert.

Durch das Speichern von Befehlsobjekten auf diese Weise können Sie den Zeitpunkt ihrer Ausführung steuern, indem Sie möglicherweise eine Reihe von Aktionen für die spätere Wiedergabe verzögern. Ebenso können Sie sie wiederholen oder rückgängig machen und zusätzliche Flexibilität hinzufü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 die Warteschlange zu stellen. Das Spiel würde dann jeden Befehl ausführen, sobald Ressourcen verfügbar werden.
  • In einem rundenbasierten Strategiespiel konnte der Spieler eine Einheit auswählen und dann ihre Züge oder Aktionen in einer Warteschlange oder einer anderen Sammlung speichern. Am Ende des Zuges führt das Spiel alle Befehle in der Warteschlange des Spielers aus.
  • In einem Puzzlespiel könnte das Befehlsmuster es dem Spieler ermöglichen, Aktionen rückgängig zu machen und zu wiederholen.
  • In einem Kampfspiel kann das Lesen von Tastendrücken oder Gamepad-Bewegungen in einer bestimmten Befehlsliste zu Combos und Spezialbewegungen führen.
Befehlsmuster in einem Beispielprojekt
Befehlsmuster in einem Beispielprojekt

Probieren Sie das Beispielprojekt auf GitHub aus, das verschiedene Programmierentwurfsmuster im Kontext der Spieleentwicklung veranschaulicht, einschließlich des Befehlsmusters.

In diesem Beispiel kann sich der Spieler durch Klicken auf die Schaltflächen auf der linken Seite in einem Labyrinth bewegen. Wenn sich Ihr Spieler bewegt, können Sie eine Bewegungsspur 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 "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 enthält die auszuführende Logik und wie sie rückgängig gemacht werden kann.

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 bei jeder Gameplay-Aktion die ICommand-Schnittstelle angewendet (Sie können dies auch mit einer abstrakten Klasse implementieren).

Jedes Befehlsobjekt ist für seine eigenen Execute- und Undo-Methoden verantwortlich. Das Hinzufügen weiterer Befehle zu Ihrem Spiel wirkt sich also nicht auf bestehende Befehle aus.

Die CommandInvoker-Klasse ist dann für das Ausführen und Rückgängigmachen von Befehlen verantwortlich. Zusätzlich zu den Methoden ExecuteCommand und UndoCommand verfügt es über einen Rückgängig-Stapel, der die Sequenz von Befehlsobjekten enthält.

Beispiel: Nicht rückgängig machende Bewegung

Im Beispielprojekt können Sie Ihren Player durch ein kleines Labyrinth bewegen. Eine einfache Möglichkeit, die Position des Spielers zu ändern, besteht darin, einen PlayerMover zu erstellen.

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

Der MoveCommand
COMMANDINVOKER, ICOMMAND UND MOVECOMMAND
Der MoveCommand

Um dem Befehlsmuster zu folgen, erfassen Sie die Move-Methode von PlayerMover als Objekt. Anstatt Move direkt aufzurufen, erstellen Sie eine neue Klasse, MoveCommand, die die ICommand-Schnittstelle implementiert.

öffentliche Klasse MoveCommand : ICommand
{
PlayerMover, PlayerMover;
Vector3-Bewegung;
public MoveCommand(PlayerMover-Spieler, Vector3 moveVector)
{
this.playerMover = player;
this.movement = moveVector;
}
public void Execute()
{
playerMover.Move(Bewegung);
}
public void Rückgängig()
{
playerMover.Move(-Bewegung);
}
}

Welche Logik Sie auch immer hier erreichen möchten, also rufen Sie Move mit dem Bewegungsvektor auf.

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

Der MoveCommand speichert alle Parameter, die er ausführen muss. Richten Sie diese mit einem Konstruktor ein. In diesem Fall speichern Sie die entsprechende PlayerMover-Komponente und den Bewegungsvektor.

Nachdem Sie das Befehlsobjekt erstellt und die erforderlichen Parameter gespeichert haben, verwenden Sie die statischen ExecuteCommand- und UndoCommand-Methoden des CommandInvoker, um Ihren MoveCommand zu übergeben. Dadurch wird das Ausführen oder Rückgängigmachen von MoveCommand ausgeführt und das Befehlsobjekt im Rückgängig-Stapel verfolgt.

Der InputManager ruft die Move-Methode von PlayerMover nicht direkt auf. Fügen Sie stattdessen eine zusätzliche Methode, RunMoveCommand, hinzu, um einen neuen MoveCommand zu erstellen und an den CommandInvoker zu senden.

Richten Sie dann die verschiedenen onClick-Ereignisse der UI-Schaltflächen ein, um RunPlayerCommand mit den vier Bewegungsvektoren aufzurufen.

Im Beispielprojekt finden Sie Details zur Implementierung des InputManagers. Sie können auch Ihre eigene Eingabe über die Tastatur oder das Gamepad einrichten. Ihr Spieler kann jetzt durch das Labyrinth navigieren. Klicken Sie auf die Schaltfläche Rückgängig, um zum Anfangsquadrat zurückzukehren.

Vor- und Nachteile

Das Implementieren der Wiederspielbarkeit oder Rückgängigmachbarkeit ist so einfach wie das Generieren einer Sammlung von Befehlsobjekten. Sie können den Befehlspuffer auch verwenden, um Aktionen nacheinander mit bestimmten Steuerelementen wiederzugeben.

Denken Sie zum Beispiel an ein Kampfspiel, bei dem eine Reihe bestimmter Tastenklicks eine Kombo-Bewegung oder einen Angriff auslöst. Das Speichern von Spieleraktionen mit dem Befehlsmuster macht das Einrichten dieser Combos 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 für die Bereitstellung von Befehlsobjekten in Ihrer Anwendung bieten

Sobald Sie die Grundlagen gelernt haben, können Sie das Timing von Befehlen beeinflussen und sie je nach Kontext nacheinander oder rückwärts wiedergeben.

Beachten Sie Folgendes, wenn Sie das Befehlsmuster integrieren:

  • Erstellen Sie weitere Befehle: Das Beispielprojekt enthält nur einen Typ von Befehlsobjekt, den MoveCommand. Sie können eine beliebige Anzahl von Befehlsobjekten erstellen, die ICommand implementieren, und sie mit dem CommandInvoker verfolgen.
  • Das Hinzufügen von Redo-Funktionen ist eine Frage des Hinzufügens eines weiteren Stacks: Wenn Sie ein Befehlsobjekt rückgängig machen, verschieben Sie es auf einen separaten Stapel, der Wiederholungsvorgänge verfolgt. Auf diese Weise können Sie schnell durch den Rückgängig-Verlauf blättern oder diese Aktionen wiederholen. Löschen Sie den Wiederholungsstapel, wenn der Benutzer eine völlig neue Bewegung aufruft (eine Implementierung finden Sie im Beispielprojekt).
  • Verwenden Sie eine andere Auflistung für den Puffer von Befehlsobjekten: Eine Warteschlange ist möglicherweise praktischer, wenn Sie FIFO-Verhalten (First In, First Out) wünschen. Wenn Sie eine Liste verwenden, verfolgen Sie den derzeit aktiven Index. Befehle vor dem aktiven Index können rückgängig gemacht werden. Befehle nach dem Index können wiederholt werden.
  • Begrenzen Sie die Größe der Stapel: Rückgängig- und Wiederholungsvorgänge können schnell außer Kontrolle geraten. Beschränken Sie die Stapel auf die geringste Anzahl von Befehlen.
  • Übergeben Sie alle erforderlichen Parameter an den Konstruktor: Dies hilft bei der Kapselung der Logik, wie im MoveCommand-Beispiel gezeigt.
  • Der CommandInvoker sieht wie andere externe Objekte nicht das Innenleben des Befehlsobjekts, sondern ruft nur Ausführen oder Rückgängig auf. Geben Sie dem Befehlsobjekt alle Daten, die beim Aufrufen des Konstruktors erforderlich sind.
E-Book blau
Weitere Ressourcen

Weitere Tipps zur Verwendung von Entwurfsmustern in Ihren Unity-Anwendungen sowie zu den SOLID-Prinzipien finden Sie im kostenlosen E-Book Verbessern Sie Ihren Code mit Spielprogrammiermustern.

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

Haben Ihnen diese Inhalte gefallen?