Vor etwa einem Monat hatte ich mit einem Tutorial begonnen, das das Spiel »Alien Invaders« mit Pygame programmieren wollte. In der Zwischenzeit bin ich jedoch mit Pygame Zero vertrauter geworden und hatte festgestellt, daß einem Pygame Zero tatsächlich sehr viele Zeilen Quellcode erspart. Daher habe ich einen Cornerturn vollzogen und das Spiel in Pygame Zero neu programmiert.
Zuerst einmal die Fassung aus oben verlinktem Tutorial statt in Pygame in Pygame Zero:
import pgzrun WIDTH = 600 HEIGHT = 600 TITLE = "Alien Invaders 1" player_ship = Actor("playership") player_ship.centerx = WIDTH/2 player_ship.bottom = HEIGHT - 20 player_speed = 5 def update(): if keyboard.left: player_ship.centerx -= player_speed if player_ship.left <= 0: player_ship.left = 0 elif keyboard.right: player_ship.centerx += player_speed if player_ship.right >= WIDTH: player_ship.right = WIDTH def draw(): screen.blit(images.background, (0, 0)) player_ship.draw() pgzrun.go()
Diese Version macht immer noch nicht mehr, als daß das Raumschiff des Spielers mit den Pfeiltasten hin und her bewegt werden kann, aber anstatt der 110 Zeilen Quellcode in der Pygame-Version kommt Pygame Zero mit 26 Zeilen Quellcode aus. Das halte ich schon für eine beachtliche Reduktion auf das Wesentliche.
Bevor die gefürchteten Aliens auftauchen, wollte ich den Spieler mit einer Waffe ausstatten, die es ihm erlaubt, sich gegen diese gefährlichen Gegner zu wehren. Dazu benötigte ich erst einmal einen weiteren Actor
und einen Flag:
bullet = Actor("laserred16") bullet_speed = 25 bullet_fire = False
Der Flag soll sicherstellen, daß imer nur eine Kugel im Spiel ist, denn den Spieler mit der Möglichkeit eines Dauerfeuers auszustatten, hielt ich gegenüber den Aliens für unfair. Das habe ich innerhalb der Update-Methode so realisiert:
if not bullet_fire: bullet.bottom = player_ship.bottom bullet.centerx = player_ship.centerx if keyboard.SPACE: bullet_fire = True if bullet_fire: bullet.bottom -= bullet_speed if bullet.bottom <= 0: bullet_fire = False
Zu Beginn der update()
-Methode muß der Flag bullet_fire
natürlich als global
deklariert werden.
In der draw()
-Methode muß nur darauf geachtet werden, daß die Kugel vor dem Raumschiff gezeichnet wird, damit sie im passiven Zustand vom Raumschiff verdeckt wird:
def draw(): screen.blit(images.background, (0, 0)) bullet.draw() player_ship.draw()
Nun ist schon einiges an Funktionalität hinzugekommen, aber die Länge des Quellcodes ist mit 41 Zeilen immer noch überschaubar.
Bevor ich eine Horde von Aliens auf den Spieler loslassen wollte, wollte ich erst einmal mit einem Alien üben. Auch dieses muß natürlich erst initialisiert werden:
def reset_enemy(): return (randint(25, WIDTH - 25), randint(-200, -50)) enemy = Actor("enemyred") enemy.pos = reset_enemy() enemy_speed = 5 score = 0 game_over = False
Da der Spieler nun auch verlieren kann, werden noch ein paar zusätzliche Flags und Variablen deklariert, die auch alle zu Beginn der update()
-Methode als global
vereinbart werden mußten.
Die Funktion reset_enemy()
gibt eine zufällige Position oberhalb des Spielefensters zurück, die als Startposition des Gegners gilt. Dafür muß zu Beginn mit
from random import randint
die entsprechende Zufallsfunktion aus Pythons Standardbibliothek importiert werden.
Die Zeilen des Quellcodes, die das Alien-Schiff betreffen werden an den Schluß der update()
-Methode angehängt:
enemy.top += enemy_speed bullet_hit = bullet.colliderect(enemy) if bullet_hit: enemy.pos = reset_enemy() bullet.bottom = player_ship.bottom bullet.centerx = player_ship.centerx bullet_fire = False score += 1 enemy_hit = enemy.colliderect(player_ship) if enemy_hit: game_over = True enemy.pos = reset_enemy() player_speed = enemy_speed = bullet_speed = 0 if enemy.top >= HEIGHT: score -= 1 enemy.pos = reset_enemy() if score <= 0: player_speed = enemy_speed = bullet_speed = score = 0 game_over = True
Das Alien-Schiff bewegt sich auf den unteren Fensterrand zu und dann wird überprüft, ob es mit einer Kugel des Spielers kollidiert. Die Klasse Actor
aus Pygame Zero erbt alle Methoden der Rect
-Klasse aus Pygame, so auch die Methode colliderect()
. Trifft also eine Kugel das feindliche Schiff, wird dieses wieder mit reset_enemy()
in den Orbit zurückgeschossen, von wo aus es einen neuen Angriff starten kann. Die Kugel wiederum wird in den Zustand versetzt, der es dem Spieler erlaubt, erneut eine Kugel abzuschießen. Außerdem bekommt der Spieler einen Punkt gutgeschrieben.
Kollidiert dagegen das feindliche Schiff mit dem Raumschiff des Spielers wird alles zurück oder auf Null gesetzt und das Spiel ist zu Ende (game_over = True
).
Das Spiel kann jedoch noch auf eine andere Art zu Ende gehen. Denn jedesmal, wenn ein Alien-Raumschiff dem Spieler entkommt – das heißt am unteren Fensterrand verschwindet – verliert der Spieler einen Punkt. Und wenn der Punktestand null oder weniger Punkte aufweist, ist das Spiel ebenfalls zu Ende.
Die draw()
-Methode sieht immer noch sehr übersichtlich aus:
def draw(): screen.blit(images.background, (0, 0)) enemy.draw() bullet.draw() player_ship.draw() screen.draw.text("Score: " + str(score), (10, 10)) if game_over: screen.draw.textbox("Game Over!", (95, 200, 400, 80), color = "red")
Neu sind eigentlich nur die beiden Aufrufe zum Zeichnen von Text auf den Bildschirm. Pygame Zero besitzt eine reichhaltige Text API, doch auch sie ist mit sinnvollen Default-Werten vorbelegt, so daß man erst einmal wenig dazu basteln muß – in diesem Fall nur die Größe und Farbe des »Game Over«-Textes.
Diese Fassung ist im Quellcode jetzt 75 Zeilen lang, auch das kann man wirklich nicht als übermäßig geschwätzig bezeichnen.
Dank der bisherigen Vorarbeit ist eine Erweiterung auf viele Aliens nur noch ein Kinderspiel. Zuerst die Initialisierung als Liste
:
num_enemies = 5 enemies = [] for _ in range(num_enemies): enemy = Actor("enemyred") enemy.pos = reset_enemy() enemies.append(enemy) enemy_speed = 2
Ohne zu üben war das Spiel bei mir immer zu schnell verloren, daher habe ich mit enemy_speed = 2
die Geschwindigkeit der feindlichen Raumschiffe drastisch herabgesetzt. Die Leserinnen und Leser sind aufgefordert, hier mit eigenen Werten zu experimentieren.
Der update()
-Methode mußte eigentlich nur ein
for enemy in enemies:
vorangestellt, werden, alles nachfolgenden (neu eingerückten!) Zeilen funktionieren wie in Stage 3.
Das komplette (und vollständige) Programm sieht daher so aus:
import pgzrun from random import randint WIDTH = 600 HEIGHT = 600 TITLE = "Alien Invaders 4" player_ship = Actor("playership") player_ship.centerx = WIDTH/2 player_ship.bottom = HEIGHT - 20 player_speed = 5 bullet = Actor("laserred16") bullet_speed = 25 bullet_fire = False def reset_enemy(): return (randint(25, WIDTH - 25), randint(-200, -50)) num_enemies = 5 enemies = [] for _ in range(num_enemies): enemy = Actor("enemyred") enemy.pos = reset_enemy() enemies.append(enemy) enemy_speed = 2 score = 0 game_over = False def update(): global bullet_fire, score, enemy_speed, bullet_speed, player_speed, game_over if keyboard.left: player_ship.centerx -= player_speed if player_ship.left <= 0: player_ship.left = 0 elif keyboard.right: player_ship.centerx += player_speed if player_ship.right >= WIDTH: player_ship.right = WIDTH if not bullet_fire: bullet.bottom = player_ship.bottom bullet.centerx = player_ship.centerx if keyboard.SPACE: bullet_fire = True if bullet_fire: bullet.bottom -= bullet_speed if bullet.bottom <= 0: bullet_fire = False for enemy in enemies: enemy.top += enemy_speed bullet_hit = bullet.colliderect(enemy) if bullet_hit: enemy.pos = reset_enemy() bullet.bottom = player_ship.bottom bullet.centerx = player_ship.centerx bullet_fire = False score += 1 enemy_hit = enemy.colliderect(player_ship) if enemy_hit: game_over = True enemy.pos = reset_enemy() player_speed = enemy_speed = bullet_speed = 0 if enemy.top >= HEIGHT: score -= 1 enemy.pos = reset_enemy() if score <= 0: player_speed = enemy_speed = bullet_speed = score = 0 game_over = True def draw(): screen.blit(images.background, (0, 0)) for enemy in enemies: enemy.draw() bullet.draw() player_ship.draw() screen.draw.text("Score: " + str(score), (10, 10)) if game_over: screen.draw.textbox("Game Over!", (95, 200, 400, 80), color = "red") pgzrun.go()
Das ist ein funktionierendes Spiel in nur 81 Zeilen Python-Code, kürzer geht es wohl kaum. Diese Einfachheit motiviert, mich noch tiefer in Pygame Zero hineinzuknien. Schaun wir einfach mal, was mir noch einfällt …
Das Hintergrundbild habe ich einem freien Set (CC-BY-3.0) von Jacob Zinman-Jeanes entnommen. Es ist etwas größer als benötigt, Ihr könnt daher mit der Weite des Bildschirms noch ein wenig spielen.
Die Raumschiffe und die Laserkugel entstammen wieder dem freien (CC0 1.0 Universal), schier unerschöpflichen Fundus von Kenney.nl. Ich habe die Bildchen mit der Bildverarbeitung meines Vertrauens auf die notwenige Größe zurechtgestutzt.
Den derzeitigen Stand des Quellcodes wie auch alle Bilder könnt Ihr meinem GitLab-Repositorium entnehmen.
Über …
Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter Rentner, Autor, Netzaktivist und Hundesportler — Reihenfolge rein zufällig). Hier steht, was mir gefällt. Wem es nicht gefällt, der braucht ja nicht mitzulesen. Wer aber mitliest, ist herzlich willkommen und eingeladen, mitzudiskutieren!
Alle eigenen Inhalte des Schockwellenreiters stehen unter einer Creative-Commons-Lizenz, jedoch können fremde Inhalte (speziell Videos, Photos und sonstige Bilder) unter einer anderen Lizenz stehen.
Der Besuch dieser Webseite wird aktuell von der Piwik Webanalyse erfaßt. Hier können Sie der Erfassung widersprechen.
Diese Seite verwendet keine Cookies. Warum auch? Was allerdings die iframes
von Amazon, YouTube und Co. machen, entzieht sich meiner Kenntnis.
Werbung