Dritter Schritt: Die Blöcke

Nun kommen also noch die Blöcke, die der Ball abschießen soll. Als Erstes muss eine Klasse Block erstellt werden, dann werden zahlreiche Blöcke platziert. Bewegen müssen die Blöcke sich nicht, aber sie müssen auf die Kollision mit dem Ball reagieren und sich dann auflösen. Sie brauchen also keine act()-Methode, aber eine collide()-Funktion.

Also ans Werk. Diese Zeilen kommen zu den anderen Klassendefinitionen:

class Block(Actor):
pass

Damit ist die Klasse Block erst einmal ins Leben gerufen. Nun müssen wir Blöcke erzeugen und auf dem Spielfeld platzieren. Wie viele sollen es sein? In meinem Beispiel sind es 17 Stück pro Reihe, drei Reihen untereinander. Das kannst du natürlich auch anders machen, wie du möchtest.

Als Bilder für die Blöcke kannst du entweder eigene Grafiken verwenden – oder die mitgelieferten Bilder seat_0.gif (grün), seat_1.gif (gelb) und seat_2.gif (rot).

Hier ist eine Möglichkeit, die erste Reihe Blöcke zu erstellen:

for xpos in range(0,17):
block = Block("sprites/seat_0.gif")
feld.addActor(block, Location(xpos*42+60,100))

Alles klar? Vielleicht nicht ganz. Wir verwenden hier nicht wie sonst die repeat-Schleife, sondern die for-Schleife mit range(). Warum? Na ja, repeat wäre schon auch gegangen, aber wir brauchen hier auf jeden Fall einen Zähler, der mitzählt, der wievielte Block gerade erzeugt wird – und dafür hätten wir bei repeat noch eine zusätzliche Zählvariable verwenden müssen, die wir auf 0 setzen und anschließend immer um 1 erhöhen müssten. Mit for und range() zählt xpos automatisch von 0 bis 17, und wir können dann xpos jedes Mal multiplizieren, um die korrekte Position auf dem Bildschirm zu bekommen – und 60 hinzuzählen, das ist der linke Rand. Die y-Position ist für diese Reihe immer gleich, nämlich 100.

Verstehst du den Code? Schau sonst auch noch mal Kapitel 11 über Listen an – da wird die for range()-Schleife behandelt. Es ist ohnehin gut, die for-range()-Schleife zu beherrschen, denn sie kommt in Standard-Python sehr oft vor (in Standard-Python existiert der repeat-Befehl ja nicht, wie schon erwähnt).

Wenn du das Programm jetzt startest, sieht das Bild so aus:

Eine grüne Reihe von Blöcken ist erschienen.

Abbildung 19.6    Eine grüne Reihe von Blöcken ist erschienen.

Jetzt noch zwei solche Reihen darunter, in anderen Farben, und dann ist unser Grund-Setup für das Spiel beisammen!

Das Erstellen aller drei Blockreihen kann in derselben Schleife erfolgen:

for xpos in range(0,17):
block = Block("sprites/seat_0.gif")
feld.addActor(block, Location(xpos*42+60,100))
block = Block("sprites/seat_1.gif")
feld.addActor(block, Location(xpos*42+60,160))
block = Block("sprites/seat_2.gif")
feld.addActor(block, Location(xpos*42+60,220))

Nun werden also in jedem Durchlauf der Schleife drei Blöcke untereinander erzeugt, deren y-Position bei 100, 160 und 220 liegt und deren x-Position in jedem Durchlauf berechnet wird.

Jetzt sind alle benötigten Spielelemente vorhanden!

Abbildung 19.7    Jetzt sind alle benötigten Spielelemente vorhanden!

Der nächste Schritt ist klar, oder? Logisch, die Blöcke sollen sich jetzt auflösen, wenn der Ball sie berührt. Weißt du, was dafür jetzt programmiert werden muss?

Es gibt zwei Möglichkeiten: Wir können jedem einzelnen Block den Ball als Kollisionsobjekt geben und in der collide-Methode von Block darauf reagieren – oder wir können dem Ball alle Blöcke als Kollisionsobjekte geben und in der collide-Methode des Balls reagieren. Beides funktioniert.

Ich entscheide mich hier einfach mal für die erste Methode. Die Erstellung der Blöcke wird jetzt also etwas erweitert: Jeder Block muss bei der Erstellung den Ball als Kollisionsobjekt erhalten.

Wir können das so machen, dass wir nach der Erstellung jedes Blocks den Befehl

block.addCollisionActor(ball)

einfügen. Oder wir können das gleich in die Klasse Block mit aufnehmen. Dann brauchen wir es nicht für jeden Block neu schreiben, sondern es nur einmal in die Klassendefinition des Blocks mit aufnehmen. Jeder Block erhält bei der Entstehung gleich automatisch das Kollisionsobjekt »ball« zugewiesen.

Um das zu erreichen, musst du es in die __init__()-Funktion der Block-Klasse schreiben. Du erinnerst dich? Die __init__()-Funktion wird immer bei der Erstellung eines Objekts ausgeführt.

Wie wäre es also so?

class Block(Actor):
def __init__(self):
self.addCollisionActor(ball)

Sieht auf den ersten Blick okay aus, ist es aber nicht, denn mit dieser __init__()-Funktion wird die zuvor vorhandene, aus der Klasse Actor geerbte, überschrieben. Diese macht aber eine Menge – unter anderem weist sie der Spielfigur eine Grafik zu usw. … Wir können auf die vorhandene __init__()-Funktion nicht verzichten, wollen sie aber dennoch erweitern. Wie geht das?

Ganz einfach so, indem wir eine neue __init__()-Funktion schreiben, die die alte aus der Klasse Actor als Erstes aufruft und danach ihre eigenen Befehle ausführt. Das geht so:

class Block(Actor):
def __init__(self, path):
Actor.__init__(self, path)
self.addCollisionActor(ball)

Damit wird also die normale init-Funktion aus Actor zuerst ausgeführt, dann wird dem Block als Kollisionsobjekt der Ball zugeordnet. All dies geschieht bei der Erstellung eines Block-Objekts von selbst, sodass wir der Erstellung der Blöcke nichts mehr hinzufügen müssen.

Was jetzt noch fehlt, ist natürlich die Kollisionsfunktion selbst für die Klasse Block – nämlich das, was passiert, wenn der Block den Ball berührt:

def collide(self,actor1,actor2):
feld.removeActor(self)
feld.refresh()
richtung = ball.getDirection()
neue_richtung = 360-richtung
ball.setDirection(neue_richtung)
return 0

actor1 ist in diesem Fall der berührte Block – der wird bei Berührung mit dem Ball vom Spielfeld entfernt. Und noch etwas passiert: Der Ball prallt gleichzeitig vom berührten Block ab. Er ändert also seine Richtung. Wir machen es einfach und setzen eine waagerechte Abprall-Linie voraus (360 richtung).

Nun kannst du wieder testen. Das Spiel fühlt sich immer mehr an wie ein richtiges Breakout-Spiel.

So soll es sein: Der Ball löscht die farbigen Blöcke!

Abbildung 19.8    So soll es sein: Der Ball löscht die farbigen Blöcke!

Sicher fallen dir jetzt noch mehrere Dinge auf, die wir ändern müssen. Fangen wir mit dem ersten Problem an:

Offensichtlich werden die Blöcke nur gelöscht, wenn der Ball ihren Mittelpunkt berührt. Streift er sie nur an der Seite, bleiben sie stehen. Das soll nicht so sein und lässt sich korrigieren, indem wir jedem einzelnen Block ein Kollisionsrechteck mitgeben – mit setCollisionRectangle().

Das Kollisionsrechteck können wir jetzt ebenfalls in die __init__()-Funktion der Blockklasse schreiben – dann müssen wir es auch nicht mehr einzeln setzen. Fügen wir also hinzu:

self.setCollisionRectangle(Point(10,10),30,30)

Dann sollte es besser funktionieren. Du kannst mit den Werten auch etwas herumspielen, um zu sehen, wie es am besten geht. Teste, bis du zufrieden bist!