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

Unity 2D Tile-basiertes 'Sokoban'-Spiel

by
Difficulty:BeginnerLength:LongLanguages:

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

Final product image
What You'll Be Creating

In diesem Tutorial werden wir einen Ansatz zum Erstellen eines Sokoban- oder Crate-Pusher-Spiels untersuchen, bei dem kachelbasierte Logik und ein zweidimensionales Array zum Speichern von Level-Daten verwendet werden. Wir verwenden Unity für die Entwicklung mit C# als Skriptsprache. Bitte laden Sie die Quelldateien herunter, die in diesem Tutorial enthalten sind.

1. Das Sokoban-Spiel

Es gibt vielleicht nur wenige unter uns, die möglicherweise keine Sokoban-Spielvariante gespielt haben. Die Originalversion ist möglicherweise sogar älter als einige von Ihnen. Bitte überprüfen Sie die Wiki-Seite für einige Details. Im Wesentlichen haben wir einen Charakter oder ein benutzergesteuertes Element, das Kisten oder ähnliche Elemente auf seine Zielkachel schieben muss.

Die Ebene besteht aus einem quadratischen oder rechteckigen Raster von Kacheln, wobei eine Kachel nicht begehbar oder begehbar sein kann. Wir können auf den begehbaren Fliesen gehen und die Kisten darauf schieben. Spezielle begehbare Kacheln werden als Zielkacheln markiert. Dort sollte die Kiste schließlich ruhen, um das Level zu beenden. Das Zeichen wird normalerweise über eine Tastatur gesteuert. Sobald alle Kisten ein Zielplättchen erreicht haben, ist das Level abgeschlossen.

Kachelbasierte Entwicklung bedeutet im Wesentlichen, dass unser Spiel aus einer Reihe von Kacheln besteht, die auf eine vorgegebene Weise verteilt sind. Ein Level-Datenelement gibt an, wie die Kacheln verteilt werden müssten, um unser Level zu erstellen. In unserem Fall verwenden wir ein quadratisches Raster. Weitere Informationen zu kachelbasierten Spielen finden Sie hier auf Envato Tuts+.

2. Vorbereiten des Unity-Projekts

Mal sehen, wie wir unser Unity-Projekt für dieses Tutorial organisiert haben.

Die Kunst

Für dieses Tutorial-Projekt verwenden wir keine externen Kunstobjekte, sondern verwenden die Sprite-Grundelemente, die mit der neuesten Unity-Version 2017.1 erstellt wurden. Das Bild unten zeigt, wie wir in Unity unterschiedlich geformte Sprites erstellen können.

How to create sprites within United 20171

Wir werden das Square-Sprite verwenden, um eine einzelne Kachel in unserem Sokoban-Level-Raster darzustellen. Wir werden das Dreieck-Sprite verwenden, um unseren Charakter darzustellen, und wir werden das Kreis-Sprite verwenden, um eine Kiste oder in diesem Fall einen Ball darzustellen. Die normalen Bodenkacheln sind weiß, während die Zielkacheln eine andere Farbe haben, um hervorzuheben.

Die Level-Daten

Wir werden unsere Level-Daten in Form eines zweidimensionalen Arrays darstellen, das die perfekte Korrelation zwischen den logischen und visuellen Elementen bietet. Wir verwenden eine einfache Textdatei, um die Ebenendaten zu speichern. Dies erleichtert es uns, die Ebene außerhalb von Unity zu bearbeiten oder Ebenen zu ändern, indem Sie einfach die geladenen Dateien ändern. Der Ordner "Ressourcen" enthält eine Textdatei auf level, die unsere Standard-Level hat.

Die Ebene hat sieben Spalten und fünf Zeilen. Ein Wert von 1 bedeutet, dass wir an dieser Position eine Bodenplatte haben. Ein Wert von -1 bedeutet, dass es sich um eine nicht begehbare Kachel handelt, während ein Wert von 0 bedeutet, dass es sich um eine Zielkachel handelt. Der Wert 2 repräsentiert unseren Helden und 3 repräsentiert einen schiebbaren Ball. Wenn wir uns nur die Level-Daten ansehen, können wir uns vorstellen, wie unser Level aussehen würde.

3. Erstellen eines Sokoban-Spiel-Levels

Um die Dinge einfach zu halten, und da dies keine sehr komplizierte Logik ist, haben wir nur eine einzige Sokoban.cs-Skriptdatei für das Projekt, die an die Szenenkamera angehängt ist. Bitte lassen Sie es in Ihrem Editor offen, während Sie dem Rest des Tutorials folgen.

Spezielle Level-Daten

Die durch das 2D-Array dargestellten Level-Daten werden nicht nur zum Erstellen des anfänglichen Rasters verwendet, sondern auch während des Spiels, um Level-Änderungen und Spielfortschritte zu verfolgen. Dies bedeutet, dass die aktuellen Werte nicht ausreichen, um einige der Levelzustände während des Spiels darzustellen.

Jeder Wert repräsentiert den Status der entsprechenden Kachel in der Ebene. Wir benötigen zusätzliche Werte für die Darstellung eines Balls auf dem Zielplättchen und des Helden auf dem Zielplättchen, die jeweils -3 und -2 sind. Diese Werte können beliebige Werte sein, die Sie im Spieleskript zuweisen, nicht unbedingt dieselben Werte, die wir hier verwendet haben.

Analysieren der Level-Textdatei

Der erste Schritt besteht darin, unsere Level-Daten aus der externen Textdatei in ein 2D-Array zu laden. Wir verwenden die ParseLevel-Methode, um den string-Wert zu laden und zu teilen, um unser 2D-Array levelData zu füllen.

Während des Parsens bestimmen wir die Anzahl der Zeilen und Spalten, die unsere Ebene hat, wenn wir unsere levelData füllen.

Zeichnung-Level

Sobald wir unsere Leveldaten haben, können wir unseren Level auf dem Bildschirm zeichnen. Wir verwenden dazu die CreateLevel-Methode.

Für unser Level haben wir einen tileSize-Wert von 50 festgelegt. Dies ist die Länge der Seite einer quadratischen Kachel in unserem Level-Raster. Wir durchlaufen unser 2D-Array und bestimmen den Wert, der an jedem der i- und j-Indizes des Arrays gespeichert ist. Wenn dieser Wert keine invalidTile (-1) ist, erstellen wir ein neues GameObject mit dem Namen tile. Wir fügen tile eine SpriteRenderer-Komponente hinzu und weisen das entsprechende Sprite oder die entsprechende Color abhängig vom Wert am Array-Index zu.

Während wir den hero oder den ball platzieren, müssen wir zuerst ein Bodenplättchen erstellen und dann diese Plättchen erstellen. Da der Held und der Ball über dem Bodenplättchen liegen müssen, geben wir ihrem SpriteRenderer eine höhere sortingOrder. Allen Kacheln wird eine localScale von tileSize zugewiesen, sodass sie in unserer Szene 50x50 sind.

Wir verfolgen die Anzahl der Bälle in unserer Szene mithilfe der Variablen ballCount. In unserem Level sollte sich die gleiche oder eine höhere Anzahl von Zielplättchen befinden, um den Abschluss des Levels zu ermöglichen. Die Magie geschieht in einer einzelnen Codezeile, in der wir die Position jeder Kachel mithilfe der GetScreenPointFromLevelIndices-Methode(int row,int col) bestimmen.

Die Weltposition einer Kachel wird durch Multiplizieren der Ebenenindizes mit dem Wert von tileSize bestimmt. Die Variable middleOffset wird verwendet, um die Ebene in der Mitte des Bildschirms auszurichten. Beachten Sie, dass der row-Wert mit einem negativen Wert multipliziert wird, um die invertierte y-Achse in Unity zu unterstützen.

4. Sokoban-Logik

Nachdem wir unser Level angezeigt haben, fahren wir mit der Spiellogik fort. Wir müssen auf die Eingabe der Benutzertaste warten und den hero basierend auf der Eingabe bewegen. Der Tastendruck bestimmt eine erforderliche Bewegungsrichtung, und der hero muss in diese Richtung bewegt werden. Sobald wir die erforderliche Bewegungsrichtung festgelegt haben, sind verschiedene Szenarien zu berücksichtigen. Nehmen wir an, das Plättchen neben dem hero in dieser Richtung ist tileK.

  • Befindet sich an dieser Position eine Kachel in der Szene oder befindet sie sich außerhalb unseres Gitters?
  • Ist tileK eine begehbare Fliese?
  • Ist tileK von einem Ball besetzt?

Wenn sich die Position von tileK außerhalb des Rasters befindet, müssen wir nichts tun. Wenn tileK gültig und begehbar ist, müssen wir den hero an diese Position bringen und unser levelData-Array aktualisieren. Wenn tileK einen Ball hat, müssen wir den nächsten Nachbarn in die gleiche Richtung betrachten, sagen wir tileL.

  • Befindet sich tileL außerhalb des Rasters?
  • Ist Fliese eine begehbare Fliese?
  • Ist die Fliese von einem Ball besetzt?

Nur in dem Fall, in dem TileL ein begehbares, nicht besetztes Plättchen ist, sollten wir den hero und den Ball bei TileK auf TileK bzw. TileL bewegen. Nach erfolgreicher Verschiebung müssen wir das levelData-Array aktualisieren.

Unterstützende Funktionen

Die obige Logik bedeutet, dass wir wissen müssen, auf welchem Plättchen sich unser hero gerade befindet. Wir müssen auch feststellen, ob ein bestimmtes Plättchen einen Ball hat und Zugriff auf diesen Ball haben soll.

Um dies zu erleichtern, verwenden wir ein Dictionary namens occupants, in dem ein GameObject als Schlüssel und seine Array-Indizes als Vector2 als Wert gespeichert sind. In der CreateLevel-Methode füllen wir occupants, wenn wir hero oder Bälle erstellen. Sobald wir das Wörterbuch ausgefüllt haben, können wir GetOccupantAtPosition verwenden, um das GameObject an einem bestimmten Array-Index zurückzugewinnen.

Die IsOccupied-Methode bestimmt, ob der levelData-Wert an den angegebenen Indizes eine Kugel darstellt.

Wir müssen auch überprüfen können, ob sich eine bestimmte Position in unserem Raster befindet und ob diese Kachel begehbar ist. Die IsValidPosition-Methode überprüft die als Parameter übergebenen Ebenenindizes, um festzustellen, ob sie in unsere Ebenendimensionen fallen. Es wird auch geprüft, ob wir eine invalidTile als diesen Index in den levelData haben.

Auf Benutzereingaben reagieren

In der Update-Methode unseres Spielskripts suchen wir nach den KeyUp-Ereignissen des Benutzers und vergleichen sie mit unseren im Array userInputKeys gespeicherten Eingabeschlüsseln. Sobald die gewünschte Bewegungsrichtung bestimmt ist, rufen wir die TryMoveHero-Methode mit der Richtung als Parameter auf.

In der TryMoveHero-Methode wird unsere zu Beginn dieses Abschnitts erläuterte Kernspiellogik implementiert. Bitte gehen Sie die folgende Methode sorgfältig durch, um zu sehen, wie die Logik wie oben erläutert implementiert wird.

Um die nächste Position entlang einer bestimmten Richtung basierend auf einer angegebenen Position zu erhalten, verwenden wir die GetNextPositionAlong-Methode. Es geht nur darum, einen der Indizes entsprechend der Richtung zu erhöhen oder zu verringern.

Bevor wir Helden oder Bälle bewegen, müssen wir ihre aktuell belegte Position im levelData-Array löschen. Dies erfolgt mit der RemoveOccupant-Methode.

Wenn wir am angegebenen Index eine heroTile oder ballTile finden, müssen wir sie auf groundTile setzen. Wenn wir eine heroOnDestinationTile oder eine ballOnDestinationTile finden, müssen wir sie auf destinationTile setzen.

Level-Abschluss

Das Level ist beendet, wenn alle Bälle an ihrem Ziel sind.

A Completed Level

Nach jeder erfolgreichen Bewegung rufen wir die CheckCompletion-Methode auf, um festzustellen, ob das Level abgeschlossen ist. Wir durchlaufen unser levelData-Array und zählen die Anzahl der Vorkommen von ballOnDestinationTile. Wenn diese Anzahl unserer von ballCount ermittelten Gesamtzahl von Bällen entspricht, ist das Level abgeschlossen.

Abschluss

Dies ist eine einfache und effiziente Implementierung der Sokoban-Logik. Sie können Ihre eigenen Ebenen erstellen, indem Sie die Textdatei ändern oder eine neue erstellen und die Variable levelName so ändern, dass sie auf Ihre neue Textdatei verweist.

Die aktuelle Implementierung verwendet die Tastatur, um den Helden zu steuern. Ich möchte Sie einladen, zu versuchen, das Steuerelement auf tippenbasiert zu ändern, damit wir berührungsbasierte Geräte unterstützen können. Dies würde auch das Hinzufügen einer 2D-Pfadfindung beinhalten, wenn Sie auf eine Kachel tippen möchten, um den Helden dorthin zu führen.

In einem anschließenden Tutorial wird untersucht, wie mit dem aktuellen Projekt isometrische und hexagonale Versionen von Sokoban mit minimalen Änderungen erstellt werden können.

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.