Hero background image
Verwendung des Fabrikmusters für die Objekterstellung zur Laufzeit

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

Betrachten Sie Entwurfsmuster nicht als fertige Lösungen, die Sie kopieren und in Ihren Code einfügen können, sondern als zusätzliche Werkzeuge, die Ihnen bei der Entwicklung größerer, skalierbarer Anwendungen helfen können.

Auf dieser Seite wird das Factory Design Pattern erklärt.

Der Inhalt hier basiert auf dem kostenlosen E-Book, Verbessern Sie Ihren Code mit Programmiermustern für Spiele.

Weitere Artikel aus der Reihe Unity Design Patterns für die Spieleprogrammierung finden Sie im Unity Best Practices Hub oder über diese Links:

Fabrik-Diagramm
EIN FACTORY-DESIGN-MUSTER BIETET EINE SCHNITTSTELLE FÜR DIE ERSTELLUNG VON OBJEKTEN IN EINER OBERKLASSE, ERLAUBT ES ABER UNTERKLASSEN, DEN TYP DER ERSTELLTEN OBJEKTE ZU ÄNDERN
Werksmuster verstehen

Manchmal ist es hilfreich, ein spezielles Objekt zu haben, das andere Objekte erzeugt. Viele Spiele bringen im Laufe des Spiels eine Vielzahl von Dingen hervor, und man weiß oft nicht, was man zur Laufzeit braucht, bis man es tatsächlich braucht.

Das Factory-Pattern sieht für diesen Zweck ein spezielles Objekt vor, das - Sie ahnen es - Factory heißt. Auf einer Ebene werden viele der Details, die bei der Herstellung der "Produkte" eine Rolle spielen, zusammengefasst. Der unmittelbare Nutzen besteht darin, Ihren Code zu entrümpeln.

Wenn jedoch jedes Produkt einer gemeinsamen Schnittstelle oder Basisklasse folgt, können Sie noch einen Schritt weiter gehen und dafür sorgen, dass es mehr eigene Konstruktionslogik enthält, die von der Fabrik selbst ferngehalten wird. Die Erstellung neuer Objekte wird dadurch leichter erweiterbar.

Sie können die Fabrik auch untergliedern, um mehrere Fabriken für bestimmte Produkte zu erstellen. Auf diese Weise lassen sich Feinde, Hindernisse oder andere Dinge zur Laufzeit erzeugen.

Fabrik Beispielprojekt
EIN PRODUKT SPIELT EINEN TON, EIN ANDERES SPIELT PARTIKEL. VERWENDEN BEIDE DIE GLEICHE SCHNITTSTELLE.
Testen Sie das Fabrikmuster in einem Beispielprojekt

Auf GitHub steht ein Beispielprojekt zur Verfügung, das verschiedene Programmiermuster im Kontext der Spieleentwicklung demonstriert, darunter auch das Factory-Muster.

Das Fabrikmusterbeispiel besteht aus Code für einen Spieler, der sich in einem Labyrinth bewegt. Im Labyrinth können Sie durch Anklicken zwei verschiedene Spielobjekte namens Produkte erzeugen. Sie verwenden beide die gleiche Schnittstelle und haben eine ähnliche Form, aber das eine erzeugt Partikel und das andere spielt einen Sound.

Die Werksmusterszene befindet sich in dem Ordner "6 Factory".

Erstellen einer einfachen Fabrik

Stellen Sie sich vor, Sie möchten ein Fabrikmuster erstellen, um Elemente für eine Spielebene zu instanziieren. Du kannst Prefabs verwenden, um GameObjects zu erstellen, aber du möchtest vielleicht auch ein benutzerdefiniertes Verhalten beim Erstellen jeder Instanz ausführen.

Anstatt if-Anweisungen oder einen Schalter zu verwenden, um diese Logik beizubehalten, erstellen Sie eine Schnittstelle namens IProduct und eine abstrakte Klasse namens Factory, wie im Codebeispiel beschrieben.

Die Produkte müssen einer bestimmten Vorlage für ihre Methoden folgen, aber ansonsten haben sie keine gemeinsame Funktionalität. Sie definieren also die Schnittstelle IProduct.

Fabriken benötigen möglicherweise einige gemeinsame Funktionen, weshalb in diesem Beispiel abstrakte Klassen verwendet werden. Achten Sie bei der Verwendung von Unterklassen auf die Liskov-Substitution gemäß den SOLID-Grundsätzen. Sie besagt, dass Objekte einer Oberklasse durch Objekte einer Unterklasse ersetzt werden können sollten, ohne dass die Korrektheit des Programms beeinträchtigt wird. Mit anderen Worten, jedes Programm, das eine Oberklassenreferenz verwendet, sollte in der Lage sein, jede seiner Unterklassen zu verwenden, ohne sie zu kennen.

VERWENDUNG EINER SCHNITTSTELLE ZUR DEFINITION GEMEINSAMER EIGENSCHAFTEN UND LOGIK ZWISCHEN IHREN PRODUKTEN
VERWENDUNG EINER SCHNITTSTELLE ZUR DEFINITION GEMEINSAMER EIGENSCHAFTEN UND LOGIK ZWISCHEN IHREN PRODUKTEN
Klassenaufbau im Werksmuster

Die IProduct-Schnittstelle definiert die Gemeinsamkeiten zwischen Ihren Produkten. In diesem Fall haben Sie einfach eine ProductName-Eigenschaft und jede Logik, die das Produkt bei Initialize ausführt.

Sie können dann beliebig viele Produkte definieren(ProduktA, ProduktB usw.), solange sie der Schnittstelle IProduct entsprechen.

Die Basisklasse Factory verfügt über eine GetProduct-Methode, die ein IProduct zurückgibt. Da es abstrakt ist, können Sie keine direkten Instanzen von Factory erstellen. Sie leiten eine Reihe konkreter Unterklassen ab(ConcreteFactoryA und ConcreteFactoryB), die die verschiedenen Produkte tatsächlich erhalten.

GetProduct in diesem Beispiel nimmt eine Vector3-Position, so dass Sie ein Prefab GameObject leichter an einer bestimmten Stelle instanziieren können. Ein Feld in jedem Betonwerk speichert auch die entsprechende Vorlage Prefab.

Das Ergebnis ist eine Struktur, die in etwa wie das obige Bild aussieht.

Code-Beispiel

Im Codeschnipsel sehen Sie ein Beispiel für ProductA und ConcreteFactoryA.

Hier haben Sie dafür gesorgt, dass die Produktklassen MonoBehaviours, die IProduct implementieren, die Vorteile von Prefabs in der Fabrik nutzen.

Beachten Sie, dass jedes Produkt seine eigene Version von Initialize haben kann. Das Beispiel ProductA Prefab enthält ein ParticleSystem, das spielt, wenn die ConcreteFactoryA eine Kopie instanziiert. Die Fabrik selbst enthält keine spezifische Logik zum Auslösen der Partikel; sie ruft lediglich die Methode Initialize auf, die allen Produkten gemeinsam ist.

Schauen Sie sich das Beispielprojekt an, um zu sehen, wie die ClickToCreate-Komponente zwischen den Fabriken umschaltet, um ProductA und ProductB zu erstellen, die unterschiedliche Verhaltensweisen haben. ProduktB spielt einen Sound ab, wenn es entsteht, während ProduktA einen Partikeleffekt auslöst, um das Kernkonzept der Produktvariationen zu veranschaulichen.

Pro und Kontra

Beim Einrichten vieler Produkte profitieren Sie am meisten von den Werksmustern. Die Definition neuer Produkttypen in Ihrer Anwendung ändert nichts an den bestehenden Typen und erfordert keine Änderung des bisherigen Codes.

Durch die Trennung der internen Logik jedes Produkts in eine eigene Klasse bleibt der Code der Fabrik relativ kurz. Jede Fabrik weiß nur, dass sie die Initialisierung für jedes Produkt aufrufen muss, ohne in die zugrunde liegenden Details eingeweiht zu sein.

Der Nachteil ist, dass Sie eine Reihe von Klassen und Unterklassen erstellen müssen, um das Muster zu implementieren. Wie bei den anderen Mustern entsteht auch hier ein gewisser Overhead, der aber unnötig sein kann, wenn Sie keine große Produktvielfalt haben. Andererseits kann sich der anfängliche Zeitaufwand für die Einrichtung der Klassen auf lange Sicht als Vorteil erweisen, da Ihr Code entkoppelt und leichter zu pflegen ist.

Anpassen des Fabrikmusters

Die Implementierung der Fabrik kann sich stark von der hier gezeigten unterscheiden. Beachten Sie die folgenden Anpassungen, wenn Sie Ihr eigenes Fabrikmuster erstellen:

Verwenden Sie ein Wörterbuch, um nach Produkten zu suchen: Vielleicht möchten Sie Ihre Produkte als Schlüssel-Wert-Paare in einem Wörterbuch speichern. Verwenden Sie einen eindeutigen String-Identifikator (z. B. den Namen oder eine ID) als Schlüssel und den Typ als Wert. Dies kann die Suche nach Produkten und/oder den dazugehörigen Fabriken vereinfachen.

Machen Sie die Fabrik (oder einen Fabrikmanager) statisch: Dies erleichtert die Nutzung, erfordert aber zusätzliche Einstellungen. Statische Klassen werden im Inspektor nicht angezeigt, daher müssen Sie Ihre Produktsammlung ebenfalls statisch machen.

Wenden Sie es auf Nicht-Spielobjekte und Nicht-MonoBehaviours an: Beschränken Sie sich nicht auf Prefabs oder andere Unity-spezifische Komponenten. Das Factory-Pattern kann mit jedem C#-Objekt arbeiten.

Kombinieren Sie mit dem Objektpool-Muster: Fabriken müssen nicht unbedingt neue Objekte instanziieren oder erstellen. Sie können auch bereits vorhandene Einträge in der Hierarchie wiederfinden. Wenn Sie viele Objekte auf einmal instanziieren (z. B. Projektile einer Waffe), verwenden Sie das Objektpool-Muster für eine optimierte Speicherverwaltung.

Fabriken können jedes Spielelement nach Bedarf spawnen. Die Herstellung von Produkten ist jedoch oft nicht ihr einziger Zweck. Möglicherweise verwenden Sie das Factory-Pattern als Teil einer anderen größeren Aufgabe (z. B. zum Einrichten von UI-Elementen in einem Dialogfeld von Teilen einer Spielebene).

Blog-Beitrag
Weitere Ressourcen

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

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

Haben Ihnen diese Inhalte gefallen?