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 C#-Skript (MonoBehaviour) 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
{
// Spielfeld-Array
public GameObject[,] gemsGrid = new GameObject[6, 6];
// Array mit Spielobjekten (Diamanten)
public GameObject[] gemsArray;
// Zähler für bewegende Diamanten
public int gemCounter = 0;
// Erlaubnis für Spielerzug
public bool canMove = false;
// Erlaubnis Matches zu prüfen
public bool canCheck = false;
// Erlaubnis das Feld neu zu füllen
public bool canRefill = false;
// Flag für keine Matches
public bool noMatches = true;
private void Update()
{
// Wenn keine Diamanten mehr in Bewegung sind und Matches geprüft werden können
if (canCheck && gemCounter <= 0)
{
canCheck = false;
gemCounter = 0;
//GetComponent<GridChecker>().CheckForMatches();
}
// Wenn keine Diamanten mehr in Bewegung sind und das Feld gefüllt werden kann
if (canRefill && gemCounter <= 0)
{
canRefill = false;
gemCounter = 0;
//GetComponent<RefillGrid>().Refill();
}
// Wenn keine Matches und keine anderen Prozesse — Zug an Spieler übergeben
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 Diamanten im Grid-Array,
// entspricht der Position im Unity-Welt-Raum
public int gridPosX, gridPosY;
// Referenz auf den Collider dieses Diamanten
private CircleCollider2D col;
// Flag, ob Bewegung zur Zielposition erlaubt ist
private bool canMoveToPos = false;
// Bewegungsgeschwindigkeit
public float speed;
// Endpunkt, zu dem sich der Diamant bewegt
private Vector3 endPos;
// Referenz auf das Hauptspiel-Skript
private GameManager manager;
private void Awake()
{
// Finde das Objekt mit dem Tag GameManager und hole sein Skript
manager = GameObject.FindWithTag("GameManager").GetComponent<GameManager>();
// Hole diesen CircleCollider2D
col = GetComponent<CircleCollider2D>();
}
private void Update()
{
// Wenn Bewegung erlaubt ist
if (canMoveToPos)
{
// Berechne normalisierte Richtung zum Endpunkt
Vector3 dir = (endPos - transform.position).normalized;
// Bewege dich in diese Richtung mit der angegebenen Geschwindigkeit
transform.Translate(dir * speed * Time.deltaTime);
// Wenn fast am Zielpunkt angekommen
if ((endPos - transform.position).sqrMagnitude < 0.05f)
{
// Setze exakt auf die Zielposition
transform.position = endPos;
// Collider wieder aktivieren, damit Kollisionen erkannt werden
col.enabled = true;
// Zähler für bewegende Diamanten im Manager verringern
manager.gemCounter--;
// Sortier-Reihenfolge wieder auf normal setzen
GetComponent<SpriteRenderer>().sortingOrder = 0;
// Bewegungs-Flag ausschalten
canMoveToPos = false;
}
}
}
// Wird aufgerufen, um die Bewegung des Diamanten zu starten
public void Move()
{
// Zielposition aus den Grid-Koordinaten aktualisieren
endPos = new Vector3(gridPosX, gridPosY);
// Collider deaktivieren, um während der Bewegung keine Kollisionen auszulösen
col.enabled = false;
// Zähler für bewegende Diamanten im Manager erhöhen
manager.gemCounter++;
// Höhere Sortier-Reihenfolge setzen, damit dieser Diamant oben gezeichnet wird
GetComponent<SpriteRenderer>().sortingOrder = 10;
// Bewegung erlauben
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 GameManager holen
manager = GetComponent<GameManager>();
// Fülle das Spielfeld einmalig beim Start
FillTheGrid();
}
public void FillTheGrid()
{
// Durchlaufe das Spielfeld auf X und Y
for (int i = 0; i < 6; i++)
{
for (int j = 0; j < 6; j++)
{
Vector2 position = new Vector2(i, j);
// Kopiere das Prefab-Array zur Filterung
List<GameObject> candidateTypes = new List<GameObject>(manager.gemsArray);
// Prüfe linken Nachbarn: falls vorhanden, entferne passenden Typ
if (i >= 1)
candidateTypes.RemoveAll(gem => gem.tag == manager.gemsGrid[i - 1, j].tag);
// Prüfe unteren Nachbarn: falls vorhanden, entferne passenden Typ
if (j >= 1)
candidateTypes.RemoveAll(gem => gem.tag == manager.gemsGrid[i, j - 1].tag);
// Wähle zufällig aus den verbleibenden Typen
GameObject gem = Instantiate(
candidateTypes[Random.Range(0, candidateTypes.Count)],
position,
Quaternion.identity
);
// Setze die Position im Grid für den Diamanten
GemData gemData = gem.GetComponent<GemData>();
gemData.gridPosX = i;
gemData.gridPosY = j;
// Speichere die Referenz im Spielfeld-Array
manager.gemsGrid[i, j] = gem;
}
}
// Erlaube dem Spieler den ersten Zug
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 Distanz zum Erkennen eines Swipes
public float swipeThreshold = 0.5f;
// Bewegungsrichtung als Text
private string direction;
// Ausgewählter Diamant
private GameObject selectedGem;
// Start- und Endpunkt der Maus
private Vector2 startMouse;
private Vector2 endMouse;
private GameManager manager;
void Start()
{
manager = GetComponent<GameManager>();
}
void Update()
{
// Wenn linke Maustaste gedrückt und Zug erlaubt ist
if (Input.GetMouseButtonDown(0) && manager.canMove)
{
startMouse = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(startMouse, Vector2.zero);
if (hit.collider != null)
{
// Gewählten Diamanten merken
selectedGem = hit.collider.gameObject;
}
}
// Wenn linke Maustaste losgelassen und ein Diamant gewählt wurde
if (Input.GetMouseButtonUp(0) && selectedGem != null && manager.canMove)
{
endMouse = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 delta = endMouse - startMouse;
direction = "";
// Prüfen, ob Swipe groß genug ist
if (delta.magnitude > swipeThreshold)
{
// Richtung bestimmen
if (Mathf.Abs(delta.x) >= Mathf.Abs(delta.y))
direction = delta.x > 0 ? "right" : "left";
else
direction = delta.y > 0 ? "up" : "down";
}
if (direction != "")
{
// Diamanten tauschen starten
SwitchGems(direction);
}
}
}
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;
}
}
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;
// Zwei Diamanten zum Tauschen auswählen
GameObject gem1 = manager.gemsGrid[x1, y1];
GameObject gem2 = manager.gemsGrid[x2, y2];
var data1 = gem1.GetComponent<GemData>();
var data2 = gem2.GetComponent<GemData>();
// Koordinaten 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 Züge sperren, bis Matches geprüft sind
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;
using System.Collections.Generic;
public class GridChecker : MonoBehaviour
{
// Array von Listen, in dem wir Gruppen von Matches (3+) speichern
private List<List<GameObject>> matchedGems;
private GameManager manager;
GameObject[,] gemsGrid;
public void CheckForMatches()
{
manager = GetComponent<GameManager>();
gemsGrid = manager.gemsGrid;
matchedGems = new List<List<GameObject>>();
GameObject firstGemToCheck = null;
// temporäre Liste zur Verarbeitung von Matches
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++)
{
GameObject current = gemsGrid[x, y];
// Wenn leeres Feld gefunden, Unterbrechung der Iteration
if (current == null)
{
// Prüfen, ob bereits Matches gesammelt wurden
if (tmpGems.Count >= 3)
// und speichern
matchedGems.Add(new List<GameObject>(tmpGems));
tmpGems.Clear();
firstGemToCheck = null;
continue;
}
// Wenn erster Diamant in Reihe oder anderer Tag als vorheriger
if (firstGemToCheck == null || current.tag != firstGemToCheck.tag)
{
// Prüfen, ob vorherige Matches vorhanden sind
if (tmpGems.Count >= 3)
// speichern
matchedGems.Add(new List<GameObject>(tmpGems));
tmpGems.Clear();
// Neuen Diamanten zur Liste hinzufügen
tmpGems.Add(current);
firstGemToCheck = current;
}
else
{
// Tag stimmt überein, Diamant zur aktuellen Kette hinzufügen
tmpGems.Add(current);
}
}
// Wenn am Ende eine Serie von 3+ gleichartigen Diamanten besteht
if (tmpGems.Count >= 3)
// speichern
matchedGems.Add(new List<GameObject>(tmpGems));
}
// Vertikale Überprüfung — nach dem gleichen Prinzip
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);
}
}
if (tmpGems.Count >= 3)
matchedGems.Add(new List<GameObject>(tmpGems));
}
// Alle gefundenen Matches aus Grid und Szene löschen
foreach (List<GameObject> group in matchedGems)
{
foreach (GameObject gem in group)
{
if (gem != null)
{
GemData data = gem.GetComponent<GemData>();
manager.gemsGrid[data.gridPosX, data.gridPosY] = null;
Destroy(gem);
}
}
}
// Prüfen, ob Matches gefunden wurden
bool hadMatches = matchedGems.Count > 0;
matchedGems.Clear();
// Wenn Matches gefunden, refill starten
if (hadMatches)
{
manager.noMatches = false;
manager.canRefill = true;
manager.canCheck = false;
}
else
{
// Sonst Zug an Spieler zurückgeben über GameManager
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;
using System.Collections;
public class RefillGrid : MonoBehaviour
{
private GameManager manager;
private GameObject[,] gemsGrid;
private GameObject[] gemsArray;
private void Start()
{
manager = GetComponent<GameManager>();
gemsArray = manager.gemsArray;
gemsGrid = manager.gemsGrid;
}
public void Refill()
{
// Starte die Sequenz: Fallen -> Spawn -> Warten -> Überprüfung erlauben
StartCoroutine(DoRefillSequence());
manager.canRefill = false;
}
IEnumerator DoRefillSequence()
{
// Zuerst fallen die bestehenden Diamanten
yield return StartCoroutine(SlowFall());
// Dann spawnen neue an leeren Stellen
yield return StartCoroutine(SlowSpawn());
// Kurze Pause, damit Bewegungen abschließen können
yield return new WaitForSeconds(0.2f);
// Nun können Matches geprüft werden
manager.canCheck = true;
}
IEnumerator SlowFall()
{
for (int x = 0; x < gemsGrid.GetLength(0); x++)
{
for (int y = 0; y < gemsGrid.GetLength(1) - 1; y++)
{
// Wenn leeres Feld in der Spalte gefunden wird
if (gemsGrid[x, y] == null)
{
// Suche darüber ersten Diamanten zum "fallen lassen"
for (int aboveY = y + 1; aboveY < gemsGrid.GetLength(1); aboveY++)
{
if (gemsGrid[x, aboveY] != null)
{
// Bewege gefundenen Diamanten ins leere Feld
GameObject gem = gemsGrid[x, aboveY];
gemsGrid[x, y] = gem;
gemsGrid[x, aboveY] = null;
// Aktualisiere Position im Skript und starte Bewegung
GemData data = gem.GetComponent<GemData>();
data.gridPosY = y;
data.Move();
// Pause, damit Fall sichtbar wird
yield return new WaitForSeconds(0.1f);
break;
}
}
}
}
}
}
IEnumerator SlowSpawn()
{
for (int x = 0; x < gemsGrid.GetLength(0); x++)
{
for (int y = 0; y < gemsGrid.GetLength(1); y++)
{
// Wenn Feld nach Fallen noch leer ist — neuen Diamanten erzeugen
if (gemsGrid[x, y] == null)
{
// Erzeuge etwas oberhalb des Spielfelds
GameObject newGem = Instantiate(
gemsArray[Random.Range(0, gemsArray.Length)],
new Vector2(x, gemsGrid.GetLength(1) + 1),
Quaternion.identity
);
gemsGrid[x, y] = newGem;
// Setze Zielkoordinaten im Grid und starte Bewegung
GemData data = newGem.GetComponent<GemData>();
data.gridPosX = x;
data.gridPosY = y;
data.Move();
// Verzögerung für visuelle Darstellung
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.