Willkommen zu diesem Unity-2D-Tutorial. In dieser Lektion erstellen wir
Schritt für Schritt eine Basis für ein Match-3-Spiel. Vorherige
Unity-Erfahrung ist nicht erforderlich.
Installiere
Unity 6.0 LTS (oder eine andere Version mit 2D-Unterstützung), falls
noch nicht geschehen — und los geht’s.
Es gibt viele Möglichkeiten, ein solches Spiel zu entwickeln. Wir
erstellen eine einfache, aber flexible Umsetzung mit einem 2D-Raum,
Sprites und deren Bewegung via
transform.Translate
. Zum Erkennen ausgewählter Elemente
nutzen wir das Raycast 2D
-System. Die Spiellogik teilen wir
in separate Module auf, damit der Code klar und leicht erweiterbar
bleibt.
Öffne den Unity Hub und erstelle ein neues 2D-Projekt, indem du die Vorlage 2D (Built-In render Pipeline) auswählst. Gehe dazu zum Tab Projects und klicke auf New Project. Suche die Vorlage 2D (Built-In render Pipeline) — sie enthält bereits grundlegende 2D-Grafik und Physik. Je nach deiner Unity-Version kann der Name leicht abweichen — wenn du sie nicht findest, wähle 2D (URP) oder einfach 2D.
Gib einen Projektnamen ein, z. B. MatchGame
(oder einen
eigenen), und wähle den Ordner, in dem es erstellt werden soll. Klicke
auf Create project und warte, bis die Umgebung
vorbereitet ist.
Für diesen Prototyp reicht uns ein einzelnes Sprite, dessen Aussehen wir später durch Farbänderungen variieren.
Du kannst das unten stehende Bild verwenden oder dein eigenes wählen. Die einzige Anforderung: Das Sprite sollte 100×100 Pixel groß sein, da dies die Grundgröße der Elemente auf dem Spielfeld ist.
Um das Bild in Unity zu importieren: Klicke mit der rechten Maustaste im Fenster Assets, wähle Import New Asset..., suche die heruntergeladene Datei und klicke auf Import.
💡 Tipp: Du kannst die Bilddatei auch direkt ins
Assets-Fenster ziehen — das geht oft noch
schneller.
💡 Tipp: Fang einfach an — die Optik kannst du später jederzeit
verbessern.
Unser Spiel besteht aus mehreren wichtigen Logikblöcken. Zuerst füllen wir das Spielfeld mit Diamanten. Dabei ist es wichtig sofort zu prüfen, dass keine Linien aus drei oder mehr gleichen Diamanten entstehen — so verhindern wir zufällige Matches beim Start.
Nachdem das Feld gefüllt ist, übergeben wir den Zug an den Spieler. Zur Vereinfachung kann der Spieler in diesem Stadium beliebige zwei benachbarte Diamanten vertikal oder horizontal tauschen, ohne auf Moves beschränkt zu sein, die direkt zu Matches führen.
Nach dem Zug des Spielers werden die Diamanten getauscht. Danach überprüfen wir das gesamte Feld auf Übereinstimmungen. Wenn Matches aus drei oder mehr gleichen Diamanten gefunden werden, entfernen wir diese und füllen die leeren Felder mit neuen Diamanten. Gibt es keine Matches, ist der Spieler wieder an der Reihe.
Um die Hauptphasen des Spiels zu steuern, erstellen wir ein separates
Skript. Erstelle ein neues MonoBehaviour
-Skript und nenne
es GameManager
. Dieses Skript bestimmt die aktuelle
Spielphase und startet die entsprechenden Logikblöcke. Außerdem
speichert es Arrays für das Spielfeld und die Menge an Spielobjekten
(z. B. Diamanten).
In diesem Stadium ist das Starten der Logikblöcke deaktiviert — wir verbinden sie später, wenn wir die jeweiligen Funktionen umgesetzt haben.
using UnityEngine;
public class GameManager : MonoBehaviour
{
// 2D-Array, das das Spielfeld darstellt (6×6 Felder)
public GameObject[,] gemsGrid = new GameObject[6, 6];
// Array mit verfügbaren Edelstein-Prefabs (verschiedene Farben)
public GameObject[] gemsArray;
// Zähler für Edelsteine, die sich gerade bewegen
public int gemCounter = 0;
// Flag: erlaubt dem Spieler, einen Zug zu machen
public bool canMove = false;
// Flag: löst die Überprüfung auf Übereinstimmungen aus
public bool canCheck = false;
// Flag: löst das Auffüllen des Spielfelds nach Löschungen aus
public bool canRefill = false;
// Flag: zeigt an, ob aktuell keine Übereinstimmungen vorhanden sind
public bool noMatches = true;
private void Update()
{
// Wenn sich keine Edelsteine mehr bewegen und Prüfung erlaubt ist — prüfe auf Matches
if (canCheck && gemCounter <= 0)
{
canCheck = false;
gemCounter = 0;
//GetComponent<GridChecker>().CheckForMatches();
}
// Wenn sich keine Edelsteine mehr bewegen und Auffüllen erlaubt ist — fülle das Feld
if (canRefill && gemCounter <= 0)
{
canRefill = false;
gemCounter = 0;
//GetComponent<RefillGrid>().Refill();
}
// Wenn keine Prozesse mehr laufen und keine Übereinstimmungen gefunden wurden — Spieler darf ziehen
if (!canRefill && noMatches && !canCheck && gemCounter <= 0)
{
gemCounter = 0;
canMove = true;
}
}
}
Jetzt erstelle ein leeres GameObject:
Benenne es um in GameManager
und gib ihm den Tag
GameManager
.
Füge das Skript GameManager
zu diesem Objekt hinzu. Wähle
dazu das Objekt in der Hierarchie aus und ziehe das Skript ins
Inspector-Feld oder nutze den Button Add Component.
GameManager
-Skript.
Jetzt erstellen wir ein Template für die Diamanten unseres Spiels. Im zweiten Schritt hast du bereits das benötigte Sprite importiert. Ziehe es in die Szene — Unity erstellt automatisch ein neues GameObject.
In diesem Spiel werden wir die Diamanten mit
transform.Translate
bewegen und damit ihre Position im
Raum ändern. Außerdem nutzen wir die integrierte 2D-Physik, um
Interaktionen mit dem Spieler zu registrieren.
Einen Teil der Spiellogik platzieren wir direkt auf dem Diamanten-Objekt, damit wir kein separates Controller-Skript brauchen. Jeder Diamant speichert dafür selbst einige wichtige Informationen.
Füge zuerst dem Diamanten einen Circle Collider 2D-Komponenten hinzu, damit Kollisionsevents genutzt werden können.
Erstelle jetzt ein neues MonoBehaviour-Skript und nenne es
GemData
:
using UnityEngine;
public class GemData : MonoBehaviour
{
// Position dieses Edelsteins im Gitter-Array,
// entspricht seiner Weltposition in Unity
public int gridPosX, gridPosY;
// Referenz auf den CircleCollider2D dieses Edelsteins
private CircleCollider2D col;
// Flag: erlaubt Bewegung zur Zielposition
private bool canMoveToPos = false;
// Bewegungsgeschwindigkeit
public float speed;
// Zielposition, zu der sich der Edelstein bewegen soll
private Vector3 endPos;
// Referenz auf das Hauptskript GameManager
private GameManager manager;
private void Awake()
{
// Suche nach dem Objekt mit dem Tag "GameManager" und hole sein Skript
manager = GameObject.FindWithTag("GameManager").GetComponent<GameManager>();
// Hole die eigene CircleCollider2D-Komponente
col = GetComponent<CircleCollider2D>();
}
private void Update()
{
// Wenn Bewegung erlaubt ist — bewege den Edelstein
if (canMoveToPos)
{
// Berechne die Richtung zur Zielposition (normalisiert)
Vector3 dir = (endPos - transform.position).normalized;
// Bewege den Edelstein in diese Richtung mit definierter Geschwindigkeit
transform.Translate(dir * speed * Time.deltaTime);
// Wenn das Ziel fast erreicht ist
if ((endPos - transform.position).sqrMagnitude < 0.05f)
{
// Setze präzise auf die Zielposition, um Ungenauigkeiten zu vermeiden
transform.position = endPos;
// Aktiviere den Collider wieder — Kollisionen sind jetzt wieder möglich
col.enabled = true;
// Informiere den GameManager, dass ein Edelstein fertig bewegt wurde
manager.gemCounter--;
// Setze die Zeichenreihenfolge zurück auf normal
GetComponent<SpriteRenderer>().sortingOrder = 0;
// Bewegung beenden
canMoveToPos = false;
}
}
}
// Methode zum Starten der Bewegung zur Zielposition
public void Move()
{
// Berechne Weltposition basierend auf Gitterkoordinaten
endPos = new Vector3(gridPosX, gridPosY);
// Deaktiviere Collider während der Bewegung, um Kollisionen zu vermeiden
col.enabled = false;
// Erhöhe Zähler für aktive Bewegungen im GameManager
manager.gemCounter++;
// Setze die Zeichenreihenfolge höher, damit dieser Edelstein oben angezeigt wird
GetComponent<SpriteRenderer>().sortingOrder = 10;
// Bewegung aktivieren
canMoveToPos = true;
}
}
Beachte dabei: Obwohl der Diamant einen 2D-Collider hat, verwenden wir
für die Bewegung
transform.Translate
ohne Rigidbody2D
. Das
ist für einfache Logik schneller, erfordert aber, dass der Collider
während der Bewegung manuell deaktiviert wird.
Beim Bewegen ändern wir außerdem die Sortier-Reihenfolge (sorting order), damit der Diamant über anderen Elementen gezeichnet wird. Sobald die Bewegung abgeschlossen ist, setzen wir sie zurück.
Zur Vereinfachung stimmen unsere Koordinaten im Array und in der
Welt-Szene überein. Da in Unity standardmäßig eine Welteinheit 100
Pixel entspricht, nimmt ein Sprite von 100×100 Pixel genau eine Zelle
ein. Ein Diamant an Position (1,4)
im Array erscheint
also genau an dieser Stelle in der Szene.
Ändere im Inspector die Bewegungsgeschwindigkeit
speed
auf 1 — später kannst du den
optimalen Wert einstellen.
Fertig: Jetzt haben wir ein vollständiges Template für unsere Diamanten. Im nächsten Schritt fügen wir Variation hinzu, damit sie sich voneinander unterscheiden.
GemData
-Skript.
Jetzt haben wir das Diamant-Template in der Szene und es ist Zeit,
daraus echte Prefabs zu machen. Dabei ist es wichtig, das
Gleichgewicht zu halten: Wenn es zu wenig Varianten gibt, entstehen
Matches zu oft und der Spieler sieht nur, wie das Feld automatisch
geleert und neu gefüllt wird (vielleicht ist das aber genau der
Effekt, den du willst). Gibt es zu viele Varianten, werden Matches
selten und das Spiel kann lange ohne mögliche Züge festhängen.
Für unser 6×6-Feld sind 4–5 Varianten optimal. Wir
wählen fünf, um einen guten Mix aus Zufall und Spielerplanung zu
erhalten.
Wähle den Diamanten in der Szene (oder im Hierarchie-Fenster) und
benenne ihn um in
Gem-Red
.
Erstelle einen neuen Tag red
und weise ihn dem Objekt
Gem-Red
zu. Ändere danach in der Komponente
Sprite Renderer die Farbe des Diamanten auf Rot.
Ziehe jetzt das Objekt Gem-Red
aus der Hierarchie in den
Ordner Assets — Unity erstellt automatisch ein Prefab
daraus.
Wähle danach erneut den Diamanten in der Szene und erstelle auf die gleiche Weise vier weitere Prefabs.
Am Ende habe ich diese Prefabs:
Gem-Red
mit dem Tag red
,
Gem-Blue
mit dem Tag blue
,
Gem-Yellow
mit dem Tag yellow
,
Gem-Green
mit dem Tag green
und
Gem-Fuchsia
mit dem Tag fuchsia
. Die Farben
stimmen jeweils überein.
Nachdem alle fünf Prefabs erstellt sind, kannst du das Diamant-Objekt aus der Szene löschen — alle benötigten Templates sind jetzt als Prefabs im Projekt gespeichert.
Der erste logische Schritt unseres Spiels ist, das Spielfeld mit zufälligen Diamanten zu füllen. Dafür bereiten wir zunächst ein Array vor, in dem die möglichen Diamant-Prefabs zum Spawnen gespeichert sind.
Wähle GameManager
in der Hierarchie aus. Im Inspector
findest du das Script GameManager
, in dem sich das Array
GemsArray
befindet. Dieses ist aktuell leer. Ziehe deine
fünf Diamant-Prefabs hinein.
Jetzt erstellen wir ein Script für die erste Befüllung des Spielfelds.
Erstelle ein neues MonoBehaviour-Script und nenne es
SpawnController
. Füge es dem selben Objekt
GameManager
hinzu.
using UnityEngine;
// Für die Verwendung von List
using System.Collections.Generic;
public class SpawnController : MonoBehaviour
{
private GameManager manager;
void Start()
{
// Referenz auf das GameManager-Skript auf demselben Objekt holen
manager = GetComponent<GameManager>();
// Beim Start des Spiels das Spielfeld mit Edelsteinen füllen
FillTheGrid();
}
public void FillTheGrid()
{
// Schleife über jedes Feld im Gitter anhand von X- und Y-Koordinaten
for (int i = 0; i < 6; i++)
{
for (int j = 0; j < 6; j++)
{
Vector2 position = new Vector2(i, j);
// Kopie des Prefab-Arrays erstellen, um Filterung zu ermöglichen
List<GameObject> candidateTypes = new List<GameObject>(manager.gemsArray);
// Linken Nachbarn prüfen – gleichen Typ entfernen
if (i >= 1)
candidateTypes.RemoveAll(gem => gem.tag == manager.gemsGrid[i - 1, j].tag);
// Unteren Nachbarn prüfen – gleichen Typ entfernen
if (j >= 1)
candidateTypes.RemoveAll(gem => gem.tag == manager.gemsGrid[i, j - 1].tag);
// Zufälliges Prefab aus den verbleibenden auswählen
GameObject gem = Instantiate(
candidateTypes[Random.Range(0, candidateTypes.Count)],
position,
Quaternion.identity
);
// Koordinaten im Gitter dem Edelstein zuweisen
GemData gemData = gem.GetComponent<GemData>();
gemData.gridPosX = i;
gemData.gridPosY = j;
// Edelstein im Spielfeld-Array speichern
manager.gemsGrid[i, j] = gem;
}
}
// Spieler darf den ersten Zug machen
manager.canMove = true;
}
}
Dieses Script wird einmalig beim Start des Spiels ausgeführt. Es kopiert das Array der verfügbaren Diamant-Prefabs in eine dynamische Liste und filtert beim Durchlaufen jeder Zelle die Typen heraus, die bereits links oder unten angrenzen. So vermeiden wir Matches am Spielstart.
Anschließend wird zufällig ein Diamant aus den verbleibenden
Kandidaten ausgewählt, im Szenenraum erstellt, seine Grid-Koordinaten
werden gesetzt und er wird im Array gemsGrid
gespeichert.
Nach dem Befüllen ist das Spielfeld bereit und der Spieler kann den
ersten Zug machen.
Zu diesem Zeitpunkt kannst du das Spiel starten und sehen, wie das Spielfeld mit zufälligen Diamanten gefüllt wird.
GemsArray
.
Nun hat der Spieler ein gefülltes Spielfeld vor sich und kann seinen ersten Zug machen. Dafür erstellen wir ein Skript, das die Maus-Eingaben und die Bewegung der ausgewählten Diamanten verarbeitet.
Erstelle ein neues MonoBehaviour-Skript mit dem Namen
MoveGem
. Füge dieses Skript dem Objekt
GameManager
hinzu.
using UnityEngine;
public class MoveGem : MonoBehaviour
{
// Minimale Wischstrecke, um eine Bewegung auszulösen (in Welt-Einheiten)
public float swipeThreshold = 0.5f;
// Bewegungsrichtung als Text ("left", "right", "up", "down")
private string direction;
// Der vom Spieler ausgewählte Edelstein
private GameObject selectedGem;
// Start- und Endposition der Maus
private Vector2 startMouse;
private Vector2 endMouse;
private GameManager manager;
void Start()
{
// Referenz auf den GameManager im selben Objekt holen
manager = GetComponent<GameManager>();
}
void Update()
{
// Mausklick erkannt — Wischbewegung beginnt
if (Input.GetMouseButtonDown(0) && manager.canMove)
{
// Startposition der Maus in Weltkoordinaten speichern
startMouse = Camera.main.ScreenToWorldPoint(Input.mousePosition);
// Prüfen, ob etwas getroffen wurde
RaycastHit2D hit = Physics2D.Raycast(startMouse, Vector2.zero);
if (hit.collider != null)
{
// Ausgewählten Edelstein speichern
selectedGem = hit.collider.gameObject;
}
}
// Maustaste losgelassen — Wischbewegung endet
if (Input.GetMouseButtonUp(0) && selectedGem != null && manager.canMove)
{
endMouse = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 delta = endMouse - startMouse;
direction = "";
// Wischstrecke prüfen
if (delta.magnitude > swipeThreshold)
{
// Richtung anhand der dominanten Achse bestimmen
if (Mathf.Abs(delta.x) >= Mathf.Abs(delta.y))
direction = delta.x > 0 ? "right" : "left";
else
direction = delta.y > 0 ? "up" : "down";
}
// Wenn Richtung erkannt — Edelsteine tauschen
if (direction != "")
{
SwitchGems(direction);
}
}
}
// Wandelt Richtungsstring in konkrete Bewegung um
void SwitchGems(string direction)
{
switch (direction)
{
case "right": GemSwap(Vector2Int.right); break;
case "left": GemSwap(Vector2Int.left); break;
case "up": GemSwap(Vector2Int.up); break;
case "down": GemSwap(Vector2Int.down); break;
}
}
// Tauscht zwei benachbarte Edelsteine
void GemSwap(Vector2Int dir)
{
int x1 = selectedGem.GetComponent<GemData>().gridPosX;
int y1 = selectedGem.GetComponent<GemData>().gridPosY;
int x2 = x1 + dir.x;
int y2 = y1 + dir.y;
// Spielfeldgrenzen prüfen
if (x2 < 0 || x2 >= manager.gemsGrid.GetLength(0) || y2 < 0 || y2 >= manager.gemsGrid.GetLength(1))
return;
GameObject gem1 = manager.gemsGrid[x1, y1];
GameObject gem2 = manager.gemsGrid[x2, y2];
var data1 = gem1.GetComponent<GemData>();
var data2 = gem2.GetComponent<GemData>();
// Gitterkoordinaten in den Daten aktualisieren
data1.gridPosX = x2;
data1.gridPosY = y2;
data2.gridPosX = x1;
data2.gridPosY = y1;
// Im Array tauschen
manager.gemsGrid[x1, y1] = gem2;
manager.gemsGrid[x2, y2] = gem1;
// Bewegung starten
data1.Move();
data2.Move();
// Weitere Eingaben vorübergehend deaktivieren
manager.canMove = false;
manager.canCheck = true;
}
}
Dieses Skript registriert und verarbeitet Mausklicks. Wenn der Spieler die linke Maustaste drückt, wird der Startpunkt und der ausgewählte Diamant gespeichert. Beim Loslassen wird geprüft, wie weit der Cursor bewegt wurde — als Schutz gegen versehentliche Klicks. Ist die Bewegung groß genug, wird die Richtung bestimmt und eine Funktion zum Tauschen der Diamanten aufgerufen.
Dann finden wir das Diamantenpaar (den ausgewählten und den Nachbarn
in der gewählten Richtung), tauschen deren Positionen im Array,
aktualisieren die Koordinaten in den GemData
-Skripten und
schicken beide zu ihren neuen Positionen. Danach wird die Möglichkeit
für weitere Züge deaktiviert, bis die Bewegung beendet und die Matches
geprüft sind.
x = 2.5
und y = 2.5
setzen, damit das Feld
zentriert im Bildschirm ist.
Nun kannst du das Spiel starten und testen, wie die Diamanten die Plätze tauschen.
MoveGem
-Skript.
Der nächste logische Schritt nach dem Zug des Spielers ist die Prüfung
des Spielfelds auf Matches. Dafür erstellen wir ein neues
MonoBehaviour-Script namens GridChecker
.
Füge dieses Script dem Objekt GameManager
hinzu.
using UnityEngine;
// Für Listen (dynamische Arrays)
using System.Collections.Generic;
// Klasse zum Überprüfen des Gitters auf Übereinstimmungen (3 oder mehr gleiche Edelsteine)
public class GridChecker : MonoBehaviour
{
// Liste aller Gruppen von passenden Edelsteinen
private List<List<GameObject>> matchedGems;
// Referenz auf das GameManager-Skript
private GameManager manager;
// Zwischengespeichertes Edelstein-Gitter
private GameObject[,] gemsGrid;
// Hauptmethode zum Finden und Entfernen von Übereinstimmungen
public void CheckForMatches()
{
// Hole GameManager und das Gitter
manager = GetComponent<GameManager>();
gemsGrid = manager.gemsGrid;
// Neue Liste für alle gefundenen Kombinationen erstellen
matchedGems = new List<List<GameObject>>();
// Erstes Objekt zum Vergleich in der Reihe
GameObject firstGemToCheck = null;
// Temporäre Liste für Übereinstimmungskette
List<GameObject> tmpGems = new List<GameObject>();
// === Horizontale Überprüfung ===
for (int y = 0; y < gemsGrid.GetLength(1); y++)
{
firstGemToCheck = null;
tmpGems.Clear();
for (int x = 0; x < gemsGrid.GetLength(0); x++)
{
// Aktuellen Edelstein holen
GameObject current = gemsGrid[x, y];
// Wenn leer — vorherige Kette ggf. speichern
if (current == null)
{
if (tmpGems.Count >= 3)
matchedGems.Add(new List<GameObject>(tmpGems));
tmpGems.Clear();
firstGemToCheck = null;
continue;
}
// Neuer Typ oder erstes Element
if (firstGemToCheck == null || current.tag != firstGemToCheck.tag)
{
if (tmpGems.Count >= 3)
matchedGems.Add(new List<GameObject>(tmpGems));
tmpGems.Clear();
tmpGems.Add(current);
firstGemToCheck = current;
}
else
{
// Gleicher Typ — Kette erweitern
tmpGems.Add(current);
}
}
// Letzte Kette in der Reihe prüfen
if (tmpGems.Count >= 3)
matchedGems.Add(new List<GameObject>(tmpGems));
}
// === Vertikale Überprüfung ===
for (int x = 0; x < gemsGrid.GetLength(0); x++)
{
firstGemToCheck = null;
tmpGems.Clear();
for (int y = 0; y < gemsGrid.GetLength(1); y++)
{
GameObject current = gemsGrid[x, y];
if (current == null)
{
if (tmpGems.Count >= 3)
matchedGems.Add(new List<GameObject>(tmpGems));
tmpGems.Clear();
firstGemToCheck = null;
continue;
}
if (firstGemToCheck == null || current.tag != firstGemToCheck.tag)
{
if (tmpGems.Count >= 3)
matchedGems.Add(new List<GameObject>(tmpGems));
tmpGems.Clear();
tmpGems.Add(current);
firstGemToCheck = current;
}
else
{
tmpGems.Add(current);
}
}
// Letzte vertikale Kette prüfen
if (tmpGems.Count >= 3)
matchedGems.Add(new List<GameObject>(tmpGems));
}
// === Alle passenden Edelsteine entfernen ===
foreach (List<GameObject> group in matchedGems)
{
foreach (GameObject gem in group)
{
if (gem != null)
{
// Aus dem Gitter entfernen
GemData data = gem.GetComponent<GemData>();
manager.gemsGrid[data.gridPosX, data.gridPosY] = null;
// Objekt in der Szene zerstören
Destroy(gem);
}
}
}
// Prüfen, ob Übereinstimmungen vorhanden waren
bool hadMatches = matchedGems.Count > 0;
// Liste bereinigen
matchedGems.Clear();
// === Spielstatus aktualisieren ===
if (hadMatches)
{
manager.noMatches = false;
manager.canRefill = true;
manager.canCheck = false;
}
else
{
manager.noMatches = true;
manager.canRefill = false;
manager.canCheck = false;
}
}
}
Dieses Script überprüft Reihen und Spalten im Feld, um Gruppen von
drei oder mehr gleichen Diamanten zu finden. Dabei wird eine temporäre
Liste tmpGems
genutzt, die aufeinanderfolgende gleiche
Diamanten sammelt. Wenn ein anderer Typ oder eine leere Zelle gefunden
wird, prüft das Script, ob mindestens drei gleiche Diamanten gesammelt
sind und speichert diese dann in der Gesamtliste
matchedGems
.
Nach der Prüfung werden alle gefundenen Matches gelöscht, und je nachdem ob welche gefunden wurden, startet das Spiel das Auffüllen leerer Felder oder gibt den Zug an den Spieler zurück.
GetComponent<GridChecker>().CheckForMatches();
im
GameManager
-Script auskommentieren, um die Match-Suche zu
testen. Beachte, dass beim Interagieren mit leeren Feldern noch Fehler
auftreten können — das wird später behoben.
GridChecker
-Script.
Jetzt steht der letzte logische Schritt des Spiels an. Auf dem Spielfeld sind nun leere Felder dort, wo vorher Diamanten gelöscht wurden. Wir müssen: diese leeren Stellen finden und die darüber liegenden Diamanten nach unten verschieben, und danach neue Diamanten für die verbleibenden leeren Felder spawnen.
Dafür erstellen wir ein neues MonoBehaviour
-Script mit
dem Namen RefillGrid
. Dieses Script fügen wir dem
GameManager
hinzu.
using UnityEngine;
// Für Coroutine-Unterstützung
using System.Collections;
public class RefillGrid : MonoBehaviour
{
// Referenz auf den Hauptspielmanager
private GameManager manager;
// Das Spielfeld mit allen Edelsteinen
private GameObject[,] gemsGrid;
// Array mit möglichen Edelstein-Prefabs
private GameObject[] gemsArray;
private void Start()
{
// Referenzen aus dem GameManager holen
manager = GetComponent<GameManager>();
gemsArray = manager.gemsArray;
gemsGrid = manager.gemsGrid;
}
// Startet den Auffüllvorgang
public void Refill()
{
// Startet die Ablauf-Reihenfolge: Fallen → Erzeugen → Prüfen
StartCoroutine(DoRefillSequence());
// Deaktiviert das Nachfüll-Flag bis alles fertig ist
manager.canRefill = false;
}
// Ablauf von Fall- und Erzeugungsprozess
IEnumerator DoRefillSequence()
{
// Bestehende Edelsteine fallen lassen
yield return StartCoroutine(SlowFall());
// Neue Edelsteine erzeugen
yield return StartCoroutine(SlowSpawn());
// Kurze Pause für Animationen
yield return new WaitForSeconds(0.2f);
// Jetzt darf nach Übereinstimmungen gesucht werden
manager.canCheck = true;
}
// Lässt Edelsteine in leere Zellen fallen
IEnumerator SlowFall()
{
for (int x = 0; x < gemsGrid.GetLength(0); x++)
{
for (int y = 0; y < gemsGrid.GetLength(1) - 1; y++)
{
// Wenn die Zelle leer ist
if (gemsGrid[x, y] == null)
{
// Suche nach einem Edelstein weiter oben
for (int aboveY = y + 1; aboveY < gemsGrid.GetLength(1); aboveY++)
{
if (gemsGrid[x, aboveY] != null)
{
// Bewege den Edelstein nach unten
GameObject gem = gemsGrid[x, aboveY];
gemsGrid[x, y] = gem;
gemsGrid[x, aboveY] = null;
// Aktualisiere Position und starte Bewegung
GemData data = gem.GetComponent<GemData>();
data.gridPosY = y;
data.Move();
// Kurze Pause für Schritt-für-Schritt-Fallanimation
yield return new WaitForSeconds(0.1f);
break;
}
}
}
}
}
}
// Neue Edelsteine in leere Felder erzeugen
IEnumerator SlowSpawn()
{
for (int x = 0; x < gemsGrid.GetLength(0); x++)
{
for (int y = 0; y < gemsGrid.GetLength(1); y++)
{
// Wenn nach dem Fall noch leer
if (gemsGrid[x, y] == null)
{
// Spawne leicht oberhalb des Spielfeldes
GameObject newGem = Instantiate(
gemsArray[Random.Range(0, gemsArray.Length)],
new Vector2(x, gemsGrid.GetLength(1) + 1),
Quaternion.identity
);
// In das Spielfeld eintragen
gemsGrid[x, y] = newGem;
// Zielposition setzen und Bewegung starten
GemData data = newGem.GetComponent<GemData>();
data.gridPosX = x;
data.gridPosY = y;
data.Move();
// Pause für sichtbare Schritt-für-Schritt-Spawns
yield return new WaitForSeconds(0.2f);
}
}
}
}
}
Dieses Script erfüllt zwei wichtige Aufgaben: Es sucht zuerst leere Felder und verschiebt die Diamanten von oben nach unten. Danach findet es die verbleibenden leeren Felder und spawnt dort neue Diamanten.
Damit ist unsere Basis-Spiel-Logik fertig! Nun kannst
du die Aufrufe
GetComponent<GridChecker>().CheckForMatches()
und
GetComponent<RefillGrid>().Refill()
im Script
GameManager
auskommentieren, und dein Spiel läuft im
kompletten Zyklus.
RefillGrid
-Script.
Ich hoffe, du hast nicht vergessen, die Aufrufe
CheckForMatches()
und Refill()
im Script
GameManager
zu aktivieren — nun läuft dein Spiel
vollständig!
Von hier an liegt alles an deiner Fantasie 🤗 Füge Spezialeffekte, Animationen, Sounds, Boni, Timer und Levels hinzu — und mach dein Match-3-Spiel einzigartig.