Heyho, bin ein Newbie in der Pythonprogrammierung.
Ich sitze seit Tagen an einem Problem.
Es handelt sich um einen 2D-Shoot'Em Up.
Wenn die Lebensenergie des Enemy01 oder Enemy02 auf Null ist, dann soll der Gegner mit all seinen Aktionen verschwinden. Tut er aber nicht. Er schwirrt einfach weiter durch das Bild und schießt Bullets.
Was ist mein Fehler? Wäre voll dankbar, wenn jemand mir einen Tipp geben kann. Auch mit der kill funktion klappt es nicht. Ich komm einfach nicht weiter Bitte helft mir!
import pygame
import os
import time
import math
from pygame import mixer
import random
class Settings:
window_width = 1920
window_height = 1080
window_border = 205
file_path = os.path.dirname(os.path.abspath(__file__))
image_path = os.path.join(file_path, "images")
class Background03(object):
def __init__(self, filename):
self.image = pygame.image.load(os.path.join(Settings.image_path, filename))
self.image = pygame.transform.scale(self.image, (Settings.window_width, Settings.window_height)).convert_alpha()
self.rect = self.image.get_rect()
def draw(self, screen):
screen.blit(self.image, (self.rect.left, self.rect.top))
class Defender(pygame.sprite.Sprite):
def __init__(self, filename):
super().__init__()
self.image = pygame.image.load(os.path.join(Settings.image_path, filename))
self.image = pygame.transform.scale(self.image, (100, 220)).convert_alpha()
self.rect = self.image.get_rect()
self.rect.centerx = Settings.window_width // 2
self.rect.bottom = Settings.window_height - Settings.window_border + 117
self.direction = 0
self.speed = 5
self.lives = 3
heart_image = pygame.image.load(os.path.join(Settings.image_path, "glühbirne.png")).convert_alpha()
heart_width = 70 # Neue Breite des Herz-Bildes
heart_height = 70 # Neue Höhe des Herz-Bildes
self.hearts_images = [
pygame.transform.scale(heart_image, (heart_width, heart_height))
for _ in range(self.lives)
]
self.points = 0
self.font = pygame.font.Font("3Dventure.ttf", 55)
def draw(self, screen):
screen.blit(self.image, (self.rect.left, self.rect.top))
hearts_x = Settings.window_border # Startposition der Herzen von links
heart_spacing = -15 # Abstand zwischen den Herzen
for i, heart_image in enumerate(self.hearts_images):
heart_rect = heart_image.get_rect()
heart_rect.left = 160 + hearts_x + i * (heart_spacing + heart_rect.width)
heart_rect.bottom = Settings.window_height - 10
screen.blit(heart_image, heart_rect)
self.draw_score(screen)
def draw_score(self, screen):
score_text = self.font.render(f"Score: {self.points}", True, (255, 255, 255)) # Score-Text rendern
score_rect = score_text.get_rect()
score_rect.width = 200 # Breite des Rechtecks anpassen
score_rect.bottomright = (Settings.window_width - 700, Settings.window_height - 5) # Position des Score-Texts
screen.blit(score_text, score_rect) # Score-Text auf dem Bildschirm anzeigen
def update(self):
newrect = self.rect.left + self.direction * self.speed
if newrect <= Settings.window_border:
self.move_stop()
if newrect >= Settings.window_width - Settings.window_border - self.rect.width:
self.move_stop()
self.rect.move_ip(self.direction * self.speed, 0)
#self.points += 1 # Punkte erhöhen
def move_left(self):
self.direction = -1
def move_right(self):
self.direction = 1
def move_stop(self):
self.direction = 0
def increase_score(self, amount):
self.points += amount
def show_menu(points):
pass
class Enemy01(pygame.sprite.Sprite):
def __init__(self, filename):
super().__init__()
self.image = pygame.image.load(os.path.join(Settings.image_path, filename))
self.image = pygame.transform.scale(self.image, (150, 180)).convert_alpha()
self.rect = self.image.get_rect()
self.rect.centerx = 500
self.rect.bottom = 245
self.direction_horizontal = 1
self.speed_horizontal = 5
self.amplitude = 4 # Amplitude der vertikalen Bewegung
self.frequency = 0.02 # Frequenz der vertikalen Bewegung
self.phase_shift = 0 # Phasenverschiebung der vertikalen Bewegung
self.horizontal_counter = 0
self.wall_collisions = 0
self.max_lives = 100
self.lives = self.max_lives
self.health_bar_width = 100
self.health_bar_height = 10
self.defender = defender
self.sound = mixer.Sound(f"treffer_silbenroboter.mp3")
def draw(self, screen):
screen.blit(self.image, (self.rect.left, self.rect.top))
self.draw_health_bar(screen)
def draw_health_bar(self, screen):
remaining_health = self.lives
health_bar_width = int((remaining_health / self.max_lives) * self.health_bar_width)
health_bar_rect = pygame.Rect(0, 0, health_bar_width, self.health_bar_height)
health_bar_rect.centerx = self.rect.centerx
health_bar_rect.top = self.rect.top - 20
if remaining_health >= 71:
color = (0, 255, 0) # Grün
elif remaining_health >= 41:
color = (255, 255, 0) # Gelb
elif remaining_health >= 11:
color = (255, 165, 0) # Orange
else:
color = (255, 0, 0) # Rot
pygame.draw.rect(screen, color, health_bar_rect)
def update(self):
self.horizontal_counter += 1
newrect = self.rect.left + self.direction_horizontal * self.speed_horizontal
if self.defender.rect.colliderect(self.rect):
self.defender.increase_score(10) # 10 Punkte zum Score hinzufügen
if newrect <= Settings.window_border or newrect >= Settings.window_width - Settings.window_border - self.rect.width:
self.switch_horizontal_direction()
self.wall_collisions += 1
self.rect.move_ip(self.direction_horizontal * self.speed_horizontal, self.calculate_vertical_movement())
if self.rect.bottom <= 200:
self.rect.bottom = 200
self.switch_vertical_direction()
elif self.rect.bottom >= 550:
self.rect.bottom = 550
self.switch_vertical_direction()
if self.wall_collisions >= 3:
self.direction_horizontal *= -1
self.wall_collisions = 0
def switch_horizontal_direction(self):
self.direction_horizontal *= -1
def switch_vertical_direction(self):
self.phase_shift = self.horizontal_counter / (2 * math.pi * self.frequency)
def calculate_vertical_movement(self):
vertical_position = self.amplitude * math.sin(self.frequency * self.horizontal_counter + self.phase_shift)
if self.rect.bottom + vertical_position >= 550:
return 550 - self.rect.bottom
elif self.rect.bottom + vertical_position <= 200:
return 200 - self.rect.bottom
else:
return vertical_position
def hit_by_rocket(self):
self.sound.play() # Sound abspielen
self.lives -= 50
if self.lives <= 0:
self.is_to_remove = True
return False
class Enemy01Bullet(pygame.sprite.Sprite):
def __init__(self, variant, enemy, defender):
super().__init__()
self.variant = variant
self.image = pygame.image.load(os.path.join(Settings.image_path, f"{variant}.png"))
self.image = pygame.transform.scale(self.image, (50, 50)).convert_alpha()
self.rect = self.image.get_rect()
self.rect.centerx = enemy.rect.centerx
self.rect.top = enemy.rect.bottom - 60
self.direction = 1
self.speed = 5
self.is_to_remove = False
self.sound = mixer.Sound(f"{variant}.mp3")
self.defender = defender
def draw(self, screen):
screen.blit(self.image, (self.rect.left, self.rect.top))
def update(self):
self.rect.move_ip(0, self.direction * self.speed)
if self.rect.bottom <= 0:
self.is_to_remove = True
if self.defender.rect.colliderect(self.rect):
self.sound.play() # Sound abspielen
self.defender.increase_score(10) # Punktzahl um 10 erhöhen
self.is_to_remove = True
class Enemy02(pygame.sprite.Sprite):
def __init__(self, filename):
super().__init__()
self.image = pygame.image.load(os.path.join(Settings.image_path, filename))
self.image = pygame.transform.scale(self.image, (88.5, 60)).convert_alpha()
self.rect = self.image.get_rect()
self.rect.centerx = 1420
self.rect.bottom = 245
self.direction_horizontal = -1
self.speed_horizontal = 3
self.max_lives = 50
self.lives = self.max_lives
self.health_bar_width = 100
self.health_bar_height = 10
self.defender = defender
self.is_stopped = False
self.stop_duration = 1000 # Duration to stop in milliseconds
self.stop_timer = 0
self.sound = mixer.Sound(f"treffer_beep.mp3")
def draw(self, screen):
screen.blit(self.image, (self.rect.left, self.rect.top))
self.draw_health_bar(screen)
def draw_health_bar(self, screen):
remaining_health = self.lives
health_bar_width = int((remaining_health / self.max_lives) * self.health_bar_width)
health_bar_rect = pygame.Rect(0, 0, health_bar_width, self.health_bar_height)
health_bar_rect.centerx = self.rect.centerx
health_bar_rect.top = self.rect.top - 20
if remaining_health >= 35:
color = (0, 255, 0) # Grün
elif remaining_health >= 25:
color = (255, 255, 0) # Gelb
elif remaining_health >= 15:
color = (255, 165, 0) # Orange
else:
color = (255, 0, 0) # Rot
pygame.draw.rect(screen, color, health_bar_rect)
def update(self):
newrect_horizontal = self.rect.left + self.direction_horizontal * self.speed_horizontal
if self.defender.rect.colliderect(self.rect):
self.defender.increase_score(10) # 10 Punkte zum Score hinzufügen
if newrect_horizontal <= Settings.window_border or newrect_horizontal >= Settings.window_width - Settings.window_border - self.rect.width:
self.switch_horizontal_direction()
self.rect.move_ip(self.direction_horizontal * self.speed_horizontal, 0)
# Neue Feuer-Logik für Enemy02Bullet
if random.randint(0, 100) < 0.002: # Probability of shooting (2% chance here)
enemy_bullet = Enemy02Bullet(self, defender)
all_enemy_bullets.add(enemy_bullet)
if self.is_stopped:
# Check if the stop duration is over
current_time = pygame.time.get_ticks()
if current_time - self.stop_timer >= self.stop_duration:
self.is_stopped = False
self.stop_timer = 0
else:
# Enemy02 is still stopped, don't move
return
newrect_horizontal = self.rect.left + self.direction_horizontal * self.speed_horizontal
if self.defender.rect.colliderect(self.rect):
self.defender.increase_score(10) # 10 Punkte zum Score hinzufügen
if newrect_horizontal <= Settings.window_border or newrect_horizontal >= Settings.window_width - Settings.window_border - self.rect.width:
self.switch_horizontal_direction()
self.rect.move_ip(self.direction_horizontal * self.speed_horizontal, 0)
# Check if it's time to stop
if pygame.time.get_ticks() % 3000 < 100: # Stop for 100 milliseconds every 3000 milliseconds (3 seconds)
self.is_stopped = True
self.stop_timer = pygame.time.get_ticks()
def switch_horizontal_direction(self):
self.direction_horizontal *= -1
def hit_by_rocket(self):
self.sound.play() # Sound abspielen
self.lives -= 10
if self.lives <= 0:
self.is_to_remove = True
return False
class Enemy02Bullet(pygame.sprite.Sprite):
def __init__(self, enemy, defender):
super().__init__()
self.image = pygame.image.load(os.path.join(Settings.image_path, "strom.png")) # Lade das Bild für den Drop
self.image = pygame.transform.scale(self.image, (33, 33)).convert_alpha() # Skaliere das Bild auf die gewünschte Größe
self.rect = self.image.get_rect()
self.rect.centerx = enemy.rect.centerx
self.rect.centery = enemy.rect.centery + 25
self.direction = 2 # Der Drop bewegt sich vertikal nach unten
self.speed = 6 # Geschwindigkeit des Drops anpassen, falls nötig
self.is_to_remove = False
self.defender = defender
self.shoot_prob = 100000 # Setze die Wahrscheinlichkeit auf 1/10 (10%)
self.sound = mixer.Sound(f"bomb.mp3")
self.sound2 = mixer.Sound(f"katzewirdvomlasergetroffen.mp3")
def draw(self, screen):
screen.blit(self.image, (self.rect.left, self.rect.top))
def update(self):
self.rect.move_ip(0, self.direction * self.speed)
if self.rect.top >= Settings.window_height:
self.is_to_remove = True
if self.defender.rect.colliderect(self.rect):
self.sound2.play() # Sound abspielen
self.defender.lives -= 1
if self.defender.lives <= 0:
show_menu(self.defender.points)
pygame.quit()
return True
else:
self.defender.hearts_images.pop()
self.is_to_remove = True
class Rocket(pygame.sprite.Sprite):
def __init__(self, filename, defender):
super().__init__()
self.image = pygame.image.load(os.path.join(Settings.image_path, filename))
self.image = pygame.transform.scale(self.image, (72, 72)).convert_alpha()
self.rect = self.image.get_rect()
self.rect.centerx = defender.rect.centerx
self.rect.bottom = defender.rect.top + 40
self.direction = -1
self.speed = 5
self.is_to_remove = False
def draw(self, screen):
screen.blit(self.image, (self.rect.left, self.rect.top))
def update(self):
self.rect.move_ip(0, self.direction * self.speed)
if self.rect.bottom <= 0:
self.is_to_remove = True
def check_collision(enemy, rocket):
if enemy.rect.colliderect(rocket.rect):
return True
return False
def check_enemy_bullet_collision(enemy_bullet, rocket):
if enemy_bullet.rect.colliderect(rocket.rect):
enemy_bullet.sound.play() # Sound abspielen
all_rockets.remove(rocket) # Rakete entfernen
all_enemy_bullets.remove(enemy_bullet) # Feindliches Projektil entfernen
defender.increase_score(10) # 10 Punkte zum Score hinzufügen
return True
return False
def show_menu(points):
pass
if __name__ == '__main__':
os.environ['SDL_VIDEO_WINDOWS_POS'] = "10, 50"
pygame.init()
pygame.mixer.init() # Initialisiere Pygame Mixer
screen = pygame.display.set_mode((Settings.window_width, Settings.window_height))
clock = pygame.time.Clock()
all_rockets = pygame.sprite.Group()
last_rocket_shot_time = pygame.time.get_ticks() # Neue Variable für den Zeitpunkt des letzten Raketenabschusses
background03 = Background03("backgroundlevel01.png")
defender = Defender("cathero.png")
enemy01 = Enemy01("enemy01.png")
enemy02 = Enemy02("enemy02.png")
enemy01.is_to_remove = False
enemy02.is_to_remove = False
rocket = Rocket("rocket.png", defender)
all_enemy_bullets = pygame.sprite.Group()
bullet_spawn_prob = 0.0001 # Setze die Wahrscheinlichkeit auf 1/10 (10%)
enemy_bullet_last_shot_time = pygame.time.get_ticks()
bulletSound = mixer.Sound("katzenball.mp3")
rockets_to_remove = [Rocket]
enemy_bullets_to_remove = [Enemy02Bullet, Enemy01Bullet]
pygame.mixer.music.load("soundtrack.wav") # Lade die Melodie
pygame.mixer.music.set_volume(0.3) # Setze die Lautstärke (0.0-1.0)
pygame.mixer.music.play(-1) # Spiele die Melodie endlos (-1)
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
defender.move_left()
elif event.key == pygame.K_RIGHT:
defender.move_right()
elif event.key == pygame.K_SPACE:
current_time = pygame.time.get_ticks()
time_since_last_shot = current_time - last_rocket_shot_time
if time_since_last_shot >= 650: # Prüfen, ob die erforderliche Zeit seit dem letzten Abschuss vergangen ist
bulletSound.play()
new_rocket = Rocket("rocket.png", defender)
new_rocket.update()
all_rockets.add(new_rocket)
last_rocket_shot_time = current_time
elif event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
defender.move_stop()
current_time = pygame.time.get_ticks()
time_since_last_shot = current_time - enemy_bullet_last_shot_time
time_since_last_bullet = current_time - enemy_bullet_last_shot_time
if time_since_last_bullet >= 6000 and random.randint(0, 10) < bullet_spawn_prob:
enemy_bullet = Enemy02Bullet(enemy02, defender)
all_enemy_bullets.add(enemy_bullet)
enemy_bullet_last_shot_time = current_time
if time_since_last_shot >= 1000:
variant = random.choice(["LU", "IS", "MA", "EI"])
enemy_bullet = Enemy01Bullet(variant, enemy01, defender)
all_enemy_bullets.add(enemy_bullet)
enemy_bullet_last_shot_time = current_time
for rocket in all_rockets:
if check_collision(enemy01, rocket):
all_rockets.remove(rocket)
if enemy01.hit_by_rocket():
show_menu(enemy01.points)
running = True
defender.update()
enemy01.update()
enemy02.update()
all_rockets.update()
for rocket in all_rockets:
if rocket.is_to_remove:
all_rockets.remove(rocket)
all_enemy_bullets.update()
for enemy_bullet in all_enemy_bullets:
if enemy_bullet.is_to_remove:
all_enemy_bullets.remove(enemy_bullet)
for rocket in all_rockets:
for enemy_bullet in all_enemy_bullets:
if check_enemy_bullet_collision(enemy_bullet, rocket):
break
for rocket in all_rockets:
if check_collision(enemy02, rocket):
all_rockets.remove(rocket)
if enemy02.hit_by_rocket():
show_menu(enemy02.points)
running = False
screen.fill((0, 0, 0))
background03.draw(screen)
enemy01.draw(screen)
enemy02.draw(screen)
defender.draw(screen)
all_rockets.draw(screen)
all_enemy_bullets.draw(screen)
pygame.display.flip()
pygame.quit()
Pygame Objekt entfernen
- __blackjack__
- User
- Beiträge: 13185
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@fatpossum: Warum denkst Du denn der Gegner sollte verschwinden? Ich sehe keinen Code der das bewirken würde. Also auch keinen der da irgendwas machen würde, was dann aber nicht funktioniert, da ist einfach nichts entsprechendes. Da ist Code der Bedingungslos bei beiden Gegnern immer `update()` und `draw()` aufruft, also werden die halt auch immer aktualisiert und gezeichnet.
Grundsätzlich könntest Du den Quelltext mal aufräumen und überarbeiten wenn Du ihn öffentlich zeigst. Um es helfenden leichter zu machen sich darin zurecht zu finden.
Also beispielsweise Sachen rauswerfen die nicht gar nicht gebraucht werden, wie der Import des `time`-Moduls, oder die `show_menu()`-Funktion die *zweimal* definiert wird, und beide Definitionen machen einfach gar nichts. Dann muss so etwas auch nicht im Quelltext stehen.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Dann fällt auf, das an einigen Stellen im Code einfach so auf globale Variablen zugegriffen wird. Das sollte nicht sein.
Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).
Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Der Name soll dem Leser vermitteln was der Wert dahinter im Programm bedeutet, nicht zum rätseln zwingen.
Man nummeriert keine Namen. Dann will man sich entweder bessere Namen überlegen, oder gar keine Einzelnamen/-werte verwenden, sondern eine Datenstruktur. Oft eine Liste. In einigen Fällen kann eine Nummer auch einfach sinnlos sein, wie bei `background03`, wo es ja gar keine anderen Nummern gibt‽
In Fall von `Background03` macht die Klasse auch keinen wirklichen Sinn. Und es macht nicht wirklich Sinn ein Surface mit einer Farbe zu füllen, wenn gleich der nächste Schritt ein anderes Surface darüber blittet das genau so gross ist.
Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.
Falsche Kommentare sind schlimmer als keine Kommentare. Ein Kommentar soll eventuelle Fragen mit dem Code klären, aber wenn dort falsche Informationen drin stehen, oder gar welche die dem Code direkt wiedersprechen, erreicht man genau das Gegenteil. Der Leser weiss dann nicht was falsch ist, der Code oder der Kommentar.
Zum Beispiel wenn im Kommentar steht die Wahrscheinlichkeit wird auf 1/10 gesetzt, im Code dann aber 0.0001 steht. Oder an anderer Stelle dann der gleiche Kommentar, als Wert aber 100000 (Das dort gesetzte Attribut wird übrigends dann nirgends verwendet!).
Die Klasse `Settings` sollte es so nicht geben. Da wird ja niemals ein Exemplar von erstellt, die wird einfach nur als Namensraum für ein paar Konstanten missbraucht.
Von `object` zu erben ist überflüssig, das passiert auch ohne das man das hin schreibt.
Der Code zum laden, skalieren, und konvertieren mit Alphakanal einer Bilddatei steht so verdammt oft immer wieder im Quelltext, einen deutlicheren Kandidaten für eine Funktion kann es kaum geben.
Die `hearts_images`-Liste macht keinen Sinn. Da ist immer `self.lives` mal das gleiche Bild enthalten, wobei sich die Bilder überhaupt nicht voneinander unterscheiden. Da kann man das Bild auch einfach *einmal* vorhalten und dann `self.lives` mal zeichnen.
Die `blit()`-Funktion kann als zweites Argument ein `Rect`-Objekt entgegen nehmen, womit das öfter vorkommente ``(self.rect.left, self.rect.top)`` einfach nur ``self.rect`` wäre. Man muss es ja nicht umständlicher schreiben als es sein muss.
Ein `f` vor einer literalen Zeichenkette macht keinen Sinn wenn dann gar kein Platzhalter darin vorkommt.
`Surface.get_rect()` kann man Argumente mitgeben, die an so einigen Stellen den Code vereinfachen können.
Beim Namen `newrect` erwartet der Leser ein `Rect`-Objekt und keine ganze Zahl.
``lives -= 50`` legt nahe das der Name `lives` falsch gewählt ist weil das dann gar nicht die Anzahl der Leben ist.
`hit_by_rocket()` gibt immer `False` zurück. Das kann man sich dann auch sparen, genau wie die ``if``\s beim Aufruf die dann ja nie was bewirken.
Nach Ablauf der `__init__()`-Methode sollte das Objekt in einem vollständig initialisierten Zustand sein. Insbesondere sollten nicht später noch irgendwelche neuen Attribute eingeführt werden wie `is_to_remove` — und das auch noch von aussen.
In `Enemy02.update()` steht doppelter Code der da sehr wahrscheinlich nur einmal stehen sollte.
`rockets_to_remove` und `enemy_bullets_to_remove` werden definiert, aber nirgends verwendet. Diese Listen mit Klassen machen auch gar keinen Sinn.
`current_time` sollte man nur einmal pro Hauptschleifendurchlauf ermitteln.
`time_since_last_shot` und `time_since_last_bullet` werden direkt nacheinander berechnet und enthalten deshalb immer den gleichen Wert. Und haben ja auch die gleiche Bedeutung. Also sollten das nicht zwei verschiedene Namen sein.
Das erste mal wo etwas an den Namen `rocket` gebunden wird, da wird dieses Objekt niemals für irgendwas benutzt.
Die Reihenfolge in der da `update()` aufgerufen wird und auf Kollisionen zwischen dem ersten Gegner und dem zweiten Gegner getestet wird, und wann was entfernt werden soll, ist ziemlich verwirrend. Die beiden Schleifen die Kollisionen mit Raketen verarbeiten machen auch das gleiche, bloss halt mit einem anderen Gegnerobjekt. Das wäre also mindestens mal eine Funktion.
Eine `Sprite.update()`-Methode sollte auf keinen Fall so etwas wie `pygame.quit()` aufrufen.
Zwischenstand (ungetestet):
Dieses `is_to_remove` auf allen Objekten ist komisch, insbesondere wenn es Sprites sind, die ja eine `kill()`-Methode haben.
``def draw()`` auf `Sprite`-Objekten ist komisch, weil dafür eigentlich die `draw()`-Methode auf Gruppen-Objekten vorgesehen ist.
Insgesamt ist in den Klassen zu viel gemeinsames das man in eine oder mehrere Basisklassen herausziehen sollte.
Die `main()`-Funktion ist auch zu umfangreich für meinen Geschmack.
Grundsätzlich könntest Du den Quelltext mal aufräumen und überarbeiten wenn Du ihn öffentlich zeigst. Um es helfenden leichter zu machen sich darin zurecht zu finden.
Also beispielsweise Sachen rauswerfen die nicht gar nicht gebraucht werden, wie der Import des `time`-Moduls, oder die `show_menu()`-Funktion die *zweimal* definiert wird, und beide Definitionen machen einfach gar nichts. Dann muss so etwas auch nicht im Quelltext stehen.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Dann fällt auf, das an einigen Stellen im Code einfach so auf globale Variablen zugegriffen wird. Das sollte nicht sein.
Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).
Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Der Name soll dem Leser vermitteln was der Wert dahinter im Programm bedeutet, nicht zum rätseln zwingen.
Man nummeriert keine Namen. Dann will man sich entweder bessere Namen überlegen, oder gar keine Einzelnamen/-werte verwenden, sondern eine Datenstruktur. Oft eine Liste. In einigen Fällen kann eine Nummer auch einfach sinnlos sein, wie bei `background03`, wo es ja gar keine anderen Nummern gibt‽
In Fall von `Background03` macht die Klasse auch keinen wirklichen Sinn. Und es macht nicht wirklich Sinn ein Surface mit einer Farbe zu füllen, wenn gleich der nächste Schritt ein anderes Surface darüber blittet das genau so gross ist.
Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.
Falsche Kommentare sind schlimmer als keine Kommentare. Ein Kommentar soll eventuelle Fragen mit dem Code klären, aber wenn dort falsche Informationen drin stehen, oder gar welche die dem Code direkt wiedersprechen, erreicht man genau das Gegenteil. Der Leser weiss dann nicht was falsch ist, der Code oder der Kommentar.
Zum Beispiel wenn im Kommentar steht die Wahrscheinlichkeit wird auf 1/10 gesetzt, im Code dann aber 0.0001 steht. Oder an anderer Stelle dann der gleiche Kommentar, als Wert aber 100000 (Das dort gesetzte Attribut wird übrigends dann nirgends verwendet!).
Die Klasse `Settings` sollte es so nicht geben. Da wird ja niemals ein Exemplar von erstellt, die wird einfach nur als Namensraum für ein paar Konstanten missbraucht.
Von `object` zu erben ist überflüssig, das passiert auch ohne das man das hin schreibt.
Der Code zum laden, skalieren, und konvertieren mit Alphakanal einer Bilddatei steht so verdammt oft immer wieder im Quelltext, einen deutlicheren Kandidaten für eine Funktion kann es kaum geben.
Die `hearts_images`-Liste macht keinen Sinn. Da ist immer `self.lives` mal das gleiche Bild enthalten, wobei sich die Bilder überhaupt nicht voneinander unterscheiden. Da kann man das Bild auch einfach *einmal* vorhalten und dann `self.lives` mal zeichnen.
Die `blit()`-Funktion kann als zweites Argument ein `Rect`-Objekt entgegen nehmen, womit das öfter vorkommente ``(self.rect.left, self.rect.top)`` einfach nur ``self.rect`` wäre. Man muss es ja nicht umständlicher schreiben als es sein muss.
Ein `f` vor einer literalen Zeichenkette macht keinen Sinn wenn dann gar kein Platzhalter darin vorkommt.
`Surface.get_rect()` kann man Argumente mitgeben, die an so einigen Stellen den Code vereinfachen können.
Beim Namen `newrect` erwartet der Leser ein `Rect`-Objekt und keine ganze Zahl.
``lives -= 50`` legt nahe das der Name `lives` falsch gewählt ist weil das dann gar nicht die Anzahl der Leben ist.
`hit_by_rocket()` gibt immer `False` zurück. Das kann man sich dann auch sparen, genau wie die ``if``\s beim Aufruf die dann ja nie was bewirken.
Nach Ablauf der `__init__()`-Methode sollte das Objekt in einem vollständig initialisierten Zustand sein. Insbesondere sollten nicht später noch irgendwelche neuen Attribute eingeführt werden wie `is_to_remove` — und das auch noch von aussen.
In `Enemy02.update()` steht doppelter Code der da sehr wahrscheinlich nur einmal stehen sollte.
`rockets_to_remove` und `enemy_bullets_to_remove` werden definiert, aber nirgends verwendet. Diese Listen mit Klassen machen auch gar keinen Sinn.
`current_time` sollte man nur einmal pro Hauptschleifendurchlauf ermitteln.
`time_since_last_shot` und `time_since_last_bullet` werden direkt nacheinander berechnet und enthalten deshalb immer den gleichen Wert. Und haben ja auch die gleiche Bedeutung. Also sollten das nicht zwei verschiedene Namen sein.
Das erste mal wo etwas an den Namen `rocket` gebunden wird, da wird dieses Objekt niemals für irgendwas benutzt.
Die Reihenfolge in der da `update()` aufgerufen wird und auf Kollisionen zwischen dem ersten Gegner und dem zweiten Gegner getestet wird, und wann was entfernt werden soll, ist ziemlich verwirrend. Die beiden Schleifen die Kollisionen mit Raketen verarbeiten machen auch das gleiche, bloss halt mit einem anderen Gegnerobjekt. Das wäre also mindestens mal eine Funktion.
Eine `Sprite.update()`-Methode sollte auf keinen Fall so etwas wie `pygame.quit()` aufrufen.
Zwischenstand (ungetestet):
Code: Alles auswählen
import math
import os
import random
import pygame
from pygame import mixer
WINDOW_WIDTH = 1920
WINDOW_HEIGHT = 1080
WINDOW_BORDER = 205
IMAGE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "images")
def load_image(filename, size):
return pygame.transform.scale(
pygame.image.load(os.path.join(IMAGE_PATH, filename)), size
).convert_alpha()
class Defender(pygame.sprite.Sprite):
def __init__(self, filename):
super().__init__()
self.image = load_image(filename, (100, 220))
self.rect = self.image.get_rect(
centerx=WINDOW_WIDTH // 2,
bottom=WINDOW_HEIGHT - WINDOW_BORDER + 117,
)
self.direction = 0
self.speed = 5
self.lives = 3
self.heart_image = load_image("glühbirne.png", (70, 70))
self.points = 0
self.font = pygame.font.Font("3Dventure.ttf", 55)
def draw(self, screen):
screen.blit(self.image, self.rect)
heart_spacing = -15
heart_rect = self.heart_image.get_rect()
for i in range(self.lives):
heart_rect.left = (
160 + WINDOW_BORDER + i * (heart_spacing + heart_rect.width)
)
heart_rect.bottom = WINDOW_HEIGHT - 10
screen.blit(self.heart_image, heart_rect)
text = self.font.render(f"Score: {self.points}", True, (255, 255, 255))
rect = text.get_rect(
bottomright=(WINDOW_WIDTH - 700, WINDOW_HEIGHT - 5)
)
rect.width = 200
screen.blit(text, rect)
def update(self):
if not (
WINDOW_WIDTH - WINDOW_BORDER - self.rect.width
<= self.rect.left + self.direction * self.speed
<= WINDOW_BORDER
):
self.move_stop()
self.rect.move_ip(self.direction * self.speed, 0)
def move_left(self):
self.direction = -1
def move_right(self):
self.direction = 1
def move_stop(self):
self.direction = 0
def increase_score(self, amount):
self.points += amount
class Enemy01(pygame.sprite.Sprite):
def __init__(self, filename, defender):
super().__init__()
self.image = load_image(filename, (150, 180))
self.rect = self.image.get_rect(centerx=500, bottom=245)
self.direction_horizontal = 1
self.speed_horizontal = 5
self.amplitude = 4
self.frequency = 0.02
self.phase_shift = 0
self.horizontal_counter = 0
self.wall_collisions = 0
self.max_lives = 100
self.lives = self.max_lives
self.health_bar_width = 100
self.health_bar_height = 10
self.defender = defender
self.sound = mixer.Sound("treffer_silbenroboter.mp3")
self.is_to_remove = False
def draw(self, screen):
screen.blit(self.image, self.rect)
remaining_health = self.lives
health_bar_width = int(
(remaining_health / self.max_lives) * self.health_bar_width
)
health_bar_rect = pygame.Rect(
0, 0, health_bar_width, self.health_bar_height
)
health_bar_rect.centerx = self.rect.centerx
health_bar_rect.top = self.rect.top - 20
if remaining_health >= 71:
color = (0, 255, 0) # Grün
elif remaining_health >= 41:
color = (255, 255, 0) # Gelb
elif remaining_health >= 11:
color = (255, 165, 0) # Orange
else:
color = (255, 0, 0) # Rot
pygame.draw.rect(screen, color, health_bar_rect)
def update(self):
self.horizontal_counter += 1
if self.defender.rect.colliderect(self.rect):
self.defender.increase_score(10)
if not (
WINDOW_BORDER
< self.rect.left
+ self.direction_horizontal * self.speed_horizontal
< WINDOW_WIDTH - WINDOW_BORDER - self.rect.width
):
self.switch_horizontal_direction()
self.wall_collisions += 1
self.rect.move_ip(
self.direction_horizontal * self.speed_horizontal,
self.calculate_vertical_movement(),
)
if self.rect.bottom <= 200:
self.rect.bottom = 200
self.switch_vertical_direction()
elif self.rect.bottom >= 550:
self.rect.bottom = 550
self.switch_vertical_direction()
if self.wall_collisions >= 3:
self.direction_horizontal *= -1
self.wall_collisions = 0
def switch_horizontal_direction(self):
self.direction_horizontal *= -1
def switch_vertical_direction(self):
self.phase_shift = self.horizontal_counter / (
2 * math.pi * self.frequency
)
def calculate_vertical_movement(self):
vertical_position = self.amplitude * math.sin(
self.frequency * self.horizontal_counter + self.phase_shift
)
if self.rect.bottom + vertical_position >= 550:
return 550 - self.rect.bottom
elif self.rect.bottom + vertical_position <= 200:
return 200 - self.rect.bottom
else:
return vertical_position
def hit_by_rocket(self):
self.sound.play()
self.lives -= 50
if self.lives <= 0:
self.is_to_remove = True
class Enemy01Bullet(pygame.sprite.Sprite):
def __init__(self, variant, enemy, defender):
super().__init__()
self.image = load_image(f"{variant}.png", (50, 50))
self.rect = self.image.get_rect(
centerx=enemy.rect.centerx, top=enemy.rect.bottom - 60
)
self.direction = 1
self.speed = 5
self.is_to_remove = False
self.sound = mixer.Sound(f"{variant}.mp3")
self.defender = defender
def draw(self, screen):
screen.blit(self.image, self.rect)
def update(self):
self.rect.move_ip(0, self.direction * self.speed)
if self.rect.bottom <= 0:
self.is_to_remove = True
if self.defender.rect.colliderect(self.rect):
self.sound.play()
self.defender.increase_score(10)
self.is_to_remove = True
class Enemy02(pygame.sprite.Sprite):
def __init__(self, filename, defender):
super().__init__()
self.image = load_image(filename, (88.5, 60))
self.rect = self.image.get_rect(centerx=1420, bottom=245)
self.direction_horizontal = -1
self.speed_horizontal = 3
self.max_lives = 50
self.lives = self.max_lives
self.health_bar_width = 100
self.health_bar_height = 10
self.defender = defender
self.is_stopped = False
self.stop_duration = 1000 # Duration to stop in milliseconds
self.stop_timer = 0
self.sound = mixer.Sound("treffer_beep.mp3")
self.is_to_remove = False
def draw(self, screen):
screen.blit(self.image, self.rect)
remaining_health = self.lives
health_bar_width = int(
(remaining_health / self.max_lives) * self.health_bar_width
)
health_bar_rect = pygame.Rect(
0, 0, health_bar_width, self.health_bar_height
)
health_bar_rect.centerx = self.rect.centerx
health_bar_rect.top = self.rect.top - 20
if remaining_health >= 35:
color = (0, 255, 0) # Grün
elif remaining_health >= 25:
color = (255, 255, 0) # Gelb
elif remaining_health >= 15:
color = (255, 165, 0) # Orange
else:
color = (255, 0, 0) # Rot
pygame.draw.rect(screen, color, health_bar_rect)
def update(self, enemy_bullets):
if self.defender.rect.colliderect(self.rect):
self.defender.increase_score(10)
if not (
WINDOW_BORDER
< self.rect.left
+ self.direction_horizontal * self.speed_horizontal
< WINDOW_WIDTH - WINDOW_BORDER - self.rect.width
):
self.switch_horizontal_direction()
self.rect.move_ip(self.direction_horizontal * self.speed_horizontal, 0)
if random.randint(0, 100) == 0:
enemy_bullet = Enemy02Bullet(self, self.defender)
enemy_bullets.add(enemy_bullet)
if self.is_stopped:
# Check if the stop duration is over
current_time = pygame.time.get_ticks()
if current_time - self.stop_timer >= self.stop_duration:
self.is_stopped = False
self.stop_timer = 0
else:
return
#
# Stop for 100 milliseconds every 3000 milliseconds (3 seconds)
#
if pygame.time.get_ticks() % 3000 < 100:
self.is_stopped = True
self.stop_timer = pygame.time.get_ticks()
def switch_horizontal_direction(self):
self.direction_horizontal *= -1
def hit_by_rocket(self):
self.sound.play()
self.lives -= 10
if self.lives <= 0:
self.is_to_remove = True
class Enemy02Bullet(pygame.sprite.Sprite):
def __init__(self, enemy, defender):
super().__init__()
self.image = load_image("strom.png", (33, 33))
self.rect = self.image.get_rect(
center=(enemy.centerx, enemy.centery + 25)
)
self.direction = 2
self.speed = 6
self.is_to_remove = False
self.defender = defender
self.sound = mixer.Sound("bomb.mp3")
self.sound2 = mixer.Sound("katzewirdvomlasergetroffen.mp3")
def draw(self, screen):
screen.blit(self.image, self.rect)
def update(self):
self.rect.move_ip(0, self.direction * self.speed)
if self.rect.top >= WINDOW_HEIGHT:
self.is_to_remove = True
if self.defender.rect.colliderect(self.rect):
self.sound2.play()
self.defender.lives -= 1
if self.defender.lives <= 0:
#
# TODO Hier etwas sinnvolles machen (oder das woanders tun).
#
pass
self.is_to_remove = True
class Rocket(pygame.sprite.Sprite):
def __init__(self, filename, defender):
super().__init__()
self.image = load_image(filename, (72, 72))
self.rect = self.image.get_rect(
centerx=defender.rect.centerx, bottom=defender.rect.top + 40
)
self.direction = -1
self.speed = 5
self.is_to_remove = False
def draw(self, screen):
screen.blit(self.image, self.rect)
def update(self):
self.rect.move_ip(0, self.direction * self.speed)
if self.rect.bottom <= 0:
self.is_to_remove = True
def handle_enemy_with_rocket_collisions(enemy, rockets):
for rocket in rockets:
if enemy.rect.colliderect(rocket.rect):
rockets.remove(rocket)
enemy.hit_by_rocket()
def check_enemy_bullet_collision(
enemy_bullets, rockets, defender, enemy_bullet, rocket
):
if enemy_bullet.rect.colliderect(rocket.rect):
enemy_bullet.sound.play()
rockets.remove(rocket)
enemy_bullets.remove(enemy_bullet)
defender.increase_score(10)
return True
return False
def main():
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()
rockets = pygame.sprite.Group()
last_rocket_shot_time = pygame.time.get_ticks()
background = load_image(
"backgroundlevel01.png", (WINDOW_WIDTH, WINDOW_HEIGHT)
)
defender = Defender("cathero.png")
enemy01 = Enemy01("enemy01.png", defender)
enemy02 = Enemy02("enemy02.png", defender)
enemy_bullets = pygame.sprite.Group()
enemy_bullet_last_shot_time = pygame.time.get_ticks()
bullet_sound = mixer.Sound("katzenball.mp3")
pygame.mixer.music.load("soundtrack.wav") # Lade die Melodie
pygame.mixer.music.set_volume(0.3) # Setze die Lautstärke (0.0-1.0)
pygame.mixer.music.play(-1) # Spiele die Melodie endlos (-1)
running = True
while running:
clock.tick(60)
current_time = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
defender.move_left()
elif event.key == pygame.K_RIGHT:
defender.move_right()
elif event.key == pygame.K_SPACE:
if current_time - last_rocket_shot_time >= 650:
bullet_sound.play()
rockets.add(Rocket("rocket.png", defender))
last_rocket_shot_time = current_time
elif event.type == pygame.KEYUP and event.key in [
pygame.K_LEFT,
pygame.K_RIGHT,
]:
defender.move_stop()
time_since_last_bullet = current_time - enemy_bullet_last_shot_time
if time_since_last_bullet >= 6000 and random.randint(0, 10) == 0:
enemy_bullets.add(Enemy02Bullet(enemy02, defender))
enemy_bullet_last_shot_time = current_time
if time_since_last_bullet >= 1000:
enemy_bullets.add(
Enemy01Bullet(
random.choice(["LU", "IS", "MA", "EI"]), enemy01, defender
)
)
enemy_bullet_last_shot_time = current_time
handle_enemy_with_rocket_collisions(enemy01, rockets)
defender.update()
enemy01.update()
enemy02.update(enemy_bullets)
rockets.update()
for rocket in rockets:
if rocket.is_to_remove:
rockets.remove(rocket)
enemy_bullets.update()
for enemy_bullet in enemy_bullets:
if enemy_bullet.is_to_remove:
enemy_bullets.remove(enemy_bullet)
for rocket in rockets:
for enemy_bullet in enemy_bullets:
if check_enemy_bullet_collision(
enemy_bullets,
rockets,
defender,
enemy_bullet,
rocket,
):
break
handle_enemy_with_rocket_collisions(enemy02, rockets)
screen.blit(background, background.get_rect())
for game_object in [
enemy01,
enemy02,
defender,
rockets,
enemy_bullets,
]:
game_object.draw(screen)
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()
``def draw()`` auf `Sprite`-Objekten ist komisch, weil dafür eigentlich die `draw()`-Methode auf Gruppen-Objekten vorgesehen ist.
Insgesamt ist in den Klassen zu viel gemeinsames das man in eine oder mehrere Basisklassen herausziehen sollte.
Die `main()`-Funktion ist auch zu umfangreich für meinen Geschmack.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis