Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Game Development

Aktualisierter Primer zum Erstellen isometrischer Welten, Teil 2

by
Read Time:17 minsLanguages:
This post is part of a series called Primer for Creating Isometric Worlds.
An Updated Primer for Creating Isometric Worlds, Part 1

German (Deutsch) translation by Valentina (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

In diesem letzten Teil der Tutorial-Reihe bauen wir auf dem ersten Tutorial auf und lernen, wie Sie Pickups, Trigger, Level-Swapping, Pfadfindung, Pfadverfolgung, Level-Scrolling, isometrische Höhe und isometrische Projektile implementieren.

1. Pickups

Pickups sind Gegenstände, die innerhalb des Levels gesammelt werden können, normalerweise indem man einfach darüber läuft - zum Beispiel Münzen, Edelsteine, Bargeld, Munition usw.

Abholdaten können wie folgt direkt in unsere Level-Daten aufgenommen werden:

In diesen Level-Daten verwenden wir 8, um eine Aufnahme auf einer Grasfliese zu bezeichnen (1 und 0 stehen wie zuvor für Wände bzw. begehbare Fliesen). Dies kann ein einzelnes Kachelbild mit einer Graskachel sein, die mit dem Aufnahmebild überlagert ist. Nach dieser Logik benötigen wir zwei verschiedene Kachelzustände für jede Kachel, die eine Aufnahme hat, d. h. Eine mit Aufnahme und eine ohne, die angezeigt wird, nachdem die Aufnahme gesammelt wurde.

Typische isometrische Kunst hat mehrere begehbare Kacheln - nehmen wir an, wir haben 30. Der obige Ansatz bedeutet, dass wir, wenn wir N Tonabnehmer haben, zusätzlich zu den 30 Originalkacheln N x 30 Kacheln benötigen, da jede Kachel eine Version mit haben muss Pickups und einer ohne. Dies ist nicht sehr effizient; Stattdessen sollten wir versuchen, diese Kombinationen dynamisch zu erstellen.

Um dies zu lösen, könnten wir dieselbe Methode verwenden, mit der wir den Helden im ersten Tutorial platziert haben. Immer wenn wir auf ein Pickup-Plättchen stoßen, legen wir zuerst ein Grasplättchen und dann das Pickup auf das Grasplättchen. Auf diese Weise benötigen wir nur N Pickup-Kacheln zusätzlich zu 30 begehbaren Kacheln, aber wir würden Zahlenwerte benötigen, um jede Kombination in den Level-Daten darzustellen. Um die Notwendigkeit von N x 30 Darstellungswerten zu lösen, können wir ein separates pickupArray behalten, um die Pickup-Daten ausschließlich neben den levelData zu speichern. Das abgeschlossene Level mit dem Pickup ist unten dargestellt:

Isometric level with coin pickupIsometric level with coin pickupIsometric level with coin pickup

In unserem Beispiel halte ich die Dinge einfach und verwende kein zusätzliches Array für Tonabnehmer.

Pickups abholen

Das Erkennen von Tonabnehmern erfolgt auf die gleiche Weise wie das Erkennen von Kollisionskacheln, jedoch nach dem Verschieben des Charakters.

In der Funktion onPickupTile() prüfen wir, ob der Wert des levelData-Arrays an der heroMapTile-Koordinate eine Pickup-Kachel ist oder nicht. Die Zahl im levelData-Array an dieser Kachelkoordinate gibt die Art der Aufnahme an. Wir suchen vor dem Verschieben des Charakters nach Kollisionen, müssen danach jedoch nach Pickups suchen, da der Charakter bei Kollisionen den Platz nicht einnehmen sollte, wenn er bereits von der Kollisionskachel besetzt ist, aber bei Pickups kann sich der Charakter frei bewegen darüber.

Eine andere Sache zu beachten ist, dass sich die Kollisionsdaten normalerweise nie ändern, aber die Abholdaten ändern sich, wenn wir einen Gegenstand abholen. (Dies beinhaltet normalerweise nur das Ändern des Werts im levelData-Array von beispielsweise 8 auf 0.)

Dies führt zu einem Problem: Was passiert, wenn wir das Level neu starten und somit alle Tonabnehmer auf ihre ursprüngliche Position zurücksetzen müssen? Wir haben nicht die Informationen, um dies zu tun, da das levelData-Array geändert wurde, als der Spieler Gegenstände aufnahm. Die Lösung besteht darin, während des Spiels ein doppeltes Array für das Level zu verwenden und das ursprüngliche LevelData-Array intakt zu halten. Zum Beispiel verwenden wir levelData und levelDataLive[], klonen letztere zu Beginn des Levels von ersteren und ändern levelDataLive[] nur während des Spiels.

Zum Beispiel spawne ich nach jeder Aufnahme einen zufälligen Pickup auf einem freien Grasplättchen und erhöhe den pickupCount. Die Funktion pickupItem sieht folgendermaßen aus.

Sie sollten beachten, dass wir immer dann nach Abholungen suchen, wenn sich der Charakter auf diesem Plättchen befindet. Dies kann innerhalb einer Sekunde mehrmals vorkommen (wir prüfen nur, wann sich der Benutzer bewegt, aber wir können innerhalb einer Kachel herumlaufen), aber die obige Logik schlägt nicht fehl. Da wir die levelData-Array-Daten beim ersten Erkennen eines Pickups auf 0 setzen, geben alle nachfolgenden onPickupTile()-Prüfungen für diese Kachel false zurück. Schauen Sie sich das interaktive Beispiel unten an:

2. Kacheln auslösen

Wie der Name schon sagt, verursachen Triggerplättchen, dass etwas passiert, wenn der Spieler darauf tritt oder eine Taste drückt, wenn er darauf tritt. Sie können den Spieler an einen anderen Ort teleportieren, ein Tor öffnen oder einen Feind hervorbringen, um nur einige Beispiele zu nennen. In gewissem Sinne sind Pickups nur eine spezielle Form von Triggerplättchen: Wenn der Spieler auf ein Plättchen mit einer Münze tritt, verschwindet die Münze und ihr Münzzähler erhöht sich.

Schauen wir uns an, wie wir eine Tür implementieren können, die den Spieler auf eine andere Ebene bringt. Die Kachel neben der Tür ist eine Auslösekachel. Wenn der Spieler die x-Taste drückt, geht er zum nächsten Level über.

Isometric level with doors trigger tilesIsometric level with doors trigger tilesIsometric level with doors trigger tiles

Um die Level zu ändern, müssen wir nur das aktuelle levelData-Array gegen das des neuen Levels austauschen und die neue heroMapTile-Position und -Richtung für den Heldencharakter festlegen. Angenommen, es gibt zwei Ebenen mit Türen, durch die sie hindurchgehen können. Da das Bodenplättchen neben der Tür in beiden Ebenen das Auslöseplättchen ist, können wir dies als neue Position für den Charakter verwenden, wenn sie in der Ebene erscheinen.

Die Implementierungslogik ist hier dieselbe wie für Pickups, und wir verwenden wieder das levelData-Array, um Triggerwerte zu speichern. In unserem Beispiel bezeichnet 2 eine Türkachel und der Wert daneben ist der Auslöser. Ich habe 101 und 102 mit der Grundkonvention verwendet, dass jede Kachel mit einem Wert größer als 100 eine Triggerkachel ist und der Wert minus 100 die Ebene sein kann, zu der sie führt:

Der Code zum Überprüfen auf ein Triggerereignis wird unten angezeigt:

Die Funktion triggerListener() prüft, ob der Triggerdaten-Array-Wert an der angegebenen Koordinate größer als 100 ist. Wenn ja, ermitteln wir, zu welcher Ebene wir wechseln müssen, indem wir 100 vom Kachelwert subtrahieren. Die Funktion findet das Triggerplättchen in den neuen levelData, die die Spawnposition für unseren Helden darstellen. Ich habe den Trigger aktiviert, wenn x losgelassen wird. Wenn wir nur auf das Drücken der Taste warten, landen wir in einer Schleife, in der wir zwischen den Ebenen wechseln, solange die Taste gedrückt gehalten wird, da der Charakter immer in der neuen Ebene auf einem Triggerplättchen erscheint.

Hier ist eine funktionierende Demo. Versuchen Sie, Gegenstände aufzunehmen, indem Sie darüber gehen und die Ebenen tauschen, indem Sie neben Türen stehen und x drücken.

3. Projektile

Ein Projektil ist etwas, das sich mit einer bestimmten Geschwindigkeit in eine bestimmte Richtung bewegt, wie eine Kugel, ein Zauberspruch, ein Ball usw. Abgesehen von der Höhe ist alles am Projektil dasselbe wie der Heldencharakter: anstatt entlang der zu rollen Boden, Projektile schweben oft in einer bestimmten Höhe darüber. Eine Kugel bewegt sich über die Taillenhöhe des Charakters, und möglicherweise muss sogar ein Ball herumspringen.

Interessant ist, dass die isometrische Höhe der Höhe in einer 2D-Seitenansicht entspricht, jedoch einen geringeren Wert aufweist. Es sind keine komplizierten Konvertierungen erforderlich. Wenn sich eine Kugel in kartesischen Koordinaten 10 Pixel über dem Boden befindet, kann sie sich in isometrischen Koordinaten 10 oder 6 Pixel über dem Boden befinden. (In unserem Fall ist die relevante Achse die y-Achse.)

Lassen Sie uns versuchen, einen Ball zu implementieren, der in unserem ummauerten Grasland springt. Als Hauch von Realismus fügen wir dem Ball einen Schatten hinzu. Alles, was wir tun müssen, ist, den Wert für die Sprunghöhe zum isometrischen Y-Wert unseres Balls zu addieren. Der Sprunghöhenwert ändert sich je nach Schwerkraft von Bild zu Bild. Sobald der Ball auf dem Boden aufschlägt, wird die aktuelle Geschwindigkeit entlang der y-Achse umgedreht.

Bevor wir uns mit dem Prellen in einem isometrischen System befassen, werden wir sehen, wie wir es in einem kartesischen 2D-System implementieren können. Stellen wir die Sprungkraft des Balls mit einem variablen zValue dar. Stellen Sie sich vor, der Ball hat zunächst eine Sprungkraft von 100, also zValue = 100.

Wir werden zwei weitere Variablen verwenden: incrementValue, das bei 0 beginnt, und gravity, die den Wert -1 hat. In jedem Frame subtrahieren wir incrementValue von zValue und gravity von incrementValue, um einen Dämpfungseffekt zu erzielen. Wenn zValue 0 erreicht, bedeutet dies, dass der Ball den Boden erreicht hat. An diesem Punkt drehen wir das Vorzeichen von incrementValue um, indem wir es mit -1 multiplizieren und es in eine positive Zahl umwandeln. Dies bedeutet, dass sich der Ball ab dem nächsten Frame nach oben bewegt und somit abprallt.

So sieht das im Code aus:

Der Code bleibt auch für die isometrische Ansicht gleich, mit dem kleinen Unterschied, dass Sie zunächst einen niedrigeren Wert für zValue verwenden können. Unten sehen Sie, wie der zValue beim Rendern zum isometrischen y-Wert des Balls addiert wird.

Schauen Sie sich das interaktive Beispiel unten an:

Verstehe, dass die Rolle des Schattens eine sehr wichtige Rolle spielt, die zum Realismus dieser Illusion beiträgt. Beachten Sie außerdem, dass wir jetzt die beiden Bildschirmkoordinaten (x und y) verwenden, um drei Dimensionen in isometrischen Koordinaten darzustellen. Die y-Achse in Bildschirmkoordinaten ist auch die z-Achse in isometrischen Koordinaten. Das kann verwirrend sein!

4. Einen Weg finden und ihm folgen

Pfadfindung und Pfadverfolgung sind ziemlich komplizierte Prozesse. Es gibt verschiedene Ansätze, bei denen unterschiedliche Algorithmen zum Ermitteln des Pfads zwischen zwei Punkten verwendet werden. Da es sich bei levelData jedoch um ein 2D-Array handelt, sind die Dinge einfacher als sonst. Wir haben gut definierte und einzigartige Knoten, die der Spieler besetzen kann, und wir können leicht überprüfen, ob sie begehbar sind.

Zusammenhängende Posts

Eine detaillierte Übersicht über Pfadfindungsalgorithmen liegt außerhalb des Geltungsbereichs dieses Artikels, aber ich werde versuchen, die gebräuchlichste Funktionsweise zu erklären: den Algorithmus für kürzeste Pfade, von dem die Algorithmen von A* und Dijkstra berühmte Implementierungen sind.

Wir wollen Knoten finden, die einen Startknoten und einen Endknoten verbinden. Vom Startknoten aus besuchen wir alle acht benachbarten Knoten und markieren sie alle als besucht. Dieser Kernprozess wird für jeden neu besuchten Knoten rekursiv wiederholt.

Jeder Thread verfolgt die besuchten Knoten. Beim Springen zu benachbarten Knoten werden bereits besuchte Knoten übersprungen (die Rekursion stoppt). Andernfalls wird der Prozess fortgesetzt, bis wir den Endknoten erreichen, wo die Rekursion endet und der vollständige Pfad als Knotenarray zurückgegeben wird. Manchmal wird der Endknoten nie erreicht. In diesem Fall schlägt die Pfadfindung fehl. Normalerweise finden wir mehrere Pfade zwischen den beiden Knoten. In diesem Fall nehmen wir den mit der geringsten Anzahl von Knoten.

Wegfindung

Es ist unklug, das Rad neu zu erfinden, wenn es um genau definierte Algorithmen geht. Daher würden wir vorhandene Lösungen für unsere Wegfindungszwecke verwenden. Um Phaser verwenden zu können, benötigen wir eine JavaScript-Lösung. Die von mir ausgewählte ist EasyStarJS. Wir initialisieren die Pfadfindungs-Engine wie folgt.

Da unsere levelData nur 0 und 1 haben, können wir sie direkt als Knotenarray übergeben. Wir setzen den Wert 0 als begehbaren Knoten. Wir aktivieren die diagonale Gehfähigkeit, deaktivieren diese jedoch, wenn Sie in der Nähe der Ecken nicht begehbarer Fliesen gehen.

Dies liegt daran, dass der Held, wenn er aktiviert ist, während eines diagonalen Spaziergangs in das nicht begehbare Plättchen schneiden kann. In einem solchen Fall lässt unsere Kollisionserkennung den Helden nicht durch. Bitte beachten Sie auch, dass ich im Beispiel die Kollisionserkennung vollständig entfernt habe, da dies für ein AI-basiertes Gehbeispiel nicht mehr erforderlich ist.

Wir werden den Abgriff auf jede freie Kachel innerhalb des Levels erkennen und den Pfad mithilfe der findPath-Funktion berechnen. Die Rückrufmethode plotAndMove empfängt das Knotenarray des resultierenden Pfads. Wir markieren die minimap mit dem neu gefundenen Pfad.

Isometric level with the newly found path highlighted in minimapIsometric level with the newly found path highlighted in minimapIsometric level with the newly found path highlighted in minimap

Pfadverfolgung

Sobald wir den Pfad als Knotenarray haben, müssen wir das Zeichen dazu bringen, ihm zu folgen.

Angenommen, wir möchten den Charakter dazu bringen, zu einem Plättchen zu gehen, auf das wir klicken. Wir müssen zuerst nach einem Pfad zwischen dem Knoten suchen, den das Zeichen derzeit belegt, und dem Knoten, auf den wir geklickt haben. Wenn ein erfolgreicher Pfad gefunden wird, müssen wir das Zeichen auf den ersten Knoten im Knotenarray verschieben, indem wir es als Ziel festlegen. Sobald wir den Zielknoten erreicht haben, prüfen wir, ob sich weitere Knoten im Knotenarray befinden, und legen in diesem Fall den nächsten Knoten als Ziel fest - und so weiter, bis wir den endgültigen Knoten erreichen.

Wir werden auch die Richtung des Spielers basierend auf dem aktuellen Knoten und dem neuen Zielknoten jedes Mal ändern, wenn wir einen Knoten erreichen. Zwischen den Knoten gehen wir einfach in die gewünschte Richtung, bis wir den Zielknoten erreichen. Dies ist eine sehr einfache KI, und im Beispiel erfolgt dies mit der teilweise unten gezeigten Methode aiWalk.

Wir müssen gültige Klickpunkte herausfiltern, indem wir feststellen, ob wir innerhalb des begehbaren Bereichs geklickt haben, anstatt einer Wandfliese oder einer anderen nicht begehbaren Fliese.

Ein weiterer interessanter Punkt für die Codierung der KI: Wir möchten nicht, dass sich der Charakter zur nächsten Kachel im Knotenarray dreht, sobald er in der aktuellen Kachel angekommen ist, da eine solche sofortige Drehung dazu führt, dass unser Charakter an den Grenzen von läuft Fliesen. Stattdessen sollten wir warten, bis sich der Charakter ein paar Schritte innerhalb der Kachel befindet, bevor wir nach dem nächsten Ziel suchen. Es ist auch besser, den Helden kurz vor dem Wenden manuell in die Mitte des aktuellen Plättchens zu legen, damit sich alles perfekt anfühlt.

Schauen Sie sich die Arbeitsdemo unten an:

5. Isometrisches Scrollen

Wenn der Ebenenbereich viel größer als der verfügbare Bildschirmbereich ist, müssen Sie ihn scrollen lassen.

Isometric level with 12x12 visible areaIsometric level with 12x12 visible areaIsometric level with 12x12 visible area

Der sichtbare Bildschirmbereich kann als kleineres Rechteck innerhalb des größeren Rechtecks des gesamten Ebenenbereichs betrachtet werden. Beim Scrollen wird im Wesentlichen nur das innere Rechteck innerhalb des größeren verschoben. Wenn ein solches Scrollen stattfindet, bleibt die Position des Helden normalerweise in Bezug auf das Bildschirmrechteck gleich, üblicherweise in der Bildschirmmitte. Interessanterweise müssen wir zum Scrollen nur den Eckpunkt des inneren Rechtecks verfolgen.

Dieser Eckpunkt, den wir in kartesischen Koordinaten darstellen, fällt in die Ebenendaten innerhalb einer Kachel. Zum Scrollen erhöhen wir die x- und y-Position des Eckpunkts in kartesischen Koordinaten. Jetzt können wir diesen Punkt in isometrische Koordinaten konvertieren und damit den Bildschirm zeichnen.

Die neu konvertierten Werte im isometrischen Raum müssen auch die Ecke unseres Bildschirms sein, was bedeutet, dass sie die neuen sind (0, 0). Während wir die Ebenendaten analysieren und zeichnen, subtrahieren wir diesen Wert von der isometrischen Position jeder Kachel und können bestimmen, ob die neue Position der Kachel in den Bildschirm fällt.

Alternativ können wir entscheiden, dass wir nur ein isometrisches X x Y-Kachelgitter auf dem Bildschirm zeichnen, um die Zeichenschleife für größere Ebenen effizient zu gestalten.

Wir können dies in Schritten wie folgt ausdrücken:

  • Aktualisieren Sie die x- und y-Koordinaten des kartesischen Eckpunkts.
  • Konvertieren Sie dies in einen isometrischen Raum.
  • Subtrahieren Sie diesen Wert von der isometrischen Zeichenposition jeder Kachel.
  • Zeichnen Sie ab dieser neuen Ecke nur eine begrenzte vordefinierte Anzahl von Kacheln auf dem Bildschirm.
  • Optional: Zeichnen Sie die Kachel nur, wenn die neue isometrische Zeichenposition innerhalb des Bildschirms liegt.

Bitte beachte, dass der Eckpunkt in die entgegengesetzte Richtung zur Positionsaktualisierung des Helden erhöht wird, wenn er sich bewegt. Dies stellt sicher, dass der Held in Bezug auf den Bildschirm dort bleibt, wo er ist. Schauen Sie sich dieses Beispiel an (scrollen Sie mit den Pfeilen, tippen Sie auf, um das sichtbare Raster zu vergrößern).

Ein paar Anmerkungen:

  • Während des Bildlaufs müssen wir möglicherweise zusätzliche Kacheln an den Bildschirmrändern zeichnen, da sonst möglicherweise Kacheln verschwinden und an den Bildschirmextremen angezeigt werden.
  • Wenn Sie Kacheln haben, die mehr als ein Feld einnehmen, müssen Sie mehr Kacheln an den Rändern zeichnen. Wenn beispielsweise die größte Kachel im gesamten Satz X mal Y misst, müssen Sie links und rechts X weitere Kacheln und oben und unten Y weitere Kacheln zeichnen. Dadurch wird sichergestellt, dass die Ecken der größeren Kachel beim Scrollen in oder aus dem Bildschirm weiterhin sichtbar sind.
  • Wir müssen immer noch sicherstellen, dass wir keine leeren Bereiche auf dem Bildschirm haben, während wir in der Nähe der Grenzen des Levels zeichnen.
  • Die Ebene sollte nur so lange gescrollt werden, bis die extremste Kachel am entsprechenden Bildschirmextrem gezeichnet wird. Danach sollte sich der Charakter ohne Scrollen der Ebene weiter im Bildschirmbereich bewegen. Dazu müssen wir alle vier Ecken des inneren Bildschirmrechtecks verfolgen und die Bildlauf- und Spielerbewegungslogik entsprechend drosseln. Sind Sie bereit für die Herausforderung, dies selbst umzusetzen?

Abschluss

Diese Serie richtet sich insbesondere an Anfänger, die versuchen, isometrische Spielwelten zu erkunden. Viele der erläuterten Konzepte haben alternative Ansätze, die etwas komplizierter sind, und ich habe bewusst die einfachsten ausgewählt.

Sie erfüllen möglicherweise nicht die meisten Szenarien, denen Sie begegnen, aber das gewonnene Wissen kann verwendet werden, um auf diesen Konzepten aufzubauen und kompliziertere Lösungen zu erstellen. Zum Beispiel wird die einfache implementierte Tiefensortierung unterbrochen, wenn mehrstöckige Ebenen und Plattformkacheln von einer Etage zur anderen wechseln.

Aber das ist ein Tutorial für ein anderes Mal.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.