Bevor du beginnst, einen Verse-Code für das [„Tagged Lights“-Rätsel] (tagged-lights-puzzle-in-verse) zu schreiben, solltest du überlegen, wie du dein Ziel am besten erreichen kannst. In diesem Abschnitt erfährst du, wie du eine Rätsel-Mechanik erstellen kannst. Am Ende dieses Schritts hast du einen Pseudocode, der den Algorithmus zum Erstellen dieses Rätsels darstellt. Im nächsten Schritt wird gezeigt, wie du diesen Algorithmus in Verse und UEFN implementierst.
Ziele, Anforderungen und Einschränkungen ermitteln
Der erste Schritt besteht darin, deine Ziele, Anforderungen und Einschränkungen zu identifizieren. Anforderungen entstehen oft dadurch, dass umfassendere Ziele in kleinere Teile aufgegliedert werden.
| Ziele |
|
| Anforderungen |
|
| Einschränkungen |
|
Unterteile das Problem
Wenn du dein Ziel kennst und weißt, womit du arbeitest, kannst du das Problem untergliedern, damit es einfacher zu handhaben ist. Fragen können helfen, ein größeres Problem zu untergliedern:
- Wie kann sich der Spieler mit dem Rätsel beschäftigen?
- Wie benutzt du Gameplay-Tags, um die Lichter zu finden?
- Wie definierst du die Anfangsbedingungen und Lösungen, die im Editor geändert werden können?
- Wie kannst du den Spielstatus, der in einer Verse-Struktur gespeichert ist, mit der Visualisierung im Spielverlauf abgleichen?
- Wie kann eine Spielerinteraktion eine bestimmte Reihe von Lichtern aktualisieren?
- Wie kannst du die Spielerinteraktion deaktivieren, wenn das Rätsel gelöst wurde?
Als Nächstes musst du mögliche Abhängigkeiten zwischen diesen kleineren Problemen identifizieren. In diesem Fall wirken die Probleme zunächst unabhängig voneinander, aber dies ist eine Überlegung wert:
- Die Fragen 1, 5 und 6 sind lose miteinander verbunden.
- Bei den Fragen 1 und 6 können die Spielerinteraktionen nicht bestimmen, wie die Interaktion deaktiviert wird, wenn das Rätsel gelöst ist.
- Bei den Fragen 1 und 5 schaltet eine einzige Interaktion mehrere Lichter auf einmal um. Daraus ergibt sich die Datenstruktur, die für die Zuordnung von Interaktion zu Lichtern zu verwenden ist.
- Frage 2 ist eine wichtige gestalterische Überlegung. Die Funktionsweise der Gameplay-Tags-API könnte sich darauf auswirken, wie die Lichter im Code gesteuert werden. Das hat Auswirkungen auf die Fragen 4 und 5, denn der Zustand der Lichter im Spiel muss geändert werden, und es sollte einen allgemeingültigen Weg dazu geben.
- Die Fragen 3 und 4 sollten zu einer einzigen Lösung für die zugrunde liegende Datenstruktur für die Start-, Ist- und Lösungszustände verschmelzen.
Mögliche Lösungen entwickeln
Jetzt, wo das Problem in kleinere Probleme aufgeteilt ist, konzentriere dich darauf, die Fragen zu beantworten, die mit den kleineren Problemen verbunden sind:
1. Wie kann sich der Spieler mit dem Rätsel beschäftigen?
Für diese Frage gibt es mehrere Lösungen. Allgemein kannst du jedes Gerät verwenden, mit dem der Spieler interagieren kann und mit dem Verse die Interaktion erkennen kann. Die Kreativmoduswerkzeuge bieten viele Geräte, die diese Anforderungen erfüllen, darunter Auslösegeräte, Tastengeräte, aber auch Kachelgeräte mit Farboptionen und Wahrnehmungsauslöser.
In diesem Beispiel wird das Schaltflächengerät mit seinem InteractedWithEvent verwendet, welches gesendet wird, wann immer der Spieler die Taste betätigt, sofern die Taste aktiviert ist. Weitere Informationen zu Ereignissen findest du unter [Code für Geräteinteraktionen] (coding-device-interactions-in-verse).
2. Wie benutzt du Gameplay-Tags, um die Lichter zu finden?
Mit Gameplay-Tags kannst du Gruppen von Actors abrufen, denen ein benutzerdefiniertes Tag zugewiesen wurde, das du in deinem Verse-Code definierst.
Du kannst die Funktion GetCreativeObjectsWithTag() verwenden, um ein Array mit allen Actors zu erhalten, denen dein benutzerdefiniertes Tag zugewiesen wurde. Das Ergebnis der Funktion ist ein Array mit allen Objekten, die creative_object_interface implementieren. Das customizable_light_device ist die Verse-Repräsentation eines anpassbaren Lichtgeräts und ist eine Klasse, die das creative_object_interface implementiert.
Es gibt keine garantierte Reihenfolge für die Liste der Geräte, die von GetCreativeObjectsWithTag() zurückgegeben wird, und der Funktionsaufruf kann einige Zeit brauchen, um alle Geräte zurückzugeben, vor allem, wenn es viele Geräte im Level gibt. Daher ist es eine gute Idee, die Lichter zu speichern, um später schnell darauf zugreifen zu können. Dieses Zwischenspeichern kann die Performance verbessern. Da die Lichter eine Sammlung desselben Typs sind, kannst du ein Array verwenden, um sie gemeinsam zu speichern.
Das bedeutet, du kannst:
- Ein neues Tag mit dem Namen
puzzle_lighterstellen. - Alle Lichter für das Rätsel mit dem Tag
puzzle_lightmarkieren. - Rufe
GetCreativeObjectsWithTag(puzzle_light)auf, um alle Actors zu erhalten, die das Tagpuzzle_lighthaben. - Bestimmen, welche der Ergebnisse des Funktionsaufrufs ein
customizable_light_deviceist. - Die Liste der
customizable_light_deviceObjekte in einem Array speichern, damit du später darauf zugreifen kannst.
3. Wie definierst du die Anfangsbedingungen und Lösungen, die im Editor geändert werden können?
Ein Licht hat nur zwei Zustände: ein oder aus. Du kannst den logic-Typ in Verse verwenden, um den Ein/Aus-Zustand eines Lichts darzustellen, da die Werte des logic-Typs nur entweder true oder false sein können. Da es mehrere Lichter gibt, kannst du auch hier einen Array verwenden, um alle logic-Werte zu speichern und die Array-Position oder den Index für einen Lichtzustand mit dem Index für das Licht, dem er zugeordnet ist, abzugleichen.
Dieses Array von logic-Werten kann verwendet werden, um den Anfangszustand der Rätsel-Lichter zu definieren. Es kann auch den aktuellen Zustand der Lichter während des Spiels enthalten. Stelle dieses Array mit dem Attribut @editable dem Editor zur Verfügung. Die Lichter zu Beginn des Spiels können dann ein- oder ausgeschaltet werden, damit sie visuell mit dem im Array gespeicherten Zustand übereinstimmen.
Die Lösung des Rätsels sollte mit dem Typ übereinstimmen, der für die Speicherung des aktuellen Zustands der Lichter verwendet wird, damit du überprüfen kannst, ob das Rätsel gelöst ist, indem du die beiden miteinander vergleichst. Das bedeutet, dass du zwei bearbeitbare logic-Arrays hast, von denen eines den aktuellen Zustand der Lichter, das andere die Lösung des Rätsels darstellt. Du kannst also den Ausgangszustand der Lichter und die Lösung des Rätsels im Editor ändern und das Rätsel so mit verschiedenen Konfigurationen wiederverwenden.
4. Wie kannst du den Spielstatus, der in einer Verse-Struktur gespeichert ist, mit der Visualisierung im Spielverlauf abgleichen?
Du kannst ein customizable_light_device im Spielverlauf mit den Funktionen TurnOn() und TurnOff() ein- oder ausschalten. Wann immer du also den aktuellen Zustand der Lichter aktualisierst, wie er durch den logischen Array dargestellt wird, solltest du auch TurnOn() und TurnOff() aufrufen, um die visuelle Darstellung im Spielverlauf mit dem Spielstatus abzugleichen.
5. Wie kann eine Spielerinteraktion eine bestimmte Reihe von Lichtern aktualisieren?
Anhand der ersten Frage hast du bereits beschlossen, dass der Spieler mithilfe des Schaltflächengeräts mit dem Rätsel interagieren soll. Du kannst für das InteractedWithEvent des Schaltflächengeräts einen Event-Handler abonnieren, der die Lichter ändert, wenn der Spieler mit dem Schaltflächengerät interagiert. Da es mehrere Schaltflächengeräte gibt, die der Spieler verwenden kann, solltest du auch hier ein Array verwenden, um sie zusammenzuhalten.
Nun musst du bestimmen, wie du die einzelnen Schaltflächen-Events den Lichtern zuordnest, die von ihnen umgeschaltet werden sollen.
Da die Reihenfolge der Lichter im „customizable_light_device“-Array die gleiche wie im „logic“-Array ist, das den Zustand der Lichter darstellt, kannst du eine Zuordnung zwischen einer Schaltfläche und den Indizes der Lichter erstellen, auf die sie sich auswirkt. Diese Zuordnung kann in einem Array dargestellt werden, wobei die Reihenfolge der Elemente der Reihenfolge der Schaltflächen entspricht und die Elemente Arrays von Indizes sind.
Du kannst das Array bearbeitbar machen, sodass du die Zuordnung von Schaltflächen und Lichtern im Editor ändern kannst, um das Rätsel wiederzuverwenden, ohne den Code ändern zu müssen.
6. Wie kannst du die Spielerinteraktion deaktivieren, wenn das Rätsel gelöst wurde?
Du weißt bereits, dass der Spieler mit dem Rätsel interagiert, indem er das Schaltflächengerät verwendet, welches durch das InteractedWithEvent erkannt wird.
Wenn das Rätsel gelöst ist, wie kann realisiert werden, dass das Rätsel-Gerät keine Eingaben vom Spieler mehr erhält, damit er das Rätsel nicht mehr ändern kann?
Es gibt dafür mindestens drei Möglichkeiten:
- Deaktiviere die Schaltflächen im Spiel, wenn das Rätsel gelöst ist.
- Füge dem
tagged_lights_puzzleeinLogik-Feld hinzu, sich ändert, wenn das Rätsel gelöst ist. Jedes Mal, wenn der Spielstatus aktualisiert wird, muss dieseslogic-Feld zuerst überprüft werden, um sicherzustellen, dass das Rätsel nicht bereits gelöst wurde. - Hebe das
InteractedWithEvent-Abonnement des Schaltflächengeräts auf, wenn das Rätsel gelöst ist, damit die Event-Handler nicht mehr aufgerufen werden.
Die dritte Möglichkeit ist die beste, weil sie eine einfache und effiziente Lösung darstellt. Du musst keine neuen Felder erstellen, um die bedingte Codeausführung zu prüfen. Das Konzept der Abmeldung von einem Geräte-Event ist auch in anderen Situationen wiederverwendbar. Im Allgemeinen ist es eine gute Praxis, ein Event zu abonnieren, wenn du darüber benachrichtigt werden möchtest, und es abzubestellen, wenn du es nicht mehr brauchst. Die Details der Implementierung für die Abmeldung werden später in diesem Tutorial erklärt.
Kombiniere die Lösungen und den Plan mit Pseudocode
Jetzt, wo du Lösungen für die kleineren Probleme hast, kombiniere sie, um das ursprüngliche Problem zu lösen. Formalisiere den Algorithmus, um die Lösung mit Pseudocode zu erstellen.
Was passiert, wenn das Spiel beginnt? Die Lichter werden eingerichtet. Du abonnierst das InteractedWithEvent der Schaltfläche, suchst alle Geräte mit dem Tag puzzle_light und speicherst sie im Cache. Außerdem schaltest du die Lichter im Spiel ein/aus je nachdem, wie ihr LightState beim Start lautet.
OnBegin:
Das Ergebnis von GetCreativeObjectsWithTag(puzzle_light) wird in der Variablen FoundDevices gespeichert
for each Device in FoundDevices:
if Device is a Customizable Light Device:
Store the Light
if ShouldLightBeOn?:
Turn on Light
else:
Turn off Light
for each Button:
„InteractedWithEvent“ der Schaltfläche mithilfe von Handler „OnButtonInteractedWith“ abonnieren
Eine Pseudocode-Version von OnButtonInteractedWith könnte so aussehen, wobei InteractedButtonIndex für den Index des Array button_device steht, die mit der Schaltfläche übereinstimmt, mit dem der Spieler interagiert hat. Du wirst später in diesem Tutorial sehen, wie du diese Informationen innerhalb des Event-Handlers erhältst.
OnButtonInteractedWith:
Rufe die Lichter ab, die mit der Schaltfläche verbunden sind, die mit dem Array ButtonsToLights interagiert und speichere sie in der Variablen „Lights“
# Schalte die Lichter um
for each Light in Lights:
if IsLightOn?:
Spielzustand des Lichts aus
Turn off Light
else:
Spielzustand des Lichts ein
Turn on Light
if IsPuzzleSolved():
Enable Item Spawner
for each Button:
Abonnement von „InteractedWithEvent“ der Tasten aufheben
Der Pseudocode für IsPuzzleSolved prüft, ob der aktuelle Zustand der Lichter mit der Lösung übereinstimmt. Wenn der aktuelle Zustand nicht mit der Lösung übereinstimmt, schlägt die Prüfung fehl und der Block if IsPuzzleSolved aus dem obigen Pseudocode wird nicht ausgeführt. Wenn der aktuelle Zustand mit der Lösung übereinstimmt, ist die Prüfung erfolgreich und der Block if IsPuzzleSolved wird ausgeführt.
IsPuzzleSolved:
for each Light:
if IsLightOn is not equal to IsLightOnInSolution
fail and return
succeed
Jetzt hast du deinen Algorithmus entwickelt!
Nächster Schritt
Im nächsten Schritt dieses Tutorials wirst du diesen Algorithmus in die Programmiersprache Verse übersetzen und dein Projekt testen, um diese Schritte in Aktion zu sehen.