Gamedev Challenge - Breakout in 10 Minuten
Ich habe euch Spieleentwicklung in Unity versprochen, also sollt ihr Spieleentwicklung in Unity kriegen. Zugebenermaßen etwas anders, als ich mir das ursprünglich dachte, aber das soll mich erstmal nicht stören. Der Anlass ist, dass ich ganz gerne die Unity Tutorials von Brackeys anschaue und der vorletzte Woche eine “10 Minute Game Challenge” ausgerufen hat. Die Aufgabe ist, in 10 Minuten ein Spiel zu entwickeln, wobei vorbereitete Grafiken erlaubt sind. Selbst hat er einen minimalistischen Klon von Super Hexagon gebaut:
Ich fand die Idee gut und habe dafür tatsächlich zum ersten Mal seit vielen vielen Jahren ein richtiges Video aufgenommen hab. Sogar mit Facecam. Falls es jemanden interessiert, kann ich auch gerne mal einen Post darüber schreiben, wie mein Aufnahmesetup funktioniert (vielen Dank hier an poetBLUE und Björn the Smexy, die mich da beraten haben).
Aber zurück zum Thema… ich hab den Arcadeklassiker Breakout nachgebaut, wenn auch natürlich bei weitem nicht vollständig. Als Engine hab ich genau wie Brackeys Unity mit einem 2D-Szenensetup verwendet. Allerdings sind meine Objekte tatsächlich dreidimensional, weil Unity direkt über ein Kontextmenü Quader und Kugeln erstellen kann, aber keine zweidimensionalen Rechtecke und Kreise. Dafür hätte ich Sprites vorbereiten müssen und nachdem ich schon über eine Stunde damit zugebracht hatte, Unity zu aktualisieren und meine Aufnahme einzurichten, hatte ich darauf echt keine Lust.
Ich gestehe, dass ich das meiste, was ich in dem Video mache, vorher schonmal Off Screen ausprobiert habe. Allerdings habe ich alles aus dem Gedächtnis nochmal neu gebaut und auch ein paar Sachen anders gemacht, als im ersten Versuch, also zählt das, oder? Leider habe ich die 10 Minuten trotzdem nicht ganz eingehalten. Das Ergebnis könnt ihr übrigens dank WebGL auch direkt im Browser spielen:
Um Zeit zu sparen, habe ich mich ziemlich auf die eingebaute Physikengine verlassen, auch wenn die viel mehr tut, als ich für so ein einfaches Spiel bräuchte. Um keine Probleme mit Reibungsverlusten zu kriegen, hab ich Physics Material mit Dynamic Friction = 0
, Static Friction = 0
und Bounciness = 1
erstellt und großzügig auf alle Spielelemente angewendet. Der Ball behält also alle Energie, egal mit was er kollidiert. Ursprünglich wollte ich Bounciness
auf einen Wert größer als 1 setzen, damit der Ball jedes Mal, wenn er abprallt, etwas schneller wird, aber leider lässt Unity das nicht zu.
Der Code hinter dem Spiel ist so kurz, dass ich ihn in voller Länge hier einbinden könnte. Ich kürze ihn aber zumindest so weit, dass kein unnötiger Schrott wie nicht benötigte Usings, Standardkommentare oder leere Methoden drin sind.
using UnityEngine;
public class Ball : MonoBehaviour {
void Start ()
{
var rigidbody = GetComponent<Rigidbody>();
rigidbody.velocity = new Vector3(0, 10f, 0);
}
}
Der einzige Code, der für den Ball ausgeführt wird, sorgt dafür, dass er beim Spielstart gerade nach oben beschleunigt wird. Nicht weiter spannend.
using UnityEngine;
public class Paddle : MonoBehaviour
{
private float _movement;
void Update ()
{
_movement = Input.GetAxis("Horizontal");
}
void FixedUpdate()
{
transform.Translate(_movement * Time.fixedDeltaTime * 15f, 0, 0);
}
void OnCollisionEnter(Collision collision)
{
collision.rigidbody.velocity += new Vector3(_movement, 0, 0);
}
}
Der Schläger ist ein bisschen komplexer, aber nicht viel. Das Bewegen hätte man vielleicht noch von FixedUpdate
in Update
verschieben können, um Zeit zu sparen, aber so ist es sauberer und ich speichere _movement
ohnehin, um es bei einer Kollision auf die Geschwindigkeit des Balls zu addieren. Das ist die simpelste Möglichkeit, um dafür zu sorgen, dass der Spieler die Flugrichtung des Balls beeinflussen kann. Deutlich eleganter wäre hier aber, zu prüfen, wo sich der Ball relativ zur Mitte des Schlägers befindet. Das ist weniger fummelig und simuliert in etwa einen Schläger mit einer leicht gebogenen Oberfläche. Außerdem sollte die Bewegung des Schlägers beschränkt werden, damit er das Spielfeld nicht verlassen kann.
using UnityEngine;
public class Brick : MonoBehaviour {
void OnCollisionEnter(Collision collision)
{
Destroy(gameObject);
}
}
Ganz unspektakulär: wenn irgendwas mit einem Stein kollidiert, wird er entfernt.
using UnityEngine;
using UnityEngine.SceneManagement;
public class Bottom : MonoBehaviour {
void OnTriggerEnter(Collider collider)
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
Am Boden befindet sich ein unsichtbarer Block mit Is Trigger = true
, was bedeutet, dass er Kollisionen registriert, aber nichts von ihm abprallt. Den Trick, wie man für ein Game Over in einer Zeile die Szene neu lädt, habe ich tatsächlich aus dem Challenge Video von Brackeys gelernt. Sehr nützlich für so eine Challenge und auch ein paar andere Teilnehmer (z.B. Sykoo) verwenden das.
Insgesamt bin ich ganz zufrieden und werde vielleicht sogar irgendwann nochmal ein bisschen Zeit investieren, um das Spiel tatsächlich so weit zu kriegen, dass es Spaß macht. Deshalb hier schonmal eine kurze Liste, was ich alles ändern oder ergänzen könnte:
- Bewegung des Schlägers nach links und rechts begrenzen
- Etwas Trägheit für den Schläger, dann kann man auch feiner justieren
- Bei Kollisionen prüfen, ob wir auch wirklich mit dem Objekt kollidieren, das wir erwarten
- Abprallen des Balls wie oben beschrieben von der Position statt von der Geschwindigkeit des Schlägers abhängig machen
- Verhindern, dass der Ball in einen Zustand kommt, wo er (fast) genau horizontal zwischen den Seitenwänden hin und her pendelt und den Schläger nie mehr erreicht
- Punktezähler
- Steine, die mehr als einen Treffer aushalten
- Sounds
- Sprites
So oder so dürft ihr euch gerne herausgefordert fühlen, auch mal ein Spiel in 10 Minuten zu entwickeln. Schickt mir das Ergebnis auf jeden Fall oder noch besser nehmt ein Video auf, so wie ich.