Birthday Sale! Up to 40% off unlimited courses & creative assets Birthday Sale! Save up to 40%!
Advertisement
  1. Game Development
  2. Programming
Gamedevelopment

Wie kann man Puzzleformen mit Bitmasken anpassen?

by
Difficulty:IntermediateLength:LongLanguages:

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

In diesem Tutorial werde ich Ihnen zeigen, wie Sie eine Kachelplatte analysieren, durchlaufen und Übereinstimmungen finden. Wir werden ein Spiel erstellen, in dem Sie Linien miteinander verbinden müssen, um vollständig geschlossene Pfade ohne offene Enden zu bilden. Zur Vereinfachung verwenden wir bitmasking als Teil unseres Algorithmus, indem wir jeder Kachel (plus ihrer Drehung) eine eigene Bitmaskennummer zuweisen. Machen Sie sich keine Sorgen, wenn Sie nicht wissen, was Bitmasking ist. Es ist eigentlich sehr einfach!


Spielen Sie die Demo

Ich werde das Projekt in C# mit Unity mit dem Futile-Framework erstellen, aber der Code kann mit wenigen Änderungen auf so ziemlich jedes 2D-Framework angewendet werden. Hier ist das Github-Repo mit dem gesamten Unity-Projekt. Und unten ist eine spielbare Demo des Spiels, das wir machen werden:


Klicken Sie auf die Pfeile, um Zeilen und Spalten zu verschieben. Versuche geschlossene Formen zu machen.

Über Match-3 hinausgehen

Als ich anfing, Polymer zu erstellen, wollte ich etwas anderes als ein Match-3-Spiel erstellen. Mein interner Spitzname dafür war ein "Match-Any"-Spiel. Match-3-Puzzlespiele gibt es überall. Während sie sicherlich Spaß machen können, kann ein Grund, warum sie so häufig sind, sein, dass der Algorithmus zum Finden einer Übereinstimmung von drei Kacheln ziemlich einfach ist.

Ich wollte in der Lage sein, mehrere Kacheln zu finden, die sich in Reihen und Spalten hinein- und herausweben konnten und sich über das Brett schlängelten. Nicht nur das, ich wollte auch kein einfaches Farbanpassungsspiel. Ich wollte, dass die Übereinstimmungen auf bestimmten Seiten der Kacheln basieren (zum Beispiel konnte eine Form nur mit anderen Formen auf der linken und rechten Seite verbunden werden, aber nicht oben und unten.) Das stellte sich als viel komplexer heraus als nur ein normaler Match-3-Algorithmus.

Dieses Tutorial ist in drei Abschnitte unterteilt: The Tile, The Match Group und The Game Board. In diesem Tutorial werde ich versuchen, so viel vergeblichen Code wie möglich zu vermeiden. Wenn Sie das vergebliche Material sehen möchten, schauen Sie sich den Quellcode an. Außerdem werde ich in diesem Beitrag nicht jede Methode und Variable zeigen. Nur die wichtigsten. Wenn Sie also glauben, dass etwas fehlt, schauen Sie sich noch einmal den Quellcode an.

Was ist eine Bitmaske?

Das Wort 'bitmaske' bezieht sich auf die Art und Weise, wie Sie eine Reihe von Wahr/Falsch-Werten in einer einzelnen numerischen Variablen speichern können. Da Zahlen in Binärform durch Einsen und Nullen dargestellt werden, können Sie durch Ändern der Zahl Werte ein- oder ausschalten, indem Sie umschalten, ob ein Bit 1 oder 0 ist.

Weitere Informationen finden Sie in diesem Artikel zu bitweisen Operatoren und in diesem Artikel zu Binärzahlen.


Die Fliese

Unsere erste Klasse heißt LineTile. Definieren wir vor Beginn des Unterrichts jede Art von der Fliese.

So sehen die Teile aus:

thePieces

Da wir nur Drehungen von 90 Grad zulassen, erstellen wir als nächstes eine enum für die Drehung.

Als nächstes folgt eine struct namens TileIndex, die im Grunde die gleiche wie ein Vector2 ist, außer mit Ints anstelle von Floats. Es wird verwendet, um zu verfolgen, wo sich ein Plättchen auf dem Spielbrett befindet.

Lassen Sie uns abschließend die drei Arten von Verbindungen zwischen zwei Fliesen definieren.

Definieren Sie als Nächstes innerhalb der Klasse selbst eine Bitmaske für jede Seite einer generischen Fliese.

tileBitmask

Definieren Sie dann die Instanzvariablen, die jede Fliese haben wird.

Erstellen Sie für den Konstruktor das Sprite und richten Sie es mit der richtigen Drehung ein. Es gibt hier einen vergeblichen Code, der jedoch sehr leicht zu verstehen sein sollte.

Nun einer der wichtigsten Teile. Wir weisen jede Fliese zusammen mit ihrer Drehung eine Bitmaske zu, die davon abhängt, welche ihrer Seiten fest und welche offen sind.

allPieces

Unsere Fliesen sind eingerichtet und wir sind bereit, sie zusammenzubringen!


Die Matchgruppe

Übereinstimmungsgruppen sind genau das: Gruppen von Kacheln, die übereinstimmen (oder nicht). Sie können auf jedem Plättchen in einer Matchgruppe beginnen und über seine Verbindungen jedes andere Plättchen erreichen. Alle seine Fliesen sind verbunden. Jede der verschiedenen Farben zeigt eine andere Übereinstimmungsgruppe an. Die einzige, die abgeschlossen ist, ist die blaue in der Mitte - sie hat keine ungültigen Verbindungen.

matchGroups

Die Matchgruppenklasse selbst ist eigentlich extrem einfach. Es ist im Grunde nur eine Sammlung von Fliesen mit ein paar Hilfsfunktionen. Hier ist es:


Das Spiel

Das ist bei weitem der komplizierteste Teil dieses Prozesses. Wir müssen das gesamte Board analysieren, es in seine einzelnen Match-Gruppen aufteilen und dann feststellen, welche, falls vorhanden, vollständig geschlossen sind. Ich werde diese Klasse BitmaskPuzzleGame nennen, da es die Hauptklasse ist, die die Spielelogik umfasst.

Bevor wir jedoch mit der Implementierung beginnen, definieren wir einige Dinge. Erstens ist eine einfache enum, dass die Pfeile basierend auf der Richtung zugewiesen werden, in die sie zeigen:

Als nächstes folgt eine struct, die von einem Pfeil gesendet wird, der gedrückt wird, damit wir bestimmen können, wo sie sich auf dem Brett befindet und in welche Richtung sie zeigt:

Definieren Sie als Nächstes innerhalb der Klasse die benötigten Instanzvariablen:

Hier ist eine Funktion, die eine Fliese nimmt und alle umgebenden Fliesen zurückgibt (die oben, unten, links und rechts davon):

Nun zwei Methoden, die alle Fliesen in einer Spalte oder Zeile zurückgeben, damit wir sie verschieben können:

Nun zwei Funktionen, die eine Spalte oder Reihe von Fliesen tatsächlich in eine bestimmte Richtung verschieben. Wenn sich eine Fliese von einer Kante löst, wird sie auf die andere Seite geschleift. Beispielsweise führt eine Rechtsverschiebung in einer Reihe von Nub, Cross, Line zu einer Reihe von Line, Nub, Cross.

Wenn wir auf einen Pfeil klicken (d. h. Wenn die Pfeiltaste losgelassen wird), müssen wir bestimmen, welche Zeile oder Spalte in welche Richtung verschoben werden soll.

Die nächsten beiden Methoden sind die wichtigsten im Spiel. Der erste nimmt zwei Kacheln und bestimmt, welche Art von Verbindung sie haben. Die Verbindung basiert auf der ersten Kacheleingabe in die Methode (baseTile genannt). Dies ist eine wichtige Unterscheidung. Die baseTile könnte eine ValidWithOpenSide-Verbindung mit der otherTile haben, aber wenn Sie sie in umgekehrter Reihenfolge eingeben, könnte sie Invalid zurückgeben.

matchExplainer

Schließlich UpdateMatches. Das ist die wichtigste Methode von allen. Das ist derjenige, der durch das Brett geht, alle Teile analysiert, bestimmt, welche Verbindungen miteinander bestehen und welche Übereinstimmungsgruppen vollständig geschlossen sind. Alles wird in den Kommentaren erklärt.

Wir haben nur noch die HandleUpdate-Funktion! Aktualisieren Sie in jedem Frame die Übereinstimmungsgruppen, wenn sie aktualisiert werden müssen (d. h. MatchGroupsAreDirty == true), und legen Sie ihre Farben fest.

So würde der Algorithmus aussehen, wenn jeder Schritt animiert wäre:

boardAnimation

Und das ist es! Während ein Teil des Codes in diesem Artikel spezifisch für Futile ist, sollte es ziemlich klar sein, wie er auf eine andere Sprache oder Engine erweitert werden kann. Und um es noch einmal zu wiederholen, in diesem Beitrag fehlen viele unwesentliche Dinge. Bitte schauen Sie sich den Quellcode an, um zu sehen, wie alles zusammenarbeitet!

Advertisement
Advertisement
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.