In diesem Tutorial gehe ich Ihnen durch, wie Sie ein Brett mit Kacheln 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. Um die Sache zu vereinfachen, verwenden wir Bitmasking als Teil unseres Algorithmus, indem wir jeder Kachel (plus ihrer Rotation) ihre ganz eigene Bitmask-Nummer zuweisen. Machen Sie sich keine Sorgen, wenn Sie nicht wissen, was Bitmasking ist. Es ist eigentlich sehr einfach!
zusammenhängende PostsIch werde das Projekt in C # mit Unity mit dem Futile-Framework erstellen, aber der Code kann mit wenigen Änderungen für so ziemlich jedes 2D-Framework verwendet werden. Hier ist das Github-Repo mit dem gesamten Unity-Projekt. Und unten ist eine spielbare Demo des Spiels, das wir machen werden:
Als ich mit dem Erstellen von Polymer begann, wollte ich etwas anderes als ein Match-3-Spiel erstellen. Mein interner Spitzname war ein "Match-Any" -Spiel. Match-3-Puzzle-Spiele gibt es überall. Obwohl sie sicherlich Spaß machen können, kann ein Grund dafür sein, dass der Algorithmus zum Finden von drei Kacheln recht einfach ist.
Ich wollte in der Lage sein, mehrere Kacheln zusammenzubringen, die sich in Reihen und Spalten einfügen und aus ihnen heraus winden können, um sich über das Brett zu winden. Nicht nur das, aber ich wollte kein einfaches Farbanpassungsspiel. Ich wollte, dass die Übereinstimmungen auf bestimmten Seiten der Kacheln basieren (eine Form konnte sich beispielsweise nur mit anderen Formen auf der linken und rechten Seite verbinden, nicht jedoch mit der Ober- und Unterseite.) Dies erwies sich als viel komplexer als nur ein normaler Match-3-Algorithmus.
Dieses Tutorial wird in drei Abschnitte unterteilt: The Tile, The Match Group und The Game Board. In diesem Tutorial werde ich versuchen, so viel Code wie möglich für Futile zu vermeiden. Wenn Sie das Futile-spezifische Zeug sehen wollen, schauen Sie sich den Quellcode an. Ich werde auch nicht alle Methoden und Variablen in diesem Beitrag zeigen. Nur die wichtigsten. Wenn Sie der Meinung sind, dass etwas fehlt, schauen Sie sich den Quellcode an.
Das Wort "Bitmaske" bezieht sich auf die Art und Weise, wie Sie eine Reihe von True / False-Werten in einer einzelnen numerischen Variablen speichern können. Da Zahlen in Binärdarstellung durch Einsen und Nullen dargestellt werden, können Sie durch Ändern der Zahl Werte ein- oder ausschalten, indem Sie auswählen, ob ein Bit 1 oder 0 ist.
Weitere Informationen finden Sie in diesem Artikel zu bitweisen Operatoren und in diesem Artikel zu Binärzahlen.
Unsere erste Klasse heißt LineTile. Vor dem Beginn der Klasse definieren wir jede Art von Kacheln.
// Die verschiedenen Fliesentypen: public enum LineTileType Nub, Line, Corner, Threeway, Cross, MAX
So sehen die Stücke aus:
Da wir nur Drehungen von 90 Grad zulassen, machen wir eine enum
zur Drehung.
// Ich benutze dies anstelle von exakten Graden, da die // Kacheln nur vier verschiedene Rotationen haben sollten: public enum RotationType Rotation0, Rotation90, Rotation180, Rotation270, MAX
Weiter ist ein struct
namens TileIndex
, Das ist im Grunde dasselbe wie ein Vector2
, außer mit Ints statt Schwebekörpern. Es wird verwendet, um zu verfolgen, wo sich ein Plättchen auf dem Spielplan befindet.
public struct TileIndex public int xIndex; public int yIndex; public TileIndex (int xIndex, int yIndex) this.xIndex = xIndex; this.yIndex = yIndex;
Schließlich definieren wir die drei Arten von Verbindungen zwischen zwei Kacheln.
public enum TileConnectionType // Eine Nichtübereinstimmung. Ungültig, // Die Kacheln verbinden sich nicht direkt, // aber nicht aufgrund einer nicht übereinstimmenden Kante. ValidWithOpenSide, // Die Kacheln verbinden sich direkt. ValidWithSolidMatch
Als Nächstes definieren Sie in der Klasse eine Bitmaske auf jeder Seite eines generischen Kachels.
// Hier sind die Bits, die ich jeder Seite der Kachel zugewiesen habe: // ===== 1 ===== // | | // | | // 8 2 // | | // | | // ===== 4 ===== // 1 == 0001 in binär // 2 == 0010 in binär // 4 == 0100 in binär // 8 == 1000 in binärer public const int kBitmaskNone = 0; public const int kBitmaskTop = 1; public const int kBitmaskRight = 2; public const int kBitmaskBottom = 4; public const int kBitmaskLeft = 8;
Definieren Sie dann die Instanzvariablen, die jede Kachel haben wird.
// Die Sprite-Darstellung der Kachel: public FSprite sprite; // Der Typ der Kachel: public LineTileType lineTileType get; private set; // Die Drehung der Kachel: public RotationType rotationType get; private set; // Die Bitmaske, die die Kachel mit ihrer Rotation darstellt: public int bitmask get; private set; // Die Position des Tiles auf der Tafel: public TileIndex tileIndex = new TileIndex ();
Erstellen Sie für den Konstruktor das Sprite und richten Sie es mit der richtigen Rotation ein. Es gibt hier einen Code, der für Futile spezifisch ist, aber er sollte sehr leicht zu verstehen sein.
public LineTile (LineTileType lineTileType, RotationType rotationType) this.lineTileType = lineTileType; this.rotationType = rotationType; // Sprite einrichten: switch (lineTileType) case LineTileType.Nub: sprite = new FSprite ("lineTileNub"); brechen; case LineTileType.Line: sprite = new FSprite ("lineTileLine"); brechen; case LineTileType.Corner: sprite = new FSprite ("lineTileCorner"); brechen; case LineTileType.Threeway: sprite = new FSprite ("lineTileThreeway"); brechen; case LineTileType.Cross: sprite = new FSprite ("lineTileCross"); brechen; default: Neue FutileException auslösen ("ungültiger Kacheltyp"); AddChild (Sprite); // Sprite-Rotation einrichten: switch (rotationType) case RotationType.rotation0: sprite.rotation = 0; brechen; case RotationType.rotation90: sprite.rotation = 90; brechen; case RotationType.rotation180: sprite.rotation = 180; brechen; case RotationType.rotation270: sprite.rotation = 270; brechen; default: Neue FutileException auslösen ("ungültiger Rotationstyp");
Nun einer der wichtigsten Teile. Wir ordnen jeder Kachel zusammen mit ihrer Drehung eine Bitmaske zu, die durch die feste Seite und die offene Seite bestimmt wird.
// Bitmaske einrichten, indem Sie bitweise ODER mit jeder Seite ausführen, die in der Form enthalten ist. // Eine Kachel mit allen vier Seiten (z. B. Kreuzkachel) wäre beispielsweise // 1 | 2 | 4 | 8 = 15, was 0001 | entspricht 0010 | 0100 | 1000 = 1111 binär. if (lineTileType == LineTileType.Nub) if (rotationType == RotationType.Rotation0) Bitmaske = kBitmaskTop; if (rotationType == RotationType.Rotation90) Bitmaske = kBitmaskRight; if (rotationType == RotationType.Rotation180) Bitmaske = kBitmaskBottom; if (rotationType == RotationType.Rotation270) Bitmaske = kBitmaskLeft; if (lineTileType == LineTileType.Line) if (rotationType == RotationType.Rotation0 || rotationType == RotationType.Rotation180) Bitmaske = kBitmaskTop | kBitmaskBottom; if (rotationType == RotationType.Rotation90 || rotationType == RotationType.Rotation270) Bitmaske = kBitmaskRight | kBitmaskLeft; if (lineTileType == LineTileType.Corner) if (rotationType == RotationType.Rotation0) Bitmaske = kBitmaskTop | kBitmaskRight; if (rotationType == RotationType.Rotation90) Bitmaske = kBitmaskRight | kBitmaskBottom; if (rotationType == RotationType.Rotation180) Bitmaske = kBitmaskBottom | kBitmaskLeft; if (rotationType == RotationType.Rotation270) Bitmaske = kBitmaskLeft | kBitmaskTop; if (lineTileType == LineTileType.Threeway) if (rotationType == RotationType.Rotation0) Bitmaske = kBitmaskTop | kBitmaskRight | kBitmaskBottom; if (rotationType == RotationType.Rotation90) Bitmaske = kBitmaskRight | kBitmaskBottom | kBitmaskLeft; if (rotationType == RotationType.Rotation180) Bitmaske = kBitmaskBottom | kBitmaskLeft | kBitmaskTop; if (rotationType == RotationType.Rotation270) Bitmaske = kBitmaskLeft | kBitmaskTop | kBitmaskRight; if (lineTileType == LineTileType.Cross) Bitmaske = kBitmaskTop | kBitmaskRight | kBitmaskBottom | kBitmaskLeft;
Unsere Fliesen sind eingerichtet und wir können loslegen!
Übereinstimmungsgruppen sind genau das: Gruppen von Kacheln, die übereinstimmen (oder nicht). Sie können mit jeder Kachel in einer Übereinstimmungsgruppe beginnen und über ihre Verbindungen zu jeder anderen Kachel gelangen. Alle seine Kacheln sind miteinander 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.
Die Match-Gruppenklasse selbst ist eigentlich extrem einfach. Es ist im Grunde nur eine Sammlung von Kacheln mit einigen Hilfsfunktionen. Hier ist es:
öffentliche Klasse MatchGroup öffentliche ListeFliesen; public bool isClosed = true; public MatchGroup () tiles = neue Liste (); public void SetTileColor (Color color) foreach (Kacheln mit Zeilen in Fliesen) tile.sprite.color = color; public void Destroy () tile.Clear ();
Dies ist bei weitem der komplizierteste Teil dieses Prozesses. Wir müssen das gesamte Board analysieren, es in seine einzelnen Matchgruppen aufteilen und dann bestimmen, welche, wenn überhaupt, vollständig geschlossen sind. Ich werde diese Klasse anrufen BitmaskPuzzleGame
, denn es ist die Hauptklasse, die die Spielelogik umfasst.
Bevor wir uns jedoch mit der Implementierung beschäftigen, wollen wir ein paar Dinge definieren. Zuerst ist es einfach enum
dass die Pfeile basierend auf der zugewiesenen Richtung zugewiesen werden:
// Um zu ermitteln, welcher Pfeil gedrückt wurde: public enum Direction Auf, Rechts, Ab, Links
Weiter ist ein struct
das wird von einem Pfeil gesendet, der gedrückt wird, damit wir feststellen können, wo es sich in der Tafel befindet und in welche Richtung es zeigt:
// Wenn ein Pfeil gedrückt wird, enthält er diese Daten, um herauszufinden, was mit der Tafel zu tun ist: public struct ArrowData public Direction direction; public int index; public ArrowData (Richtungsrichtung, Int-Index) this.direction = direction; this.index = index;
Als Nächstes definieren Sie in der Klasse die Instanzvariablen, die wir benötigen:
// Enthält alle Kacheln der Karte: public LineTile [] [] tileMap; // Enthält alle Gruppen verbundener Kacheln: public ListmatchGroups = neue Liste (); // Wenn eine Zeile / Spalte verschoben wird, wird dies auf true festgelegt, sodass HandleUpdate die Aktualisierung durchführen kann: private bool matchGroupsAreDirty = true; // Wie viele Plättchen breit ist das Board: private int tileMapWidth; // Wie viele Plättchen hat das Board: private int tileMapHeight;
Hier ist eine Funktion, die ein Plättchen nimmt und alle umgebenden Plättchen (die darüber, unten, links und rechts davon) zurückgibt:
// Hilfsmethode, um alle Kacheln zu erhalten, die sich oberhalb / unterhalb / rechts / links von einer bestimmten Kachel befinden: private ListeGetTilesSurroundingTile (Kachel LineTile) Liste umlaufende Felder = neue Liste (); int xIndex = tile.tileIndex.xIndex; int yIndex = tile.tileIndex.yIndex; if (xIndex> 0) umgebendeTiles.Add (tileMap [xIndex - 1] [yIndex]); if (xIndex < tileMapWidth - 1) surroundingTiles.Add(tileMap[xIndex + 1][yIndex]); if (yIndex > 0) umgebendeTiles.Add (tileMap [xIndex] [yIndex - 1]); if (yIndex < tileMapHeight - 1) surroundingTiles.Add(tileMap[xIndex][yIndex + 1]); return surroundingTiles;
Nun zwei Methoden, die alle Kacheln in einer Spalte oder Zeile zurückgeben, damit wir sie verschieben können:
// Hilfsmethode, um alle Kacheln in einer bestimmten Spalte abzurufen: private LineTile [] GetColumnTiles (int columnIndex) if (columnIndex < 0 || columnIndex >= tileMapWidth) Neue FutileException auslösen ("ungültige Spalte:" + columnIndex); LineTile [] columnTiles = neues LineTile [tileMapHeight]; für (int j = 0; j < tileMapHeight; j++) columnTiles[j] = tileMap[columnIndex][j]; return columnTiles; // Helper method to get all the tiles in a specific row: private LineTile[] GetRowTiles(int rowIndex) if (rowIndex < 0 || rowIndex >= tileMapHeight) Neue FutileException auslösen ("ungültige Spalte:" + rowIndex); LineTile [] rowTiles = neue LineTile [tileMapWidth]; für (int i = 0; i < tileMapWidth; i++) rowTiles[i] = tileMap[i][rowIndex]; return rowTiles;
Jetzt zwei Funktionen, die eine Spalte oder Reihe von Kacheln tatsächlich in eine bestimmte Richtung verschieben. Wenn eine Kachel von einer Kante verschoben wird, wird sie auf die andere Seite verschoben. Zum Beispiel führt eine Rechtsverschiebung in einer Reihe von Nub, Cross, Line zu einer Reihe von Line, Nub, Cross.
// Verschiebe die Kacheln in einer Spalte entweder nach oben oder nach unten (mit Umbruch). private void ShiftColumnInDirection (int columnIndex, Direction dir) LineTile [] currentColumnArrangement = GetColumnTiles (columnIndex); int nextIndex; // Verschiebe die Kacheln so, dass sie sich im tileMap-Array an den richtigen Stellen befinden. if (dir == Direction.Up) für (int j = 0; j < tileMapHeight; j++) nextIndex = (j + 1) % tileMapHeight; tileMap[columnIndex][nextIndex] = currentColumnArrangement[j]; tileMap[columnIndex][nextIndex].tileIndex = new TileIndex(columnIndex, nextIndex); else if (dir == Direction.Down) for (int j = 0; j < tileMapHeight; j++) nextIndex = j - 1; if (nextIndex < 0) nextIndex += tileMapHeight; tileMap[columnIndex][nextIndex] = currentColumnArrangement[j]; tileMap[columnIndex][nextIndex].tileIndex = new TileIndex(columnIndex, nextIndex); else throw new FutileException("can't shift column in direction: " + dir.ToString()); // Once the tileMap array is set up, actually visually move the tiles to their correct spots. for (int j = 0; j < tileMapHeight; j++) tileMap[columnIndex][j].y = (j + 0.5f) * tileSize; matchGroupsAreDirty = true; // Shift the tiles in a row either right or left one (with wrapping). private void ShiftRowInDirection(int rowIndex, Direction dir) LineTile[] currentRowArrangement = GetRowTiles(rowIndex); int nextIndex; // Move the tiles so they are in the correct spots in the tileMap array. if (dir == Direction.Right) for (int i = 0; i < tileMapWidth; i++) nextIndex = (i + 1) % tileMapWidth; tileMap[nextIndex][rowIndex] = currentRowArrangement[i]; tileMap[nextIndex][rowIndex].tileIndex = new TileIndex(nextIndex, rowIndex); else if (dir == Direction.Left) for (int i = 0; i < tileMapWidth; i++) nextIndex = i - 1; if (nextIndex < 0) nextIndex += tileMapWidth; tileMap[nextIndex][rowIndex] = currentRowArrangement[i]; tileMap[nextIndex][rowIndex].tileIndex = new TileIndex(nextIndex, rowIndex); else throw new FutileException("can't shift row in direction: " + dir.ToString()); // Once the tileMap array is set up, actually visually move the tiles to their correct spots. for (int i = 0; i < tileMapWidth; i++) tileMap[i][rowIndex].x = (i + 0.5f) * tileSize; matchGroupsAreDirty = true;
Wenn wir auf einen Pfeil klicken (d. H., Wenn die Pfeilschaltfläche losgelassen wird), müssen wir bestimmen, welche Zeile oder Spalte in welche Richtung verschoben werden soll.
// Wenn ein Pfeil gedrückt und losgelassen wird, verschieben Sie eine Spalte nach oben / unten oder eine Zeile nach rechts / links. public void ArrowButtonReleased (FButton-Schaltfläche) ArrowData arrowData = (ArrowData) button.data; if (arrowData.direction == Direction.Up || arrowData.direction == Direction.Down) ShiftColumnInDirection (arrowData.index, arrowData.direction); else if (arrowData.direction == Direction.Right || arrowData.direction == Direction.Left) ShiftRowInDirection (arrowData.index, arrowData.direction);
Die nächsten zwei Methoden sind die wichtigsten im Spiel. Der erste nimmt zwei Kacheln und bestimmt, welche Art von Verbindung sie haben. Es basiert auf der Verbindung zuerst Flieseingabe in die Methode (aufgerufen baseTile
). Dies ist eine wichtige Unterscheidung. Das baseTile
könnte eine haben ValidWithOpenSide
Verbindung mit der otherTile
, Wenn Sie sie jedoch in umgekehrter Reihenfolge eingeben, kann es zu einer Rückkehr kommen Ungültig
.
// Es gibt drei Arten von Verbindungen, die zwei Kacheln haben können: // 1. ValidWithSolidMatch - Dies bedeutet, dass die Kacheln genau aufeinander abgestimmt sind und ihre festen Seiten miteinander verbunden sind. // 2. ValidWithOpenSide - Dies bedeutet, dass das baseTile eine offene Seite hat, die die andere Kachel berührt, sodass es egal ist, was die andere Kachel ist. // 3. Ungültig - Dies bedeutet, dass die feste Seite des baseTile mit der offenen Seite des anderen Felds übereinstimmt, was zu einer Nichtübereinstimmung führt. private TileConnectionType TileConnectionTypeBetweenTiles (LineTile baseTile, LineTile otherTile) int baseTileBitmaskSide = baseTile.bitmask; // Die Bitmaske für die bestimmte baseTile-Seite, die die andere Kachel berührt. int otherTileBitmaskSide = otherTile.bitmask; // Die Bitmaske für die andere otherTile-Seite, die die Basisfliese berührt. // Abhängig davon, auf welcher Seite der Grundplatte sich die andere Platte befindet, bitweise und jede Seite zusammen. mit // die bitweise Konstante für diese einzelne Seite. Wenn das Ergebnis 0 ist, ist die Seite offen. Ansonsten // ist die Seite solide. if (otherTile.tileIndex.yIndex < baseTile.tileIndex.yIndex) baseTileBitmaskSide &= LineTile.kBitmaskBottom; otherTileBitmaskSide &= LineTile.kBitmaskTop; else if (otherTile.tileIndex.yIndex > baseTile.tileIndex.yIndex) baseTileBitmaskSide & = LineTile.kBitmaskTop; otherTileBitmaskSide & = LineTile.kBitmaskBottom; else if (otherTile.tileIndex.xIndex.) < baseTile.tileIndex.xIndex) baseTileBitmaskSide &= LineTile.kBitmaskLeft; otherTileBitmaskSide &= LineTile.kBitmaskRight; else if (otherTile.tileIndex.xIndex > baseTile.tileIndex.xIndex) baseTileBitmaskSide & = LineTile.kBitmaskRight; otherTileBitmaskSide & = LineTile.kBitmaskLeft; if (baseTileBitmaskSide == 0) return TileConnectionType.ValidWithOpenSide; // baseTile-Seite, die otherTile berührt, ist geöffnet. else if (otherTileBitmaskSide! = 0) gibt TileConnectionType.ValidWithSolidMatch zurück. // baseTile-Seite und otherTile-Seite sind solide und abgestimmt. else return TileConnectionType.Invalid; // baseTile-Seite ist solide, andere Tile-Seite ist jedoch offen. Nicht übereinstimmen!
Endlich, UpdateMatches
. Dies ist die wichtigste Methode von allen. Dies ist diejenige, die durch die Tafel geht, analysiert alle Teile und bestimmt, welche Verbindung miteinander besteht und welche Übereinstimmungsgruppen vollständig geschlossen sind. Alles wird in den Kommentaren erklärt.
// Durchsuchen Sie das Board und analysieren Sie alle Kacheln und suchen Sie nach Übereinstimmungen: private void UpdateMatches () // Übereinstimmungsgruppen werden aktualisiert, sodass sie nicht länger fehlerhaft sind: matchGroupsAreDirty = false; // Da das Verschieben von Spalten und Zeilen alles durcheinander bringen kann, müssen wir die alten Übereinstimmungsgruppen loswerden und neu beginnen. // Denken Sie daran, es gibt wahrscheinlich eine Möglichkeit, den Algorithmus zu verwenden, bei dem wir nicht alle Übereinstimmungen loswerden müssen und // jedes Mal von vorne beginnen müssen (z. B. aktualisieren Sie nur die durch eine Verschiebung unterbrochenen Übereinstimmungen), aber das ist der Fall kann später kommen, wenn // Sie die Leistung verbessern müssen. foreach (MatchGroup matchGroup in matchGroups) matchGroup.Destroy (); matchGroups.Clear (); // Wir beginnen mit der Analyse der Karte von der linken unteren Kachel. Das aktuelle Basis-Kachel ist das //, von dem aus wir gerade starten und Matchgruppen daraus erstellen. LineTile currentBaseTile = tileMap [0] [0]; ListetileSurrounder; // Variable, in der umliegende Fliesen verschiedener Basisfliesen gespeichert werden. Liste checkedTiles = neue Liste (); // Wir speichern hier Basisplättchen, sobald sie analysiert wurden, damit wir sie nicht erneut analysieren. MatchGroup currentMatchGroup; // Die Übereinstimmungsgruppe, die wir analysieren, enthält die aktuelle Grundkachel. // Durchlaufe kontinuierlich das Spielfeld und mache Matchgruppen, bis keine Kacheln mehr vorhanden sind, aus denen die Matchgruppen gebildet werden könnten. while (currentBaseTile! = null) // Erstellen Sie eine neue Übereinstimmungsgruppe, fügen Sie die aktuelle Basis-Kachel als erste Kachel hinzu. currentMatchGroup = new MatchGroup (); currentMatchGroup.tiles.Add (currentBaseTile); // Schleife durch die Kacheln, beginnend mit der aktuellen Basiskachel, analysiere ihre Verbindungen, suche eine neue Basiskachel, // und erneut eine Schleife usw., bis Sie keine möglichen Verbindungen mehr zu den Kacheln in der Übereinstimmungsgruppe bool stillWorkingOnMatchGroup finden = wahr; while (stillWorkingOnMatchGroup) // Füllen Sie die tileSurrounders-Liste mit allen Kacheln, die die aktuelle Basisfliese umgeben, auf: tileSurrounders = GetTilesSurroundingTile (currentBaseTile); // Durchlaufe alle umgebenden Kacheln und überprüfe, ob ihre festen Seiten mit den festen Seiten der Basisfliesen ausgerichtet sind: foreach (LineTile umlaufende Kachel in tileSurrounders) TileConnectionType connectionType = TileConnectionTypeBetweenTiles (currentBaseTile, umlaufende Tile); // Wenn es eine feste Übereinstimmung gibt, fügen Sie den Surrounder zur Übereinstimmungsgruppe hinzu. // Wenn es keine Übereinstimmung gibt, ist die Matchgruppe keine perfekte "geschlossene" Matchgruppe. // Wenn es aufgrund einer offenen Seite der Basisplatte keine Übereinstimmung gibt, ist das eigentlich nicht wichtig, // da keine feste Seite abgeschnitten wird (dies wird TileConnectionType.ValidWithOpenSide genannt). if (connectionType == TileConnectionType.ValidWithSolidMatch) currentMatchGroup.tiles.Add (umgebendeDatei); else if (TileConnectionTypeBetweenTiles (currentBaseTile, umgebendesTile) == TileConnectionType.Invalid) currentMatchGroup.isClosed = false; // Wenn die Basisfliese eine geschlossene / durchgehende Seite hat, die die Kante der Tafel berührt, kann die Übereinstimmungsgruppe nicht geschlossen werden. if (((((currentBaseTile.bitmask & LineTile.kBitmaskTop)! = 0 && currentBaseTile.tileIndex.yIndex == tileMapHeight - 1) || ((currentBaseTile.bitmask & LineTile.kBitmaskRight)! = 0 && currentBaseText tileMapWidth - 1) || ((currentBaseTile.bitmask & LineTile.kBitmaskBottom)! = 0 && currentBaseTile.tileIndex.yIndex == 0) || ((currentBaseTile.bitmask & LineTile.kBitmaskLeft)! = 0 & amp; == 0)) currentMatchGroup.isClosed = false; // Füge unsere Basisfliese einem Array hinzu, damit wir sie später nicht noch einmal überprüfen können: if (! CheckedTiles.Contains (currentBaseTile)) checkedTiles.Add (currentBaseTile); // Suchen Sie nach einer neuen Basis-Kachel, die wir der Übereinstimmungsgruppe hinzugefügt haben, diese jedoch noch nicht analysiert haben: for (int i = 0; i < currentMatchGroup.tiles.Count; i++) LineTile tile = currentMatchGroup.tiles[i]; // If the checkedTiles array has the tile in it already, check to see if we're on the last // tile in the match group. If we are, then there are no more base tile possibilities so we are // done with the match group. If checkedTiles DOESN'T have a tile in the array, it means // that tile is in the match group but hasn't been analyzed yet, so we need to set it as // the next base tile. if (checkedTiles.Contains(tile)) if (i == currentMatchGroup.tiles.Count - 1) stillWorkingOnMatchGroup = false; matchGroups.Add(currentMatchGroup); else currentBaseTile = tile; break; // We're done with a match group, so now we need to find a new un-analyzed tile that's // not in any match groups to start a new one from. So we'll set currentBaseTile to // null then see if we can find a new one: currentBaseTile = null; for (int i = 0; i < tileMapWidth; i++) for (int j = 0; j < tileMapHeight; j++) LineTile newTile = tileMap[i][j]; if (!TileIsAlreadyInMatchGroup(newTile)) currentBaseTile = newTile; break; if (currentBaseTile != null) break;
Wir haben nur noch die HandleUpdate
Funktion! Aktualisieren Sie bei jedem Frame die Übereinstimmungsgruppen, wenn sie aktualisiert werden müssen (d. H. matchGroupsAreDirty == wahr
), und legen Sie ihre Farben fest.
public void HandleUpdate () if (matchGroupsAreDirty) UpdateMatches ();
So würde der Algorithmus aussehen, wenn jeder Schritt animiert wäre:
Und das ist es! Ein Teil des Codes in diesem Abschnitt ist spezifisch für Futile. Es sollte jedoch klar sein, wie er auf eine andere Sprache oder Engine erweitert werden kann. Und um es noch einmal zu sagen, es fehlt in diesem Beitrag eine Menge nicht wesentlicher Dinge. Bitte sehen Sie sich den Quellcode an, um zu sehen, wie alles zusammen funktioniert!