Verfahrenserzeugung für einfache Rätsel

Was Sie erstellen werden

Rätsel sind für viele Genres ein wesentlicher Bestandteil des Spiels. Ob einfach oder komplex, das manuelle Entwickeln von Puzzlespielen kann schnell mühsam werden. Dieses Tutorial zielt darauf ab, diese Belastung zu lindern und den Weg für andere, spaßigere Aspekte des Designs zu ebnen.

Gemeinsam werden wir einen Generator zum Erstellen einfacher "verschachtelter" Rätsel erstellen. Die Art des Puzzles, auf die wir uns konzentrieren werden, ist das traditionelle „Sperren und Schlüssel“, das meistens wiederholt wird als: x-Element erhalten, um den Bereich freizugeben. Diese Arten von Rätseln können für Teams langweilig werden, die an bestimmten Arten von Spielen arbeiten, insbesondere bei Dungeon-Crawlern, Sandboxen und Rollenspielen, bei denen Rätsel häufiger für Inhalte und Erkundungen verwendet werden.

Mit der prozeduralen Generierung möchten wir eine Funktion erstellen, die einige Parameter übernimmt und ein komplexeres Asset für unser Spiel liefert. Durch die Anwendung dieser Methode wird die Entwicklerzeit exponentiell genutzt, ohne die Qualität des Spiels zu beeinträchtigen. Die Bestürzung der Entwickler kann auch als glückliche Nebenwirkung abnehmen.

Was muss ich wissen??

Um folgen zu können, müssen Sie mit einer Programmiersprache Ihrer Wahl vertraut sein. Da das meiste, was wir besprechen, nur Daten ist und in Pseudocode verallgemeinert wird, reicht jede objektorientierte Programmiersprache aus. 

In der Tat funktionieren auch einige Drag & Drop-Editoren. Wenn Sie eine spielbare Demo des hier genannten Generators erstellen möchten, müssen Sie auch mit Ihrer bevorzugten Spielebibliothek vertraut sein.

Generator erstellen

Beginnen wir mit einem Blick auf einen Pseudocode. Die grundlegendsten Bausteine ​​unseres Systems sind Schlüssel und Räume. In diesem System darf ein Spieler die Zimmertür nicht betreten, es sei denn, er besitzt seinen Schlüssel. So würden diese beiden Objekte als Klassen aussehen:

Klassenschlüssel Var playerHas; Var Lage; Funktion init (setLocation) Location = setLocation; PlayerHas = falsch;  Funktion pickUp () this.playerHas = true;  class Room Var isLocked; Var assocKey; Funktion init () isLocked = true; assocKey = neuer Schlüssel (dieser);  Funktion unlock () this.isLocked = false;  Funktion canUnlock If (this.key.PlayerHas) Rückgabe true;  Else Rückgabe falsch; 

Unsere Schlüsselklasse enthält momentan nur zwei Informationen: den Ort des Schlüssels und ob der Spieler diesen Schlüssel in seinem Inventar hat. Seine zwei Funktionen sind Initialisierung und Abholung. Durch die Initialisierung werden die Grundlagen eines neuen Schlüssels festgelegt, während die Aufnahme für die Interaktion eines Spielers mit dem Schlüssel bestimmt wird.

Unsere Zimmerklasse enthält wiederum zwei Variablen: ist gesperrt, die den aktuellen Zustand der Schleuse des Raums enthält, und assocKey, welches das Key-Objekt enthält, das diesen bestimmten Raum freigibt. Es enthält eine Funktion für die Initialisierung sowie eine zum Aufrufen der Entriegelung der Tür und eine weitere zum Überprüfen, ob die Tür aktuell geöffnet werden kann.

Eine einzige Tür und ein Schlüssel machen Spaß, aber wir können es immer mit Schachteln aufpeppen. Die Implementierung dieser Funktion ermöglicht es uns, Türen innerhalb von Türen zu erstellen und gleichzeitig als unser erster Generator zu dienen. Um die Verschachtelung beizubehalten, müssen wir unserer Tür auch einige zusätzliche Variablen hinzufügen:

Klassenzimmer Var isLocked; Var assocKey; Var parentRoom; Var Tiefe; Funktion init (setParentRoom, setDepth) If (setParentRoom) parentRoom = setParentRoom;  Else parentRoom = keine;  Tiefe = setDepth; isLocked = true; assocKey = neuer Schlüssel (dieser);  Funktion unlock () this.isLocked = false;  Funktion canUnlock If (this.key.playerHas) Rückgabe true;  Else Rückgabe falsch;  Funktion roomGenerator (depthMax) Array roomsToCheck; Array fertige Räume; Raum initialRoom.init (keine, 0); roomsToCheck.add (initialRoom); While (roomsToCheck! = Leer) If (currentRoom.depth == depthMax) beendetRooms.add (currentRoom); roomsToCheck.remove (currentRoom);  Else Room newRoom.init (currentRoom, currentRoom.depth + 1); roomsToCheck.add (newRoom); beendetRooms.add (currentRoom); roomsToCheck.remove (currentRoom); 

Dieser Generatorcode führt Folgendes aus:

  1. Einfügen des Parameters für unser generiertes Puzzle (insbesondere wie viele Ebenen tief ein verschachtelter Raum sein sollte).

  2. Erstellen Sie zwei Arrays: eines für Räume, die auf mögliche Verschachtelung geprüft werden, und eines, um bereits verschachtelte Räume zu registrieren.

  3. Erstellen Sie einen ersten Raum, der die gesamte Szene enthält, und fügen Sie ihn dann dem Array hinzu, damit wir ihn später überprüfen können.

  4. Nehmen Sie den Raum an der Vorderseite des Arrays, um die Schleife zu durchlaufen.

  5. Überprüfung der Tiefe des aktuellen Raums gegen die maximal zur Verfügung gestellte Tiefe (dies entscheidet, ob wir einen weiteren Kinderraum erstellen oder ob der Vorgang abgeschlossen ist).

  6. Einen neuen Raum einrichten und mit den erforderlichen Informationen aus dem Elternraum auffüllen.

  7. Hinzufügen des neuen Raums zum zimmer zu überprüfen Array und verschieben Sie den vorherigen Raum zum fertigen Array.

  8. Wiederholen Sie diesen Vorgang, bis jeder Raum im Array vollständig ist.

Jetzt können wir so viele Räume haben, wie unsere Maschine bewältigen kann, aber wir brauchen immer noch Schlüssel. Die Schlüsselplatzierung hat eine große Herausforderung: Lösbarkeit. Wo immer wir den Schlüssel platzieren, müssen wir sicherstellen, dass ein Spieler darauf zugreifen kann! Egal wie gut der versteckte Schlüssel-Cache aussieht, wenn der Spieler ihn nicht erreichen kann, ist er effektiv gefangen. Damit der Spieler das Rätsel fortsetzen kann, müssen die Schlüssel verfügbar sein.

Die einfachste Methode, um die Lösbarkeit in unserem Puzzle sicherzustellen, ist die Verwendung eines hierarchischen Systems von Eltern-Kind-Objektbeziehungen. Da sich jeder Raum in einem anderen befindet, müssen wir davon ausgehen, dass ein Spieler Zugang zum Elternteil jedes Zimmers haben muss, um ihn zu erreichen. Solange sich der Schlüssel über dem Raum in der hierarchischen Kette befindet, garantieren wir, dass unser Spieler Zugriff hat.

Um die Schlüsselgenerierung zu unserer prozeduralen Generierung hinzuzufügen, fügen wir den folgenden Code in unsere Hauptfunktion ein:

 Funktion roomGenerator (depthMax) Array roomsToCheck; Array fertige Räume; Raum initialRoom.init (keine, 0); roomsToCheck.add (initialRoom); While (roomsToCheck! = Leer) If (currentRoom.depth == depthMax) beendetRooms.add (currentRoom); roomsToCheck.remove (currentRoom);  Else Room newRoom.init (currentRoom, currentRoom.depth + 1); roomsToCheck.add (newRoom); beendetRooms.add (currentRoom); roomsToCheck.remove (currentRoom); Array allParentRooms; roomCheck = newRoom; While (roomCheck.parent) allParentRooms.add (roomCheck.parent); roomCheck = roomCheck.parent;  Key newKey.init (Random (allParentRooms)); newRoom.Key = newKey; Fertige Räume zurückgeben;  Else beendetRooms.add (currentRoom); roomsToCheck.remove (currentRoom);  

Dieser zusätzliche Code erzeugt jetzt eine Liste aller Räume, die sich in der Kartenhierarchie über Ihrem aktuellen Raum befinden. Dann wählen wir zufällig einen davon aus und setzen den Schlüssel auf diesen Raum. Danach weisen wir den Schlüssel dem Raum zu, den er aufschließt.

Nach dem Aufruf erstellt unsere Generatorfunktion nun eine bestimmte Anzahl von Räumen mit Schlüsseln und gibt sie zurück. Dies spart möglicherweise Stunden an Entwicklungszeit!

Damit ist der Pseudocode-Teil unseres einfachen Puzzle-Generators abgeschlossen, also setzen wir ihn jetzt in die Tat um.

Demo zur Prozedural Puzzle Generation

Wir haben unsere Demo mit JavaScript und der Crafty.js-Bibliothek erstellt, um sie so leicht wie möglich zu halten, sodass wir unser Programm unter 150 Codezeilen halten können. Es gibt drei Hauptkomponenten unserer Demo:

  1. Der Spieler kann sich durch jedes Level bewegen, Schlüssel abholen und Türen öffnen.

  2. Der Generator, mit dem wir automatisch eine neue Karte erstellen, wenn die Demo ausgeführt wird.

  3. Eine Erweiterung für unseren Generator zur Integration in Crafty.js, mit der Objekt-, Kollisions- und Entitätsinformationen gespeichert werden können.

Der obige Pseudocode dient als Erklärungswerkzeug, sodass die Implementierung des Systems in Ihrer eigenen Programmiersprache einige Änderungen erfordert.

Für unsere Demo wurde ein Teil der Klassen vereinfacht, um die Verwendung von JavaScript effizienter zu gestalten. Dies beinhaltet das Löschen bestimmter Funktionen in Bezug auf die Klassen, da JavaScript den Zugriff auf Variablen innerhalb von Klassen erleichtert.

Um den Spielteil unserer Demo zu erstellen, initialisieren wir Crafty.js und dann eine Spielerentität. Als Nächstes geben wir unserer Spieler-Einheit die grundlegenden vier Richtungssteuerungen und eine geringfügige Kollisionserkennung, um das Betreten von geschlossenen Räumen zu verhindern.

Die Räume erhalten jetzt eine Crafty-Einheit, in der Informationen zu Größe, Ort und Farbe für die visuelle Darstellung gespeichert werden. Wir werden auch eine Zeichenfunktion hinzufügen, mit der wir einen Raum erstellen und auf den Bildschirm zeichnen können.

Wir stellen Schlüssel mit ähnlichen Ergänzungen zur Verfügung, einschließlich der Speicherung der Crafty-Entität, der Größe, des Ortes und der Farbe. Die Schlüssel werden auch farblich codiert, um den Räumen zu entsprechen, die sie entsperren. Schließlich können wir nun die Schlüssel platzieren und ihre Entitäten mit einer neuen Draw-Funktion erstellen.

Zu guter Letzt entwickeln wir eine kleine Hilfsfunktion, die einen zufälligen hexadezimalen Farbwert erstellt und zurückgibt, um die Auswahl der Farben zu beseitigen. Es sei denn, Sie mögen Farbmuster, natürlich.

Was tue ich als nächstes?

Da Sie nun Ihren eigenen einfachen Generator haben, möchten wir Ihnen einige Beispiele für die Erweiterung unserer Beispiele vorstellen:

  1. Portieren Sie den Generator, um die Verwendung in Ihrer bevorzugten Programmiersprache zu ermöglichen.

  2. Erweitern Sie den Generator um das Erstellen von Verzweigungsräumen für weitere Anpassungen.

  3. Fügen Sie unserem Generator die Möglichkeit hinzu, mit mehreren Raumzugängen zu arbeiten, um komplexere Rätsel zu ermöglichen.

  4. Erweitern Sie den Generator, um die Schlüssel an komplizierteren Stellen zu platzieren, um die Problemlösung für Spieler zu verbessern. Dies ist besonders interessant, wenn für Spieler mehrere Pfade verwendet werden.

Einpacken

Jetzt, da wir diesen Puzzle-Generator zusammen erstellt haben, können Sie die gezeigten Konzepte verwenden, um Ihren eigenen Entwicklungszyklus zu vereinfachen. Welche sich wiederholenden Aufgaben erledigen Sie? Was stört Sie am meisten an der Erstellung Ihres Spiels?? 

Die Chancen stehen gut, mit ein wenig Planung und Verfahrensgenerierung können Sie den Prozess erheblich vereinfachen. Hoffentlich können Sie sich mit unserem Generator auf die ansprechenderen Teile des Spielens konzentrieren, während Sie das Alltägliche ausschalten.

Viel Glück und wir sehen uns in den Kommentaren!