Der Mausinator¶
Lernziele
- Gruppenkollisionen zwischen verschiedenen Sprite-Gruppen
- Soundeffekte laden und abspielen
- Event: Tastendruck (
py.KEYDOWN) vs. Taste loslassen (py.KEYUP) - Dynamisches Erzeugen von Sprites (Explosionen)
Übersicht: Das Spiel¶
Du wirst ein komplettes Spiel programmieren: Die Maus schiesst auf Münzen und versucht, alle in Zeit zu eliminieren.
- Maus: Kann mit Pfeiltasten links/rechts bewegt werden
- Münzen: Animierte Münzen an zufälligen Positionen
- Schüsse: Werden mit der Leertaste abgefeuert
- Explosionen: Animiert, wenn ein Schuss eine Münze trifft
- Soundeffekte: Schussgeräusch + Explosionsgeräusch
- Endbildschirm: Zeigt die benötigte Zeit
Vorlage: Lade hier einf_pygame_06_mausinator.zip herunter
Schritt 1: Soundeffekte laden und initialisieren¶
Soundeffekte müssen vor der Hauptschleife geladen werden.
-
Füge nach der Schriftinitialisierung folgende Zeilen hinzu:
# Sounds laden schusse = py.mixer.Sound("sound/gun-shot.wav") schusse.set_volume(0.3) explosion = py.mixer.Sound("sound/explosion-012.mp3") explosion.set_volume(0.4)py.mixer.Sound()lädt die Audiodateiset_volume()regelt die Lautstärke (0.0 = stumm, 1.0 = maximal)- Die Dateien befinden sich im
sound/Ordner der Vorlage
-
Die Sounds werden später in der Hauptschleife mit
py.mixer.Sound.play()abgespielt.
Schritt 2: Die neuen Sprite-Klassen¶
Die Bullet-Klasse¶
Schüsse sind bewegliche Objekte, die sich selbst nach oben bewegen.
Erstelle eine neue Klasse Bullet:
class Bullet(py.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = py.image.load("res/bullet.png").convert()
self.rect = self.image.get_rect()
self.rect.centerx = x
self.rect.y = y
self.step_size = 20
def update(self):
# Schuss nach oben bewegen
self.rect.y = self.rect.y - self.step_size
# Wenn der Schuss oben angekommen, löschen
if self.rect.y + self.rect.height < 0:
self.kill()
Detaillierte Erklärung der update()-Funktion¶
Die update()-Funktion wird von Pygame bei jedem Schleifendurchgang automatisch aufgerufen (wenn du all_sprites.update() aufrufst). Sie hat zwei Aufgaben:
1. Schuss nach oben bewegen:
Dies geschieht bei jedem Durchgang. Wenn der Schuss z.B. bei y=500 ist und step_size=20:
- Durchgang: y = 500 - 20 = 480
- Durchgang: y = 480 - 20 = 460
- Durchgang: y = 460 - 20 = 440
- ...und so weiter
Warum subtrahieren? Weil in Pygame die y-Achse von oben nach unten läuft (y=0 ist oben, y=800 ist unten).
2. Speicher freigeben, wenn der Schuss das Fenster verlässt:
Dies ist sehr wichtig! Schüsse dürfen nicht endlos weiterfliegen, sonst verbrauchen sie Speicher.
Wie funktioniert die Bedingung?
Bullet-Logik
self.rect.y= Position des oberen Randes des Schussesself.rect.height= Höhe des Schusses (z.B. 10 Pixel)self.rect.y + self.rect.height= Position des unteren Randes des Schusses
Wenn der untere Rand kleiner als 0 ist, bedeutet das: Der Schuss ist komplett oben aus dem Fenster raus!
Beispiel: Schuss hat Höhe 10 und oberer Rand ist bei y=-5 - Unterer Rand: -5 + 10 = 5 (noch sichtbar, nicht gelöscht)
Schuss hat Höhe 10 und oberer Rand ist bei y=-12 - Unterer Rand: -12 + 10 = -2 (komplett weg, gelöscht!)
Wichtig: self.kill() entfernt das Objekt aus allen Gruppen (all_bullets und all_sprites), sodass es nicht mehr gezeichnet oder aktualisiert wird.
Die Explosion-Klasse¶
Explosionen sind animierte visuellen Effekte, die automatisch verschwinden, wenn die Animation zu Ende ist.
Erstelle die Explosion Klasse:
class Explosion(py.sprite.Sprite):
def __init__(self, center):
super().__init__()
# 40 Bilder für die Explosionsanimation
self.animation = load_images("res/explosion/", "e", ".gif", 40, 128, 128)
self.costume = 0
self.image = self.animation[self.costume]
self.rect = self.image.get_rect()
self.rect.center = center
def update(self):
self.costume = (self.costume + 1) % 40
self.image = self.animation[self.costume]
# Nach der Animation (Bild 38) löschen
if self.costume > 38:
self.kill()
Detaillierte Erklärung der Explosion-Klasse¶
Unterschied zu anderen Sprites: Eine Explosion wird nur einmal erzeugt, lädt alle ihre Bilder auf einmal, spielt die Animation ab und löscht sich dann selbst.
In der __init__()-Funktion:
self.animation gespeichert.
Dies ist neu! Der Parameter center ist eine Koordinate (x, y) – die Stelle, wo die Münze getroffen wurde. Mit self.rect.center = center wird die Explosion genau dort positioniert.
Die update()-Funktion erklärt:
Diese Funktion wird bei jedem Schleifendurchgang aufgerufen und hat zwei Aufgaben:
1. Animation abspielen:
Das Prozentzeichen % ist der Modulo-Operator. Hier ist, was passiert:
| Durchgang | Rechnung | Ergebnis | Bedeutung |
|---|---|---|---|
| 1. | (0 + 1) % 40 = 1 | costume = 1 | Zeige Bild 1 |
| 2. | (1 + 1) % 40 = 2 | costume = 2 | Zeige Bild 2 |
| ... | ... | ... | ... |
| 39. | (38 + 1) % 40 = 39 | costume = 39 | Zeige Bild 39 |
| 40. | (39 + 1) % 40 = 0 | costume = 0 | Zeige Bild 0 (Schleife) |
| 41. | (0 + 1) % 40 = 1 | costume = 1 | Zeige Bild 1 (wieder vorne) |
Die Modulo-Operation sorgt dafür, dass costume immer zwischen 0 und 39 bleibt – die Animation wird endlos wiederholt!
2. Animation stoppen und löschen:
Aber wir wollen die Explosion ja nicht endlos laufen lassen! Daher wird geprüft:
- Wenn
costumegrösser als 38 ist (also 39), dann ist die Animation fast vorbei - Mit
self.kill()wird die Explosion gelöscht
Warum > 38 statt > 39?
Weil die Bilder von 0-39 gehen (40 Bilder insgesamt). Wenn costume = 39, haben wir das letzte Bild gerade angezeigt. Beim nächsten Durchgang würde costume = 0 und die Animation würde von vorne beginnen. Mit der Bedingung > 38 stoppen wir dies und löschen die Explosion nach dem letzten Bild.
Zusammengefasst:
- Die Explosion spielt 40 Bilder ab (0-39)
- Bei jedem Durchgang wird das nächste Bild gezeigt
- Nach Bild 39 wird die Explosion aus dem Speicher entfernt
- Alles geschieht automatisch durch all_sprites.update() in der Hauptschleife
Schritt 3: Gruppenkollisionen - Teil 2¶
Im Gegensatz zu vorher überprüfen wir jetzt Kollisionen zwischen zwei Gruppen (Schüsse vs. Münzen).
-
Verwende
py.sprite.groupcollide()stattspritecollide():def is_a_collision(): # groupcollide(group1, group2, dokill_group1, dokill_group2, collided=None) kollisionsliste = py.sprite.groupcollide(all_bullets, all_coins, True, True, collided=py.sprite.collide_mask) if len(kollisionsliste) != 0: # Für jede Kollision eine Explosion erstellen for i in kollisionsliste.values(): for j in i: all_sprites.add(Explosion(j.rect.center)) return True else: return FalseErklärung der Parameter: -
all_bullets: Gruppe der Schüsse -all_coins: Gruppe der Münzen -True, True: Beide Gruppen-Objekte sollen gelöscht werden bei Kollision -collided=py.sprite.collide_mask: Pixel-perfekte Kollisionserkennung - Rückgabewert: Dictionary mit Kollisionen:{Bullet: [Coin]} -
Wenn Kollisionen gefunden wurden, erstelle für jede Münze eine Explosion an ihrer Position.
Schritt 4: Tasten-Events verarbeiten¶
Unterscheide zwischen verschiedenen Tastendrücken:
-
Ergänze die Event-Verarbeitung in der Hauptschleife:
for event in py.event.get(): if event.type == py.QUIT: game_is_running = False # Taste LOSGELASSEN (nicht gedrückt!) elif event.type == py.KEYDOWN and event.key == py.K_ESCAPE: game_is_running = False # Leertaste GEDRÜCKT elif event.type == py.KEYDOWN and event.key == py.K_SPACE: bullet = Bullet(maus.rect.centerx - 7, maus.rect.y) bullet.rect.y -= bullet.rect.height all_bullets.add(bullet) all_sprites.add(bullet) py.mixer.Sound.play(schusse)Wichtig: -
py.KEYDOWN: Taste wurde soeben gedrückt -py.KEYUP: Taste wurde soeben losgelassen (wird hier nicht verwendet) - Schüsse werden nur erzeugt, wenn die Leertaste gedrückt wird -
Pfeiltasten werden weiterhin mit
py.key.get_pressed()in derkey_handler()Funktion verarbeitet.
Schritt 5: Hauptschleife zusammenbinden¶
Die Hauptschleife folgt diesem Ablauf:
- Events verarbeiten (Fenster schliessen, Schuss abfeuern)
- Tastatur abfragen (Pfeiltasten für Bewegung)
- Kollisionen prüfen (Schuss trifft Münze?)
- Sprites aktualisieren (
all_sprites.update()) - Alles zeichnen (Hintergrund, Sprites, Text)
while game_is_running:
# Events
for event in py.event.get():
# ...
if which_window == 0:
# Zeit messen
counter = (py.time.get_ticks() - start) // 1000
# Tasten verarbeiten
key_handler()
# Kollisionen prüfen
if is_a_collision():
anzahl_coins = len(all_coins)
py.mixer.Sound.play(explosion)
# Update
all_sprites.update()
# Zeichnen
screen.fill([255, 255, 255])
all_sprites.draw(screen)
draw_score(anzahl_coins, counter)
# Spiel beendet?
if anzahl_coins == 0:
which_window = 1
time_needed = counter
else:
# Endbildschirm
screen.fill([255, 255, 255])
draw_ende(time_needed)
py.display.update()
clock.tick(fps)
Teste dein Spiel.
Erweiterte Aufgaben¶
-
Verstehen: Starte das Programm und spiele es. Versuche alle Münzen zu treffen.
-
Soundlautstärke ändern: Erhöhe oder senke die Lautstärke der Soundeffekte durch Anpassung von
set_volume(). -
Schwierigkeit erhöhen:
- Erhöhe
anzahl_coinsauf 10 - Verringere
step_sizeder Schüsse auf 15 (langsamer) - Münzen schneller machen: Erhöhe die Anzahl der Animationsbilder
- Erhöhe
-
(s) Neue Sprite-Klasse: Erstelle einen
Enemy, der sich von oben nach unten bewegt. Schüsse sollen auch den Enemy treffen können (Explosion). -
(s) Score-System: Zähle die Anzahl der getroffenen Münzen und zeige sie im Endbildschirm an.
-
(s) Weitere Soundeffekte: Füge einen Sound hinzu, der abgespielt wird, wenn das Spiel endet.