Willkommen zu Teil 2 unseres Unity 2D Plattform-Tutorials. In Teil 1 hast du gelernt, wie man eine Spielfigur steuert. Jetzt baust du ein echtes Level – eines, durch das man gerne läuft.
Statt komplizierter Gegner oder Szenenwechsel konzentrieren wir uns auf die Spielwelt: Münzen zum Sammeln, Wände, Abgründe, ein leicht genervter Pilz… und etwas, das dich bis zum Ende begleitet.
Du lernst, wie man Objekte platziert, eine „klebrige Wand“ behebt, eine Plattform bewegt und einfache Logik für Gegner und Neustarts erstellt. Und wenn du den Ausgang erreichst – merkst du vielleicht, dass es gar nicht darum ging.
Öffne also Unity, atme tief durch und beginne – nicht weil du musst, sondern weil etwas leise darauf wartet, dass du es platzierst.
Wir beginnen mit dem Hinzufügen einer vertikalen Wand zur Szene.
Verwende den gleichen largeGround
-Sprite wie für den
Boden – dreh ihn einfach und stelle ihn senkrecht auf. Unity erstellt
automatisch ein neues GameObject. Benenne es in
Wall um.
Im Inspector der neuen Wand:
0
.Drücke jetzt Play und probiere Folgendes: Lauf auf die Wand zu, springe daran hoch und halte die Bewegungstaste gedrückt. Die Ratte klammert sich an die Wand wie ein winziger Superheld. Das ist keine Magie – das ist Reibung.
Das passiert, weil Unitys Standard-Collider Reibung eingebaut haben. Um das zu beheben, weisen wir ein Physics Material 2D mit null Reibung zu.
Im Assets-Ordner:
noFriction
.0
.
Wähle jetzt das Rat-GameObject aus. Im
Capsule Collider 2D-Component setze das
Material auf noFriction
.
Spiele die Szene erneut – die Ratte sollte nicht mehr an Wänden oder Plattformen hängenbleiben.
noFriction
-Material der
Ratte zugewiesen.
Geben wir unserer Ratte etwas zum Einsammeln – Münzen!
Importiere das coin
-Bild in dein Projekt:
Bild hier herunterladen
(wir verwenden die Münze aus diesem Sprite-Sheet). Ziehe sie dann in
die Szene – Unity erstellt ein neues GameObject.
Wähle die Münze in der Hierarchy aus, dann im Inspector:
0
.-10
(damit
sie hinter der Ratte erscheint).
Erstelle ein neues Tag mit dem Namen Coin
und weise es
dem Münzobjekt zu.
Benenne die Münze in CoinPrefab um und ziehe sie dann in den Assets-Ordner, um ein Prefab zu erstellen.
Coin
gesetzt.
Jetzt erstellen wir ein Skript zum Einsammeln der Münzen. Im Assets-Ordner:
CoinController
.
Füge diesen Code in das Skript ein:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CoinController : MonoBehaviour {
// Sound, der beim Einsammeln einer Münze abgespielt wird
public AudioClip CoinSound;
// Münzzähler
public int coin;
void OnTriggerEnter2D (Collider2D other) {
// Wenn das Objekt, das wir treffen, das Tag "Coin" hat
if (other.tag == "Coin") {
// Erhöhe den Münzzähler
coin = coin + 1;
// Spiele den Münz-Sound ab
AudioSource.PlayClipAtPoint(CoinSound, transform.position);
// Zerstöre die eingesammelte Münze
Destroy(other.gameObject);
}
}
}
Füge das CoinController
-Skript dem
Rat-GameObject hinzu. Du kannst den Münzsound hier
herunterladen:
CoinSound.wav herunterladen. Importiere den CoinSound
-Clip ins Projekt und weise
ihn im Inspector dem Feld Coin Sound
im Skript zu.
💡 Hinweis: Wir verwenden PlayClipAtPoint
, daher wird der
Ton im Welt-Raum abgespielt. Wenn er zu leise klingt, liegt das
wahrscheinlich daran, dass der Audio Listener noch
auf der Kamera sitzt. Du kannst das beheben, indem du entweder die
Kamera näher an die Spielfigur bewegst oder den
Audio Listener vom Kameraobjekt auf das Objekt
Rat verschiebst.
Zeigen wir an, wie viele Münzen die Ratte gesammelt hat – mit einem UI-Textfeld.
Gehe in der Hierarchy zu:
Canvas
,
Text(TMP)
und EventSystem
.
Wähle das Canvas-GameObject aus. Im Inspector:
Scale With Screen Size
.
Expand
.
Scale With Screen Size
und Expand
für ein
anpassbares UI.
Doppelklicke auf das Canvas in der Hierarchy, um die UI-Ansicht einzurahmen. Du siehst ein weißes Rechteck, das den sichtbaren Bereich des Bildschirms darstellt.
Wähle Text(TMP) und nimm folgende Änderungen vor:
CoinCounterText
um.0
.
Jetzt aktualisieren wir das CoinController
-Skript, um
diesen Text zu verbinden.
using UnityEngine;
using TMPro;
public class CoinController : MonoBehaviour
{
// Sound beim Einsammeln von Münzen
public AudioClip CoinSound;
// Münzzähler
public int coin;
// Verknüpfung zum TextMeshPro-Zähler
public TMP_Text CoinCounterText;
void OnTriggerEnter2D(Collider2D other)
{
// Prüfen, ob das Objekt das Tag "Coin" hat
if (other.tag == "Coin")
{
coin = coin + 1;
// Abspielgeräusch
AudioSource.PlayClipAtPoint(CoinSound, transform.position);
// Text aktualisieren
CoinCounterText.text = coin.ToString();
// Münze aus Szene entfernen
Destroy(other.gameObject);
}
}
}
Wähle die Rat in der Hierarchy. Im
CoinController-Komponentenfeld ziehe das
CoinCounterText
-GameObject in das Feld
Coin Counter Text.
Drücke nun Play – beim Einsammeln der Münzen wird die UI in Echtzeit aktualisiert.
Lass uns eine kleine Herausforderung hinzufügen: ein Loch, in das der Spieler fallen kann.
Verschiebe eine der Plattformen, um eine Lücke im Boden zu schaffen – das wird zur Fallzone.
Klicke mit der rechten Maustaste in der Hierarchy und
wähle: Create Empty. Benenne das neue Objekt in
DeadEnd
um.
Im Inspector, führe Folgendes aus:
0
.
Erstelle jetzt ein neues MonoBehaviour
-Skript mit dem
Namen DeadEndController
. Füge folgenden Code ein:
using UnityEngine;
using UnityEngine.SceneManagement; // für Szenen-Neuladen
// Dieses Skript wird an ein "Dead End"-Objekt (z. B. Abgrund) gehängt
// Wenn der Spieler hinein fällt, wird die Szene neu geladen
public class DeadEndController : MonoBehaviour
{
// Wird aufgerufen, wenn etwas den Trigger betritt
void OnTriggerEnter2D(Collider2D other)
{
// Prüfen, ob das Objekt den Tag "Player" hat
if (other.tag == "Player")
{
// Die aktuelle Szene neu laden
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
}
Wähle das DeadEnd-Objekt und füge das
DeadEndController
-Skript hinzu. Erstelle dann ein Prefab,
indem du DeadEnd
aus der Hierarchy in
den Assets-Ordner ziehst.
Wenn die Ratte in den Trigger-Bereich fällt, wird die Szene neu geladen – das ist eine einfache Möglichkeit, das Level ohne zusätzlichen Aufwand zurückzusetzen.
Zeit für ein bewegliches Hindernis: ein Pilz-Gegner, der hin und her läuft. Wenn die Ratte auf seinen Kopf springt – verschwindet er. Wenn sie ihn seitlich berührt – wird das Level zurückgesetzt.
Platziere ein Ground
-Prefab und ein
DeadEnd
darunter. Ziehe dann den Pilz-Sprite aus den
Assets in die Szene, auf die letzte Plattform. Benenne das neue Objekt
in Enemy um.
Im Inspector für Enemy:
0
.Interpolate
.
Erstelle nun ein neues Skript mit dem Namen
MushroomController
. Füge diesen Code ein:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MushroomController : MonoBehaviour
{
// Maximale Entfernung, die der Pilz von seiner Startposition entfernt laufen darf (in Unity-Einheiten)
public float distanceToRun = 2f;
// Bewegungsgeschwindigkeit
public float speed = 2f;
// Blickt der Pilz aktuell nach links?
public bool isLookingLeft = true;
// Startposition des Pilzes
Vector3 startPos;
// Referenz auf das Rigidbody2D-Komponente
Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
startPos = transform.position;
// Optimierung: Entfernung als Quadratwert speichern
distanceToRun = distanceToRun * distanceToRun;
}
void FixedUpdate()
{
// Entfernung von der Startposition berechnen
Vector2 dist = transform.position - startPos;
if (isLookingLeft)
{
// Zu weit nach links gelaufen → umdrehen
if (transform.position.x < startPos.x && dist.sqrMagnitude > distanceToRun)
{
TurnTheMushroom();
rb.linearVelocity = new Vector2(speed, rb.linearVelocity.y);
}
else
{
rb.linearVelocity = new Vector2(-speed, rb.linearVelocity.y);
}
}
else
{
// Zu weit nach rechts gelaufen → umdrehen
if (transform.position.x > startPos.x && dist.sqrMagnitude > distanceToRun)
{
TurnTheMushroom();
rb.linearVelocity = new Vector2(-speed, rb.linearVelocity.y);
}
else
{
rb.linearVelocity = new Vector2(speed, rb.linearVelocity.y);
}
}
}
// Pilz spiegeln (Richtung ändern + visuelle Drehung)
void TurnTheMushroom()
{
isLookingLeft = !isLookingLeft;
transform.localScale = new Vector3(
transform.localScale.x * -1,
transform.localScale.y,
transform.localScale.z
);
}
// Wenn der Spieler von der Seite kollidiert → Szene neu laden
void OnCollisionEnter2D(Collision2D other)
{
if (other.collider.gameObject.tag == "Player")
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
// Wenn der Spieler auf den Kopf springt (Trigger oben) → Pilz zerstören
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
Destroy(gameObject);
}
}
}
Füge das MushroomController
-Skript dem
Enemy-Objekt hinzu. Passe Speed und
Distance To Run im Inspector an (z. B. beides auf
2
).
Ziehe abschließend Enemy in den Assets-Ordner, um ein wiederverwendbares Prefab zu erstellen.
Fügen wir eine vertikal bewegliche Plattform hinzu, auf der die Ratte fahren kann.
Ziehe das largeGround
-Sprite in die Szene und benenne das
neue Objekt in Platform um.
Im Inspector für Platform:
0
.Ground
.Kinematic
.
Interpolate
.
Jetzt geben wir der Plattform ein Skript, das sie zwischen zwei Punkten auf- und abbewegt.
Erstelle ein neues MonoBehaviour
Skript mit dem Namen
PlatformMoving
und füge diesen Code ein:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlatformMoving : MonoBehaviour
{
// Geschwindigkeit der Plattformbewegung
public float speed;
// Oberer Punkt, zu dem sich die Plattform bewegt
public Transform endPoint;
// Startpunkt (anfängliche Position)
Vector3 startPoint;
// Referenz zu Rigidbody2D
Rigidbody2D rb;
// Aktueller Geschwindigkeitswert
float currentSpeed;
void Start()
{
// Anfangsposition speichern
startPoint = transform.position;
// Referenz zu Rigidbody2D holen
rb = GetComponent<Rigidbody2D>();
// Anfangsrichtung setzen
currentSpeed = speed;
}
void FixedUpdate()
{
// Wenn Plattform den Endpunkt erreicht hat, Richtung umkehren
if (transform.position.y > endPoint.position.y)
currentSpeed = -speed;
// Wenn Plattform zum Start zurückkehrt, Richtung erneut umkehren
if (transform.position.y < startPoint.y)
currentSpeed = speed;
// Geschwindigkeit auf Rigidbody anwenden
rb.linearVelocity = new Vector3(0, currentSpeed, 0);
}
}
Erstelle in der Hierarchy ein leeres GameObject und nenne es EndPoint. (Optional kannst du ein Icon zuweisen, um es sichtbar zu machen.) Platziere es über der Plattform mit dem Move-Tool.
Wähle erneut Platform aus. Füge das
PlatformMoving
-Skript hinzu. Setze
Speed auf 1
und weise das Objekt
EndPoint dem entsprechenden Feld zu.
Starte die Szene und beobachte, wie sich die Plattform vertikal zwischen zwei Positionen bewegt.
Füge rechts von der Plattform eine Wand hinzu und gib ihr einen Box Collider 2D. Platziere dann ein DeadEnd-Prefab unter der beweglichen Plattform und passe dessen Größe an.
Fügen wir eine Kiste hinzu, die mit der Ratte und anderen Objekten durch echte Physik interagiert.
Ziehe das imageGround
-Sprite in die Szene und benenne das
neue Objekt in Box um.
Im Inspector für Box:
Ground
.0
.Scale X = 0.2
, Y = 1.5
).
Interpolate
.
Du kannst die Kiste jetzt durch die Szene schieben — probiere aus, sie auf der beweglichen Plattform zu benutzen oder als Stufe zu verwenden!
Fügen wir dem Level einen Ausgang hinzu. Er lädt noch keine neue Szene, aber er fungiert als Ziel-Trigger – wenn du ihn erreichst.
Füge zuerst ein weiteres Ground-Prefab an der Stelle
hinzu, wo die bewegliche Plattform endet – also nahe dem oberen Ende
ihres Pfades. Platziere dann am Ende des Levels eine Wand mit dem
largeGround
-Sprite und füge ihr einen
Box Collider 2D hinzu.
Ziehe den Exit
-Sprite aus dem
Assets-Ordner in die Szene — er ist bereits im
Projekt enthalten. Platziere ihn in der Nähe der oberen Wand, so hoch,
dass die Ratte ihn nicht alleine erreichen kann. Um den
Ausgang zu erreichen, braucht der Spieler eine
Box als Hilfestufe.
Im Inspector für das Exit-Objekt:
0
.DeadEndController
hinzu (wir nutzen es
zum Neuladen der Szene).
Jetzt wird beim Berühren des Ausgangs durch die Ratte die Szene neu geladen – aber nur, wenn du es geschafft hast, hinaufzuklettern.
Um ein Gefühl von Tiefe zu erzeugen, lassen wir den Hintergrund leicht mitbewegen, wenn sich der Spieler bewegt. Das nennt man Parallax-Effekt — er simuliert Tiefe, indem weiter entfernte Ebenen langsamer scrollen.
Du kannst das Hintergrundbild hier herunterladen: BackGround.png herunterladen
Importiere das Bild in dein Projekt und ziehe es in die Szene. Im Inspector:
X: 6
,
Y: 6
(je nach Bedarf anpassen).
-100
, damit
es hinter allem anderen dargestellt wird.
Erstelle ein neues MonoBehaviour
-Skript mit dem Namen
Parallax
. Füge diesen Code ein:
using UnityEngine;
public class Parallax : MonoBehaviour
{
// Spieler, dem wir folgen möchten
private GameObject player;
// Position des Spielers im vorherigen Frame
private Vector3 lastPosition;
// Parallax-Geschwindigkeit (je kleiner, desto langsamer bewegt sich der Hintergrund)
public float speed = 60f;
void Start()
{
// Finde den Spieler über das Tag
player = GameObject.FindWithTag("Player");
// Startposition speichern
if (player != null)
{
lastPosition = player.transform.position;
}
}
void Update()
{
// Falls kein Spieler gefunden wurde, nichts tun
if (player == null) return;
// Berechne die Bewegung des Spielers seit dem letzten Frame
Vector3 offset = player.transform.position - lastPosition;
// Bewege den Hintergrund entsprechend, aber langsamer – für den Parallaxeffekt
transform.Translate(offset * speed * Time.deltaTime);
// Speichere die aktuelle Spielerposition für den nächsten Frame
lastPosition = player.transform.position;
}
}
Hänge das Skript an das Hintergrundobjekt. Setze den
Speed-Wert — 60
ist ein guter
Startpunkt, aber du kannst gern experimentieren.
Das ist der Schritt, auf den alles leise hingeführt hat. Nicht die Münzen, nicht der Pilz, nicht einmal der Ausgang. Das hier — ist der Grund.
In den Assets gibt es eine lächelnde Wolke. Sie greift nicht an, bringt keine Punkte. Und doch — ist sie das Einzige, das bei dir bleibt. Immer.
Ziehe das Wolken-Sprite in die Szene und benenne es
Cloud. Setze seine Z-Position auf
0
und Order in Layer auf
-50
, damit es hinter der Ratte bleibt.
Platziere sie irgendwo über und hinter der Ratte — z. B. bei
(x - 2, y + 3)
. Präzision ist nicht nötig. Nur Präsenz.
Erstelle ein neues MonoBehaviour
-Skript mit dem Namen
CloudFollower
und hänge es an die Wolke. Verwende diesen
Code:
using UnityEngine;
public class CloudFollower : MonoBehaviour
{
// Versatz der Wolke relativ zum Spieler (nach links und oben)
public Vector3 offset = new Vector3(-2f, 3f, 0f);
// Geschwindigkeit, mit der die Wolke dem Spieler folgt
public float followSpeed = 1.5f;
// Referenz auf das Ziel (den Spieler)
private Transform target;
void Start()
{
// Suche das Objekt mit dem Tag "Player"
GameObject playerObj = GameObject.FindWithTag("Player");
if (playerObj != null)
{
target = playerObj.transform;
}
}
void Update()
{
// Wenn kein Ziel gefunden — nichts tun
if (target == null) return;
// Zielposition: Spielerposition + Versatz
Vector3 desiredPos = target.position + offset;
// Bewege die Wolke sanft zur Zielposition
transform.position = Vector3.Lerp(
transform.position, // aktuelle Position
desiredPos, // Zielposition
followSpeed * Time.deltaTime // Interpolation über die Zeit
);
}
}
Die Wolke folgt der Ratte jetzt — nicht perfekt, nicht exakt, sondern mit sanfter Verzögerung. Sie hilft nicht. Sie spricht nicht. Sie bleibt einfach.
Wenn du langsamer wirst oder fällst — sie wartet. Wenn du neu startest — sie ist wieder da. Du bist in diesem Level nie allein. Nicht wirklich.
Jetzt kannst du Münzen und Pilze hinzufügen. Eine Kiste unter die Plattform fallen lassen. Und — vielleicht — ein wenig länger in diesem Level bleiben, als geplant.
Alles wirkt einfach. Und genau das ist das Schöne daran. Versuch nicht sofort, das beste Spiel der Welt zu bauen. Versteh zuerst die Grundlagen: Bewegung, Kollisionen, Interaktionen.
Danach wirst du dein eigenes Spiel machen. Eines, das sich wirklich nach dir anfühlt.