Pygame Objekt entfernen

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
fatpossum
User
Beiträge: 3
Registriert: Sonntag 30. Juli 2023, 21:29

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 :cry: 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()
Benutzeravatar
__blackjack__
User
Beiträge: 13117
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):

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()
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.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
fatpossum
User
Beiträge: 3
Registriert: Sonntag 30. Juli 2023, 21:29

Oh, wow! Danke für deine umfangreiche Kritik und die Zeit, die Du da reingesteckt hast. Echt vielen, vielen Dank! Ich muss noch so viel lernen :o
Antworten