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

Erstellen eines sechseckigen Minesweeper

by
Read Time:16 minsLanguages:

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

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

In diesem Tutorial werde ich versuchen, die interessante Welt der sechseckigen Kachel-basierten Spiele mit den einfachsten Ansätzen vorzustellen. Sie erfahren, wie Sie zweidimensionale Array-Daten auf dem Bildschirm in ein entsprechendes hexagonales Ebenenlayout umwandeln und umgekehrt. Mit den gewonnenen Informationen werden wir ein sechseckiges Minesweeper-Spiel in zwei verschiedenen sechseckigen Layouts erstellen.

Dies wird Ihnen den Einstieg in die Erforschung einfacher sechseckiger Brettspiele und Puzzlespiele erleichtern und ist ein guter Ausgangspunkt, um kompliziertere Ansätze wie das axiale oder kubische sechseckige Koordinatensystem zu erlernen.

1. Sechseckige Kacheln und Layouts

In der aktuellen Generation von Casual Gaming sehen wir nicht viele Spiele, die einen hexagonalen Kachel-basierten Ansatz verwenden. Diejenigen, denen wir begegnen, sind normalerweise Puzzlespiele, Brettspiele oder Strategiespiele. Außerdem werden die meisten unserer Anforderungen durch den quadratischen Rasteransatz oder isometrischen Ansatz erfüllt. Dies führt zu der natürlichen Frage: "Warum brauchen wir einen anderen und offensichtlich komplizierten hexagonalen Ansatz?" Lass es uns herausfinden.

Vorteile des Hexagonal-Ansatzes

Was macht also den auf hexagonalen Kacheln basierenden Ansatz relevant, da wir bereits andere Ansätze gelernt und perfektioniert haben? Lassen Sie mich einige der Gründe auflisten.

  • Kleinere Anzahl von Nachbarplättchen: Im Vergleich zu einem quadratischen Raster, das acht Nachbarplättchen hat, hat ein sechseckiges Plättchen nur sechs Nachbarn. Dies reduziert die Berechnungen für komplizierte Algorithmen.
  • Alle Nachbarkacheln haben den gleichen Abstand: Bei einem quadratischen Raster sind die vier diagonalen Nachbarn im Vergleich zu den horizontalen oder vertikalen Nachbarn weit entfernt. Gleiche Entfernungen von Nachbarn sind eine große Erleichterung bei der Berechnung von Heuristiken und reduzieren den Aufwand, zwei verschiedene Methoden zu verwenden, um etwas in Abhängigkeit vom Nachbarn zu berechnen.
  • Einzigartigkeit: Heutzutage kommen Millionen von Casual Games heraus und konkurrieren um die Zeit des Spielers. Großartige Spiele schaffen es nicht, ein Publikum zu gewinnen, und eine Sache, die garantiert die Aufmerksamkeit eines Spielers auf sich zieht, ist Einzigartigkeit. Ein Spiel, das einen sechseckigen Ansatz verwendet, wird sich optisch von den anderen abheben und das Spiel wird für eine Menge interessanter erscheinen, die von all den herkömmlichen Spielmechaniken gelangweilt ist.

Ich würde sagen, der letzte Grund sollte für Sie ausreichen, diesen neuen Ansatz zu meistern. Das Hinzufügen dieses einzigartigen Gameplay-Elements zu Ihrer Spiellogik könnte den Unterschied ausmachen und Ihnen ermöglichen, ein großartiges Spiel zu entwickeln.

Die anderen Gründe sind rein technischer Natur und würden erst bei komplizierten Algorithmen oder größeren Kachelsets zum Tragen kommen. Es gibt noch viele andere Aspekte, die als Vorteile des hexagonalen Ansatzes aufgeführt werden können, aber die meisten davon hängen vom persönlichen Interesse des Spielers ab.

Grundrisse

Ein Sechseck ist ein Polygon mit sechs Seiten, und ein Sechseck, bei dem alle Seiten gleich lang sind, wird als regelmäßiges Sechseck bezeichnet. Aus theoretischen Gründen betrachten wir unsere sechseckigen Kacheln als regelmäßige Sechsecke, aber sie könnten in der Praxis gequetscht oder verlängert werden.

Das Interessante daran ist, dass ein Sechseck auf zwei verschiedene Arten platziert werden kann: Die spitzen Ecken könnten vertikal oder horizontal ausgerichtet werden. Wenn spitze Spitzen vertikal ausgerichtet sind, wird dies als horizontales Layout bezeichnet, und wenn sie horizontal ausgerichtet sind, wird es als vertikales Layout bezeichnet. Sie könnten denken, dass die Namen in Bezug auf die bereitgestellte Erklärung falsch sind. Dies ist nicht der Fall, da die Benennung nicht nach den spitzen Ecken erfolgt, sondern nach der Art und Weise, wie ein Fliesenraster angeordnet wird. Das Bild unten zeigt die verschiedenen Kachelausrichtungen und die entsprechenden Layouts.

The vertical and horizontal hexagonal tile grid layoutThe vertical and horizontal hexagonal tile grid layoutThe vertical and horizontal hexagonal tile grid layout

Die Wahl des Layouts hängt vollständig von der Grafik und dem Gameplay Ihres Spiels ab. Ihre Wahl endet hier jedoch nicht, da jedes dieser Layouts auf zwei verschiedene Arten implementiert werden kann.

Betrachten wir ein horizontales sechseckiges Rasterlayout. Alternative Zeilen des Rasters müssten horizontal um hexTileWidth/2 versetzt werden. Dies bedeutet, dass wir entweder die ungeraden Zeilen oder die geraden Zeilen versetzen können. Wenn wir auch die entsprechenden Zeilen- und Spaltenwerte anzeigen, würden diese Varianten wie in der Abbildung unten aussehen.

Horizontal hexagonal layout with even odd offsetsHorizontal hexagonal layout with even odd offsetsHorizontal hexagonal layout with even odd offsets

In ähnlicher Weise könnte das vertikale Layout in zwei Variationen implementiert werden, während alternative Spalten um hexTileHeight/2 versetzt werden, wie unten gezeigt.

Vertical hexagonal layout showing even odd variationsVertical hexagonal layout showing even odd variationsVertical hexagonal layout showing even odd variations

2. Implementieren von sechseckigen Layouts

Von hier an beginnen Sie bitte, sich auf den Quellcode zu beziehen, der zusammen mit diesem Tutorial zum besseren Verständnis bereitgestellt wird.

Die obigen Bilder mit den angezeigten Zeilen und Spalten erleichtern die Visualisierung einer direkten Korrelation mit einem zweidimensionalen Array, das die Pegeldaten speichert. Nehmen wir an, wir haben ein einfaches zweidimensionales Array levelData wie unten.

Zur besseren Visualisierung zeige ich hier das beabsichtigte Ergebnis sowohl in vertikaler als auch in horizontaler Variation.

simple hexagonal grid based on level data arraysimple hexagonal grid based on level data arraysimple hexagonal grid based on level data array

Beginnen wir mit dem horizontalen Layout, dem Bild auf der linken Seite. In jeder Reihe, wenn einzeln genommen, sind die Nachbarkacheln horizontal um hexTileWidth versetzt. Alternative Zeilen werden horizontal um einen Wert von hexTileWidth/2 versetzt. Der vertikale Höhenunterschied zwischen den einzelnen Zeilen beträgt hexTileHeight*3/4.

Um zu verstehen, wie wir zu einem solchen Wert für den Höhenversatz gekommen sind, müssen wir die Tatsache berücksichtigen, dass die oberen und unteren dreieckigen Teile eines horizontal angelegten Sechsecks genau hexTileHeight/4 sind.

Dies bedeutet, dass das Sechseck einen rechteckigen hexTileHeight/2-Anteil in der Mitte, einen dreieckigen hexTileHeight/4-Anteil oben und einen umgekehrt dreieckigen hexTileHeight/4-Anteil unten hat. Diese Informationen reichen aus, um den erforderlichen Code zu erstellen, um das sechseckige Raster auf dem Bildschirm zu erstellen.

Mit dem HexTile-Prototyp habe ich dem Phaser.Sprite-Prototyp einige zusätzliche Funktionalitäten hinzugefügt, die es ihm ermöglichen, die i- und j-Werte anzuzeigen. Der Code platziert im Wesentlichen eine neue sechseckige Kachel Sprite an startX und startY. Dieser Code kann geändert werden, um die gerade Offset-Variante anzuzeigen, indem Sie einfach einen Operator in der if-Bedingung wie folgt entfernen: if(i%2===0).

Bei einem vertikalen Layout (das Bild in der rechten Hälfte) sind benachbarte Kacheln in jeder Spalte vertikal um hexTileHeight versetzt. Jede alternative Spalte ist vertikal um hexTileHeight/2 versetzt. Wenn wir die Logik anwenden, die wir für den vertikalen Versatz für das horizontale Layout angewendet haben, können wir sehen, dass der horizontale Versatz für das vertikale Layout zwischen benachbarten Kacheln in einer Reihe hexTileWidth*3/4 beträgt. Der entsprechende Code steht unten.

Genauso wie beim horizontalen Layout können wir auf die gerade versetzte Variante umschalten, indem wir einfach das ! Operator in der oberen if-Bedingung. Ich verwende eine Phaser-Group, um alle hexTiles mit dem Namen hexGrid zu sammeln. Der Einfachheit halber verwende ich den Mittelpunkt des sechseckigen Kachelbildes als Anker, sonst müssten wir auch die Bildversätze berücksichtigen.

Beachten Sie, dass die Werte für Kachelbreite und Kachelhöhe im horizontalen Layout nicht mit den Werten für Kachelbreite und Kachelhöhe im vertikalen Layout übereinstimmen. Wenn wir jedoch dasselbe Bild für beide Layouts verwenden, könnten wir das Kachelbild einfach um 90 Grad drehen und die Werte für Kachelbreite und Kachelhöhe vertauschen.

3. Ermitteln des Array-Index einer sechseckigen Kachel

Die Anordnungslogik von Array zu Bildschirm war interessanterweise einfach, aber umgekehrt ist es nicht so einfach. Bedenken Sie, dass wir den Array-Index der sechseckigen Kachel finden müssen, auf die wir getippt haben. Der Code, um dies zu erreichen, ist nicht schön und wird normalerweise durch Versuch und Irrtum erreicht.

Wenn wir das horizontale Layout betrachten, kann es scheinen, dass der mittlere rechteckige Teil der sechseckigen Kachel uns leicht helfen kann, den j-Wert zu ermitteln, da es nur darum geht, den x-Wert durch hexTileWidth zu dividieren und den ganzzahligen Wert zu nehmen. Aber wenn wir den i-Wert nicht kennen, wissen wir nicht, ob wir uns in einer ungeraden oder geraden Reihe befinden. Ein ungefährer Wert von i kann gefunden werden, indem der y-Wert durch hexTileHeight*3/4 geteilt wird.

Jetzt kommen die komplizierten Teile der sechseckigen Fliese: die oberen und unteren dreieckigen Teile. Das folgende Bild hilft uns, das vorliegende Problem zu verstehen.

a horizontally laid hexagonal tile split into regionsa horizontally laid hexagonal tile split into regionsa horizontally laid hexagonal tile split into regions

Die Bereiche 2, 3, 5, 6, 8 und 9 bilden zusammen eine Kachel. Der komplizierteste Teil besteht darin, herauszufinden, ob sich die Gewindeposition in 1/2 oder 3/4 oder 7/8 oder 9/10 befindet. Dazu müssen wir alle einzelnen Dreiecksbereiche berücksichtigen und anhand der Neigung der schrägen Kante gegenprüfen.

Diese Neigung kann aus der Höhe und Breite jedes dreieckigen Bereichs ermittelt werden, die jeweils hexTileHeight/4 und hexTileWidth/2 sind. Lassen Sie mich Ihnen die Funktion zeigen, die dies tut.

Zuerst finden wir xVal und yVal auf die gleiche Weise wie für ein quadratisches Gitter. Dann finden wir die verbleibenden horizontalen (dX) und vertikalen (dY) Werte, nachdem wir den Kachelmultiplikator-Offset entfernt haben. Mit diesen Werten versuchen wir herauszufinden, ob der Punkt in einem der komplizierten Dreiecksbereiche liegt.

Falls gefunden, nehmen wir entsprechende Änderungen an den Anfangswerten von xVal und yVal vor. Wie ich bereits sagte, ist der Code nicht schön und nicht einfach. Der einfachste Weg, dies zu verstehen, wäre, findHexTile bei Mausbewegung aufzurufen, dann console.log in jede dieser Bedingungen einzufügen und die Maus über verschiedene Bereiche innerhalb einer sechseckigen Kachel zu bewegen. Auf diese Weise können Sie sehen, wie jede intrahexagonale Region behandelt wird.

Die Codeänderungen für das vertikale Layout werden unten gezeigt.

4. Nachbarn finden

Nachdem wir nun die Kachel gefunden haben, auf die wir getippt haben, suchen wir alle sechs benachbarten Kacheln. Dies ist ein sehr einfach zu lösendes Problem, sobald wir das Gitter visuell analysieren. Betrachten wir das horizontale Layout.

odd and even rows of horizontal layout when a middle tile has ij set to 0odd and even rows of horizontal layout when a middle tile has ij set to 0odd and even rows of horizontal layout when a middle tile has ij set to 0

Das obige Bild zeigt die ungeraden und geraden Reihen eines horizontal angelegten sechseckigen Gitters, wenn eine mittlere Kachel den Wert 0 sowohl für i als auch für j hat. Aus dem Bild wird deutlich, dass, wenn die Reihe ungerade ist, für eine Kachel bei i,j die Nachbarn i, j-1, i-1,j-1, i-1,j, i,j+1 sind i+1,j und i+1,j-1. Wenn die Reihe gerade ist, dann sind für eine Kachel bei i,j die Nachbarn i, j-1, i-1,j, i-1,j+1, i,j+1, i+1,j+1 und i+1,j. Dies könnte leicht manuell berechnet werden.

Analysieren wir ein ähnliches Bild für die ungeraden und geraden Spalten eines vertikal ausgerichteten sechseckigen Gitters.

odd and even columns of vertical layout when a middle tile has ij set to 0odd and even columns of vertical layout when a middle tile has ij set to 0odd and even columns of vertical layout when a middle tile has ij set to 0

Wenn wir eine ungerade Spalte haben, hat eine Kachel bei i,j i,j-1, i-1,j-1, i-1,j, i-1,j+1, i,j+1 und i+1,j als Nachbarn. Ähnlich sind für eine gerade Spalte die Nachbarn i+1,j-1, i,j-1, i-1,j, i,j+1, i+1,j+1 und i+1,j.

5. Sechseckiger Minensucher

Mit dem obigen Wissen können wir versuchen, ein sechseckiges Minensucherspiel in den beiden verschiedenen Layouts zu erstellen. Lassen Sie uns die Funktionen eines Minesweeper-Spiels aufschlüsseln.

  1. Es werden N Minen innerhalb des Gitters versteckt sein.
  2. Wenn wir auf ein Plättchen mit einer Mine tippen, ist das Spiel vorbei.
  3. Wenn wir auf eine Kachel tippen, die eine benachbarte Mine hat, wird die Anzahl der Minen direkt um sie herum angezeigt.
  4. Wenn wir auf eine Mine ohne benachbarte Minen tippen, würde dies dazu führen, dass alle verbundenen Kacheln aufgedeckt werden, die keine Minen haben.
  5. Wir können tippen und halten, um eine Kachel als Mine zu markieren.
  6. Das Spiel ist beendet, wenn wir alle Kacheln ohne Minen aufgedeckt haben.

Wir können einfach einen Wert im levelData-Array speichern, um eine Mine anzuzeigen. Die gleiche Methode kann verwendet werden, um den Wert nahegelegener Minen im Array-Index der benachbarten Kacheln zu füllen.

Beim Spielstart werden wir das levelData-Array nach dem Zufallsprinzip mit N Minen füllen. Danach aktualisieren wir die Werte für alle benachbarten Kacheln. Wir werden eine rekursive Methode verwenden, um alle verbundenen leeren Kacheln nacheinander aufzudecken, wenn der Spieler auf eine Kachel tippt, die keine Mine als Nachbar hat.

Füllstandsdaten

Wir müssen ein gut aussehendes sechseckiges Gitter erstellen, wie in der Abbildung unten gezeigt.

horizontal hexagonal minesweeper gridhorizontal hexagonal minesweeper gridhorizontal hexagonal minesweeper grid

Dies kann erreicht werden, indem nur ein Teil des levelData-Arrays angezeigt wird. Wenn wir -1 als Wert für eine nicht verwendbare Kachel und 0 als Wert für eine verwendbare Kachel verwenden, dann sehen unsere levelData zum Erreichen des obigen Ergebnisses so aus.

Beim Durchlaufen des Arrays würden wir nur dann sechseckige Kacheln hinzufügen, wenn levelData den Wert 0 hat. Für die vertikale Ausrichtung können die gleichen levelData verwendet werden, aber wir müssten das Array transponieren. Hier ist eine raffinierte Methode, die dies für Sie tun kann.

Minen hinzufügen und Nachbarn aktualisieren

Standardmäßig haben unsere levelData nur zwei Werte, -1 und 0, von denen wir nur den Bereich mit 0 verwenden würden. Um anzuzeigen, dass eine Kachel eine Mine enthält, können wir den Wert 10 verwenden.

Ein leeres sechseckiges Plättchen kann maximal sechs Minen in seiner Nähe haben, da es sechs benachbarte Plättchen hat. Wir können diese Informationen auch in den levelData speichern, nachdem wir alle Minen hinzugefügt haben. Im Wesentlichen hat ein levelData-Index mit einem Wert von 10 eine Mine, und wenn er Werte von 0 bis 6 enthält, gibt dies die Anzahl der benachbarten Minen an. Wenn nach dem Auffüllen von Minen und dem Aktualisieren von Nachbarn ein Array-Element immer noch 0 ist, zeigt dies an, dass es sich um eine leere Kachel ohne benachbarte Minen handelt.

Wir können die folgenden Methoden für unsere Zwecke verwenden.

Für jede in addMines hinzugefügte Mine inkrementieren wir den Array-Wert, der in allen seinen Nachbarn gespeichert ist. Die Methode getNeighbors gibt keine Kachel zurück, die sich außerhalb unseres effektiven Bereichs befindet oder eine Mine enthält.

Tippe auf Logik

Wenn der Spieler auf eine Kachel tippt, müssen wir das entsprechende Array-Element mithilfe der zuvor erläuterten findHexTile-Methode finden. Wenn der Kachelindex innerhalb unseres effektiven Bereichs liegt, vergleichen wir einfach den Wert am Arrayindex, um herauszufinden, ob es sich um eine Mine oder eine leere Kachel handelt.

Wir verfolgen die Gesamtzahl der leeren Kacheln mit der Variablen blankTiles und die Anzahl der aufgedeckten Kacheln mit revealedTiles. Sobald sie gleich sind, haben wir das Spiel gewonnen.

Wenn wir auf eine Kachel mit einem Array-Wert von 0 tippen, müssen wir die Region mit allen verbundenen leeren Kacheln rekursiv aufdecken. Dies geschieht durch die Funktion recursiveReveal, die die Kachelindizes der angetippten Kachel erhält.

In dieser Funktion finden wir die Nachbarn jeder Kachel und geben den Wert dieser Kachel an, während wir Nachbarkacheln zu einem Array hinzufügen. Wir wiederholen dies mit dem nächsten Element im Array, bis das Array leer ist. Die Rekursion stoppt, wenn wir auf Array-Elemente treffen, die eine Mine enthalten, was dadurch sichergestellt wird, dass getNeighbors keine Kachel mit einer Mine zurückgibt.

Fliesen markieren und freilegen

Sie müssen bemerkt haben, dass ich hexTile.reveal() verwende, was durch die Erstellung eines HexTile-Prototyps ermöglicht wird, der die meisten Attribute unserer sechseckigen Kachel beibehält. Ich verwende die reveal-Funktion, um den Kachelwerttext anzuzeigen und die Farbe der Kachel festzulegen. Ebenso wird die toggleMark-Funktion verwendet, um die Kachel als Mine zu markieren, wenn wir tippen und halten. HexTile hat auch ein revealed Attribut, das verfolgt, ob es getappt und aufgedeckt wird oder nicht.

Schauen Sie sich unten das sechseckige Minensuchgerät mit horizontaler Ausrichtung an. Tippe, um Kacheln aufzudecken, und tippe und halte, um Minen zu markieren. Noch ist kein Spiel vorbei, aber wenn du einen Wert von 10 entdeckst, dann ist es hasta la vista baby!

Änderungen für die vertikale Version

Da ich für beide Ausrichtungen dasselbe Bild einer sechseckigen Kachel verwende, drehe ich das Sprite für die vertikale Ausrichtung. Der folgende Code im HexTile-Prototyp tut dies.

Die Minesweeper-Logik bleibt für das vertikal ausgerichtete hexagonale Gitter gleich, mit dem Unterschied für findHextile und getNeighbors-Logik, die nun den Ausrichtungsunterschied berücksichtigen müssen. Wie bereits erwähnt, müssen wir auch die Transponierung des Level-Arrays mit entsprechender Layout-Schleife verwenden.

Schauen Sie sich die vertikale Version unten an.

Der Rest des Quellcodes ist einfach und unkompliziert. Ich möchte, dass Sie versuchen, die fehlenden Neustart-, Spielgewinn- und Game-Over-Funktionen hinzuzufügen.

Abschluss

Dieser Ansatz eines auf sechseckigen Kacheln basierenden Spiels unter Verwendung eines zweidimensionalen Arrays ist eher ein Ansatz für Laien. Interessantere und funktionellere Ansätze beinhalten die Änderung des Koordinatensystems in verschiedene Typen unter Verwendung von Gleichungen.

Die wichtigsten sind axiale Koordinaten und kubische Koordinaten. Es wird eine Folge-Tutorialreihe geben, in der diese Ansätze diskutiert werden. In der Zwischenzeit würde ich empfehlen, Amits unglaublich gründlichen Artikel über hexagonale Gitter zu lesen.

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.