Die Balken – als Spielgegner
Nun kommt die eigentliche Schwierigkeit ins Spiel. Balken, die von rechts nach links durch das Bild wandern und denen der Ball ausweichen muss.
Was wir dazu brauchen? Natürlich eine Klasse Balken, aus der mehrere Objekte erstellt werden können, und eine Kollisionserkennung, wenn der Ball den Balken berührt. Der Balken muss in seiner act()-Methode schrittweise in einem bestimmten Tempo nach links wandern. Wenn er aus dem Bild ist, kann er sinnvollerweise wieder von rechts kommen und sollte dann, um Abwechslung ins Spiel zu bringen, vielleicht zufällig variiert höher oder tiefer liegen.
Wir machen einfach eines nach dem anderen und beginnen mit der Klasse Balken. Die wird nach der Ball-Klasse definiert.
class Balken(Actor):
speed = 2
def act(self):
xpos = self.getX()
self.setX(xpos-self.speed)
Damit haben wir definiert, dass der Balken sich selbstständig immer 2 Pixel nach links pro Takt bewegt. Diese Geschwindigkeit wird durch die Eigenschaft speed festgelegt und kann später auch beliebig geändert werden.
Mit dieser act()-Methode würde der Balken kontinuierlich immer weiter nach links wandern. Damit das Spiel aber immer weitergehen kann, ist es natürlich angebracht, dass der Balken rechts wieder auftaucht, wenn er nach links aus dem Bild verschwunden ist.
Das könnte man einfach so machen:
if xpos < -10:
self.setX(810)
Ebenso wichtig ist aber auch die Frage nach der y-Position des Balkens. Denn wenn die immer gleich bliebe, wäre das Spiel langweilig. Besser ist es, wenn der Balken mal oben, mal unten ist, auf verschiedenen Höhen. Erst dann wird das Spiel abwechslungsreich. Der Balken ist 400 Pixel hoch. Er sollte sich entweder im y-Bereich von 0 bis 200 befinden (oberer Balken) oder im Bereich von 400 bis 600 (unterer Balken).
Die Position sollte zufällig sein, und auch die Frage, ob der Balken oben oder unten ist. Wir brauchen also einmal wieder das random-Modul, das ganz am Anfang des Programms wie immer importiert werden muss: from random import *
Nun können wir zufällig entscheiden lassen, ob der Balken oben oder unten sein soll:
if randint(0,1) == 0:
# Balken oben
self.setY(randint(0,200))
else:
# Balken unten
self.setY(randint(400,600))
Die erste randint-Zahl ist entweder 0 oder 1 – damit wird über oben und unten entschieden. Ist die Zahl 0, dann wird der Balken oben platziert, ist sie 1, ist er unten angesiedelt.
Je nachdem wird dann die Zufallsposition des Balkens entweder zwischen 0 und 200 oder zwischen 400 und 600 gesetzt.
Die ganze Klasse Balken sieht jetzt so aus:
class Balken(Actor):
speed = 2
def act(self):
xpos = self.getX()
self.setX(xpos-self.speed)
if xpos < -10:
self.setX(810)
if randint(0,1) == 0:
# Balken oben
self.setY(randint(0,200))
else:
# Balken unten
self.setY(randint(400,600))
Jetzt muss natürlich auch noch ein Balkenobjekt erstellt werden – bzw. mehrere, aber fangen wir mal mit einem an, um es zu testen (füge diesen Code unten vor feld.show() ein):
balken = Balken("sprites/bar3.gif")
feld.addActor(balken, Location (810,200))
Wir setzen den Balken an die Position x:810, y:200 – damit ist er rechts knapp außerhalb des Spielfeldes und vertikal oben platziert. Durch seine act()-Methode sollte er direkt nach dem Start ins Bild kommen und dann nach links wandern. Wenn er links aus dem Bild ist, sollte er rechts wieder erscheinen, diesmal an zufälliger y-Position, entweder als unterer oder oberer Balken.
Gleich mal ausprobieren …
Abbildung 21.4 Der Balken ist da und wandert durchs Bild.
Gut, nur ein Balken ist langweilig. Damit das Spiel spannend wird, empfehle ich vier Balken, die im Abstand von 200 Pixeln liegen. Da das Gesamtfeld eine Breite von 800 Pixeln hat, ist somit ein kontinuierlicher Fluss der Balken gegeben.
Die Balken müssen also am Anfang die x-Positionen 810, 1010, 1210 und 1410 haben. Es wäre überflüssig, vier einzelne Balken per Programm nacheinander zu erstellen, denn das kann man auch in einer Schleife machen. Entweder eine Schleife, die von 1 bis 4 zählt und sich die Positionen berechnet, oder gleich mit einer Liste, die die vier Positionen enthält und einfach durchlaufen wird.
Zum Beispiel so:
for xpos in [810,1010,1210,1410]:
balken = Balken("sprites/bar3.gif")
feld.addActor(balken, Location (xpos,randint(400,500)))
So werden vier Balken an ihren Startpositionen erstellt – die y-Position ist jeweils zufällig, aber immer irgendwo in der unteren Bildhälfte. Damit ist der Start des Spiels noch etwas einfacher, bis die Balken aus dem Bild sind, dann aber werden die vertikalen Positionen der Balken ja automatisch zufällig neu gesetzt. Sie können dann unten oder oben sein.
Ein neuer Test zeigt: Jetzt sind vier Balken vorhanden, die immer wieder neu variiert am rechten Rand erscheinen und nach links wandern.
Abbildung 21.5 So soll es aussehen. Bald ist das Spiel fertig …
Klar ist natürlich auch, was noch fehlt: Die Kollisionserkennung, denn jetzt kann der Ball noch seelenruhig durch die Balken hindurchfliegen. So ist es natürlich nicht gedacht.
Wir können die Kollisionserkennung sowohl im Ball-Objekt durchführen als auch an den Balken. Hier im Beispiel nehmen wir einfach den Ball, dem wir jeden Balken nach Erstellung als Kollisionsobjekt zuweisen müssen.
Die Balkenerstellung bekommt also eine zusätzliche Zeile nach der Definition jedes Balkens:
ball.addCollisionActor(balken)
Und der Ball braucht eine runde Kollisionsfläche. Nach der Erstellung des Balles ist also hier zum Beispiel folgende Zeile einzufügen:
ball.setCollisionCircle(Point(0,0),20)
Dann fehlt nur noch eines: Die collide()-Methode für den Ball. Also das, was passieren soll, wenn der Ball mit einem Balken zusammenstößt.
In die Klassendefinition für den Ball schreiben wir also noch folgende Methode hinein:
def collide(self,actor1,actor2):
spielEnde()
return 0
Das war alles: Jetzt ist die Basisversion des Spiels fertig. Ein Ball erscheint, der steuerbar ist, Balken wandern durchs Bild – und bei Kollision mit einem Balken oder mit dem oberen und unteren Rand ist das Spiel beendet. In die Nachricht bei Spielende schreiben wir jetzt einfach »GAME OVER«.
Hier kommt der gesamte Code für die simple Grundversion von Flappy Ball:
from gamegrid import *
from random import *
class Ball(Actor):
speed = 0.5
def act(self):
ypos = self.getY()
ypos += self.speed
if ypos < 0 or ypos > 600:
feld.refresh()
spielEnde()
self.setY(int(ypos))
self.speed += 0.2
def hochkicken(self):
self.speed = -4
def collide(self,actor1,actor2):
spielEnde()
return 0
class Balken(Actor):
speed = 2
def act(self):
xpos = self.getX()
self.setX(xpos-self.speed)
if xpos < -10:
self.setX(810)
if randint(0,1) == 0:
# Balken oben
self.setY(randint(0,200))
else:
# Balken unten
self.setY(randint(400,600))
def mausKlick(e):
ball.hochkicken()
return 0
def spielEnde():
feld.doPause()
msgDlg("GAME OVER")
feld = GameGrid(800, 600)
feld.setTitle("Flappy Ball")
feld.setBgColor(Color.WHITE)
feld.setSimulationPeriod(20)
feld.addMouseListener(mausKlick,1)
ball = Ball("sprites/peg_5.png")
ball.setCollisionCircle(Point(0,0),20)
feld.addActor(ball, Location (400,20))
for xpos in [810,1010,1210,1410]:
balken = Balken("sprites/bar3.gif")
feld.addActor(balken, Location (xpos,randint(400,500)))
ball.addCollisionActor(balken)
feld.show()
feld.doRun()
Abbildung 21.6 Das Spiel ist spielbar – wenn auch noch etwas roh!
Flappy Ball Version 1.0 läuft! Die grundlegende Spielmechanik ist komplett. Jetzt geht es wie bei den anderen Spielen auch um die Erweiterungen. Wie kann man das Spiel noch attraktiver machen? Wie immer steht es dir völlig frei, das Spiel selber zu erweitern. Ändere Geschwindigkeiten (Spieltakt, Ball, Balken), ändere Grafiken, füge Sound hinzu usw.