Werte von einer Klasse einer Instanzierung einer anderen Klasse übergeben

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
Benutzeravatar
Patsche
User
Beiträge: 54
Registriert: Samstag 23. Oktober 2021, 00:17

Moin.
Lange ist es her, dass ich mich hier mal wieder zu Wort gemeldet habe. Ich bin mal wieder zu Pygame gekommen und möchte möchte eine Art Space Invader mit meinem Sohn programmieren.

Jetzt habe ich eine Klasse namens Player, die so aussieht:

Code: Alles auswählen

        class Player:
            def __init__(self, x, y, move, image):
                self.x = x
                self.y = y
                self.move = move
                self.image = image
                self.width = self.image.get_width()
                self.height = self.image.get_height()
                self.rect = pygame.Rect(self.x, self.y, self.width, self.height)

            def movement(self):
                keys = pygame.key.get_pressed()

                if keys[pygame.K_a] and (self.rect.x > self.width//2):
                    self.rect.move_ip(-self.move, 0)

                if keys[pygame.K_d] and (self.rect.x < aufloesung.current_w - self.width - 20):
                    self.rect.move_ip(self.move, 0)

            def draw(self, window):
    
                window.blit(self.image, (self.rect))

            def update(self, window):
                self.movement()
                self.draw(window)
Und eine Bullet-Klasse, die so aussieht:

Code: Alles auswählen

        class Bullet:
            def __init__(self, x, y, move, speed, image):
                self.x = x
                self.y = y
                self.move = move
                self.image = image
                self.width = self.image.get_width()
                self.height = self.image.get_height()
                self.rect = pygame.Rect(self.x, self.y, self.width, self.height)

            def movement(self):
                keys = pygame.key.get_pressed()

                if keys[pygame.K_SPACE]:
                    pygame.mixer.Sound.play(laser_sound)
                    self.rect.move_ip(0, -self.move)

            def draw(self, window):
                window.blit(self.image, (self.rect))

            def update(self, window):
                self.movement()
                self.draw(window)
Jetzt weiß ich aber nicht, wie ich die Klasse Bullet instanzieren soll.
Ich hatte folgendes probiert:

Code: Alles auswählen

bullet = Bullet(Player.rect.x, Player.rect.y, 5, bullet_img)
Nach dem Drücken der Space-Taste soll Bullet ja an der stelle erscheinen, wo sich der Spieler befindet. Ich weiß jedoch nicht, wie ich auf diese Daten zugreifen kann?
Vielleicht kann mir da jemand helfen?
Benutzeravatar
Patsche
User
Beiträge: 54
Registriert: Samstag 23. Oktober 2021, 00:17

Ach.....ich habe schon den Fehler erkannt.
Ich muss den ganzen Teil in der Hauptschleife machen, da die Bullet Klasse überhaupt nicht mitbekommt, wo das player.rect aktuell ist.
Außerdem müsste es wenn dann so heißen:

Code: Alles auswählen

bullet = Bullet(player.rect.x, player.rect.y, 5, bullet_img)
Also player mit kleinem p.
Benutzeravatar
__blackjack__
User
Beiträge: 14016
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patsche: Warum sind die Klassendefinitionen denn eingerückt? Die sollten auf Modulebene stehen.

Das sieht nach kopieren, einfügen und leicht ändern aus — die beiden Klassen haben viel zu viel Code doppelt.

`move` sollte wohl eher `speed` heissen.

`x`, `y`, und `width` und `height` lassen sich alle aus dem `rect` ermitteln, das sollte man nicht alles noch mal redundant speichern. Das `rect` lässt sich auch gleich aus dem angebenen `image` und der Position der linken oberen Ecke ermitteln. (Oder auch aus anderen Punkten die das `Rect` so anbietet.)

`movement()` sollte auch das `Surface` bekommen und nicht auf magische Weise auf einem globalen `Info`-Objekt die Bildschirmbreite abfragen.

Methoden werden auf den Objekten aufgerufen und auf den Klassen mit dem Objekt als Argument. → ``lasersound.play()``. Wobei `lasersound` auch wieder nicht einfach so magisch irgendwo her kommen sollte.

Zwischenstand (ungetestet):

Code: Alles auswählen

class Movable:
    def __init__(self, position, speed, image):
        self.speed = speed
        self.image = image
        self.rect = self.image.get_rect(topleft=position)

    def move(self, _surface):
        pass

    def draw(self, surface):
        surface.blit(self.image, self.rect)

    def update(self, surface):
        self.move(surface)
        self.draw(surface)


class Player(Movable):

    def move(self, surface):
        keys = pygame.key.get_pressed()

        if keys[pygame.K_a] and self.rect.center_x > 0:
            self.rect.move_ip(-self.speed, 0)

        if keys[pygame.K_d] and self.rect.center_x < surface.get_width():
            self.rect.move_ip(self.speed, 0)


class Bullet(Movable):

    def __init__(self, position, speed, image, laser_sound):
        Movable.__init__(self, position, speed, image)
        self.laser_sound = laser_sound

    def move(self, _surface):
        keys = pygame.key.get_pressed()

        if keys[pygame.K_SPACE]:
            self.laser_sound.play()
            self.rect.move_ip(0, -self.speed)
Das mit den `image`- und `rect`-Attributen sieht sehr nach selber nachbauen von den Sachen in `pygame.sprite` aus.

Die `get_pressed()`-Funktion ist nicht schön. Besser wäre es die Ereignisse abzuarbeiten und da die Aktionen für Taste wurde gedrück/losgelassen entsprechend zu verarbeiten.

Das sich die Geschosse nur bewegen wenn man die Leertaste gedrückt hält ist komisch.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Patsche
User
Beiträge: 54
Registriert: Samstag 23. Oktober 2021, 00:17

Danke für die Hinweise.

Ich merke immer wieder, dass mich das ganze doch etwas überfordert. Man hangelt sich an irgendwelchen Tutorials lang und hören sie auf oder der Ersteller macht es falsch.
Ich versuche jetzt erstmal deine Hinweise abzuarbeiten.

Ich bekomme es beispielsweise nicht mit der KEYDOWN oder KEYUP Methode hin, die Steuerung in die Klasse zu bringen, weil es dort nicht registriert wird. Deswegen bin ich bei der Player Figur auf "keypressed" umgestiegen, macht bei der Bulletklasse aber halt auch kein Sinn.....
Benutzeravatar
Patsche
User
Beiträge: 54
Registriert: Samstag 23. Oktober 2021, 00:17

Sooo....habe es jetzt so hinbekommen, wie ich es wollte.

Jetzt habe ich aber noch eine Frage:

Meine Enemy-Klasse sieht folgendermaßen aus:

Code: Alles auswählen

        class Enemy:
            def __init__(self, x, y, speed, life, image):
                self.x = x
                self.y = y
                self.speed = speed
                self.life = life
                self.image = image
                self.width = self.image.get_width()
                self.height = self.image.get_height()
                self.rect = pygame.Rect(self.x, self.y, self.width, self.height)

            def move(self, window):
                enemy.rect.move_ip(0, self.speed)

            def draw(self, window):
                window.blit(self.image, (self.rect))

            def update(self, window):
                self.move(window)
                self.draw(window)
Ich instanzieren 90 Gegner mit:

Code: Alles auswählen

enemies = [Enemy(random.randint(100, 1800), random.randint(-4000, -200), 1, 1, alien_img)
                for _ in range(enemy_max)
                ]
Jetzt möchte ich eine Art Endboss zum Schluss haben und instanziere noch einen einzelnen Gegner mit:

Code: Alles auswählen

boss_enemy = Enemy(800, -4500, 1, 3, boss_img)
Doch der letzte Boss wird nicht aufs Window gezeichnet....

Am Ende der Schleife lasse ich alles neu zeichnen:

Code: Alles auswählen

# Spielfiguren zeichnen
            player.update(window)
            for enemy in enemies:
                enemy.update(window)
            enemy.update(window)
            bullet.update(window)
            pygame.display.update()
Wo liegt der Fehler?
Benutzeravatar
Dennis89
User
Beiträge: 1526
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

wenn ich das richtig interpretiere, ist nach dem erstellen von `boss_enemy` die Figur auserhalb des Bildschirms und ich sehe keinen Codezeile in der du irgendwas mit `boss_enemy` machst.

Wieso rufst du nach der `for`-Schleife noch mal `enemy.update(window)` auf?


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Patsche
User
Beiträge: 54
Registriert: Samstag 23. Oktober 2021, 00:17

Dennis89 hat geschrieben: Samstag 23. November 2024, 08:05 wenn ich das richtig interpretiere, ist nach dem erstellen von `boss_enemy` die Figur außerhalb des Bildschirms und ich sehe keinen Codezeile in der du irgendwas mit `boss_enemy` machst.
Das ist richtig so, denn der alle Gegner kommen langsam von außerhalb des Bildschirm in das "window" hereingeflogen. Damit er zum Ende kommt, wird er ganz weit in der negativen Y-Achse "gespawnt".

Code: Alles auswählen

def move(self, window):
   enemy.rect.move_ip(0, self.speed)
Dennis89 hat geschrieben: Samstag 23. November 2024, 08:05 Wieso rufst du nach der `for`-Schleife noch mal `enemy.update(window)` auf?
Das war nur testweise, um zu sehen, ob ich den "Boss" eventuell nochmal einzeln aktualisieren muss......



Edit:
Ich habe es jetzt so gelöst, dass ich mehrere Gegnergruppen erstellt habe.
Jetzt habe ich folgenden Code:

Code: Alles auswählen

            for enemy in enemies1:
                enemy.update(window)
                if bullet.rect.colliderect(enemy.rect):
                    bullet.rect.x = -400
                    bullet.rect.y = -400
                    shot = False
                    enemy.life -= 1
                    if enemy.life == 0:
                        pygame.mixer.Sound.play(alien_died_sound)
                        alien_died += 1
                        enemies1.remove(enemy)

            for enemy in enemies2:
                enemy.update(window)
                if bullet.rect.colliderect(enemy.rect):
                    bullet.rect.x = -400
                    bullet.rect.y = -400
                    shot = False
                    enemy.life -= 1
                    if enemy.life == 0:
                        pygame.mixer.Sound.play(alien_died_sound)
                        alien_died += 1
                        enemies2.remove(enemy)

            for enemy in enemies3:
                enemy.update(window)
                if bullet.rect.colliderect(enemy.rect):
                    bullet.rect.x = -400
                    bullet.rect.y = -400
                    shot = False
                    enemy.life -= 1
                    if enemy.life == 0:
                        pygame.mixer.Sound.play(alien_died_sound)
                        alien_died += 1
                        enemies3.remove(enemy)
Jetzt würde ich gerne mit einer For-Schleife und der For-Variable "i" den Variablennamen "verändern".
Ich hatte an so etwas gedacht:

Code: Alles auswählen

for i in range (1,4)
   for enemy in enemies[i]:
      enemy.update(window)
         if bullet.rect.colliderect(enemy.rect):
            bullet.rect.x = -400
            bullet.rect.y = -400
            shot = False
            enemy.life -= 1
            if enemy.life == 0:
                pygame.mixer.Sound.play(alien_died_sound)
                alien_died += 1
                enemies[i].remove(enemy)
Wie kann ich statt den Zahlen 1-3 von den enemies, dass i nutzen? Ich hoffe ihr versteht, was ich meine...
Benutzeravatar
Dennis89
User
Beiträge: 1526
Registriert: Freitag 11. Dezember 2020, 15:13

Ist `boss_enemy` in der `enemies`-Liste? Wenn nicht dann meine ich, du musst irgendwo `boss_enemy.update()` aufrufen, damit damit auch was passiert?

Zum neuen Teil:
`enemie1` und die anderen sind in der Liste `enemies`?
Dann würde man direkt über die Liste iterieren und nicht auf den Index zugreifen. Hat auch den Vorteil, dass das keine Probleme gibt, wenn die `enemies`-Liste größer wird.

Code: Alles auswählen

for enemy_group in enemies:
   for enemy in enemy_group:
       enemy.update(window)
       if bullet.rect.colliderect(enemy.rect):
            bullet.rect.x = -400
            bullet.rect.y = -400
            shot = False
            enemy.life -= 1
            if enemy.life == 0:
                pygame.mixer.Sound.play(alien_died_sound)
                alien_died += 1
                enemy_group.remove(enemy)

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 14016
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ich würde da vielleicht noch `enemies` in `enemy_groups` umbenennen, damit man nicht aus versehen annehmen kann, dass in `enemies` *direkt* `Enemy`-Objekte drin stecken.

Dann haben wir hier das Problem das etwas aus einer Liste entfernt wird *während* über die Liste iteriert wird. Damit werden dann Objekte übersprungen:

Code: Alles auswählen

In [1]: A = list(range(10))

In [2]: A
Out[2]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [3]: for a in A:
   ...:     A.remove(a)
   ...: 

In [4]: A
Out[4]: [1, 3, 5, 7, 9]  # Ups, nicht leer!
``pygame.mixer.Sound.play(alien_died_sound)`` sollte auch hier einfach ``alien_died_sound.play()`` heissen.

Und das mit `pygame.sprite` erwähnte ich ja schon mal. Jetzt werden nicht nur einzelne Sprites selbst geschrieben, sondern auch Sprite-Gruppen nachprogrammiert. 🙂 Da gäbe es dann auch die `groupcollide()`-Funktion damit man das nicht alles selbst ausprogrammieren muss.

Es gibt auch eine Gruppe die maximal ein Sprite enthalten kann, die würde sich für das Geschoss anbieten.

Mit den Gruppen wird das setzen des Geschosses ausserhalb des Bildschirms unnötig, denn wenn man das aus der Gruppe raus nimmt, wird es auch nicht mehr gezeichnet. Auch `shot` wird unnötig, weil man jetzt an der Gruppe prüfen kann ob das Geschoss drin ist — also aktiv ist, oder nicht, also kein Geschoss aktiv ist.

Ansatz:

Code: Alles auswählen

    for enemy_group in enemy_groups:
        enemy_group.update(window)
        hit_enemies = groupcollide(bullet_group, enemy_group, True, False).get(
            bullet
        )
        if hit_enemies:
            assert len(bullet_group) == 0
            enemy = hit_enemies[0]
            enemy.life -= 1
            if enemy.life <= 0:
                alien_died_sound.play()
                alien_died += 1
                enemy_group.remove(enemy)
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Patsche
User
Beiträge: 54
Registriert: Samstag 23. Oktober 2021, 00:17

Oha....schon viel neuer Input.
Danke dafür.

Die einzelnen enemies Gruppen erstelle ich gerade so:

Code: Alles auswählen

# Klassen verwenden
        player = Player(aufloesung.current_w // 2, aufloesung.current_h - 130, 20, player_img)

        enemies1 = [Enemy(random.randint(100, 1800), random.randint(-4500, -200), 1, 1, alien1_img)
                for _ in range(enemy_max)
                ]
        enemies2 = [Enemy(random.randint(700, 720), random.randint(-5300, -5200), 1, 10, boss_img)
                   for _ in range(1)
                   ]
        enemies3 = [Enemy(random.randint(100, 1800), random.randint(-4500, -200), 1, 2, alien2_img)
                    for _ in range(7)
                    ]
        enemies4 = [Enemy(random.randint(100, 1800), random.randint(-7000, -200), 2, 1, alien3_img)
                    for _ in range(10)
                    ]
        enemies5 = [Enemy(random.randint(100, 1800), random.randint(-7000, -200), 2, 2, alien4_img)
                    for _ in range(10)
                    ]
        bullet = Bullet(-400, -400, 40, bullet_img)
__blackjack__ hat geschrieben: Samstag 23. November 2024, 13:56 Ich würde da vielleicht noch `enemies` in `enemy_groups` umbenennen, damit man nicht aus versehen annehmen kann, dass in `enemies` *direkt* `Enemy`-Objekte drin stecken.
Ich glaube da steckt irgendwie mein Fehler drin, denn ich muss für jede Gruppe eine neue Kollisionsdetektion erstellen:

Code: Alles auswählen

# Kollisionen            
            for enemy in enemies1:
                enemy.update(window)
                if bullet.rect.colliderect(enemy.rect):
                    bullet.rect.x = -400
                    bullet.rect.y = -400
                    shot = False
                    enemy.life -= 1
                    if enemy.life == 0:
                        pygame.mixer.Sound.play(alien_died_sound)
                        alien_died += 1
                        enemies1.remove(enemy)

            for enemy in enemies2:
                enemy.update(window)
                if bullet.rect.colliderect(enemy.rect):
                    bullet.rect.x = -400
                    bullet.rect.y = -400
                    shot = False
                    enemy.life -= 1
                    if enemy.life <=3:
                        enemy.image = boss_hurt_img
                    if enemy.life == 0:
                        alien_died_sound.play()
                        alien_died += 1
                        enemies2.remove(enemy)

            for enemy in enemies3:
                enemy.update(window)
                if bullet.rect.colliderect(enemy.rect):
                    bullet.rect.x = -400
                    bullet.rect.y = -400
                    shot = False
                    enemy.life -= 1
                    if enemy.life == 0:
                        alien_died_sound.play()
                        alien_died += 1
                        enemies3.remove(enemy)

            for enemy in enemies4:
                enemy.update(window)
                if bullet.rect.colliderect(enemy.rect):
                    bullet.rect.x = -400
                    bullet.rect.y = -400
                    shot = False
                    enemy.life -= 1
                    if enemy.life == 0:
                        alien_died_sound.play()
                        alien_died += 1
                        enemies4.remove(enemy)

            for enemy in enemies5:
                enemy.update(window)
                if bullet.rect.colliderect(enemy.rect):
                    bullet.rect.x = -400
                    bullet.rect.y = -400
                    shot = False
                    enemy.life -= 1
                    if enemy.life == 0:
                        alien_died_sound.play()
                        alien_died += 1
                        enemies5.remove(enemy)
Und alle Gruppen einzeln zeichnen:

Code: Alles auswählen

# Spielfiguren zeichnen
            player.update(window)
            for enemy in enemies1:
                enemy.update(window)
            for enemy in enemies2:
                enemy.update(window)
            for enemy in enemies3:
                enemy.update(window)
            for enemy in enemies4:
                enemy.update(window)
            for enemy in enemies5:
                enemy.update(window)
            bullet.update(window)
Ich hatte jetzt gehofft die Zahlen hinter enemies durch ein i zu ersetzen zu können und dann in einer For-Schleife alles in einem Rutsch machen zu können. Zum Beispiel statt:

Code: Alles auswählen

# Spielfiguren zeichnen
            for enemy in enemies1:
                enemy.update(window)
            for enemy in enemies2:
                enemy.update(window)
            for enemy in enemies3:
                enemy.update(window)
            for enemy in enemies4:
                enemy.update(window)
            for enemy in enemies5:
                enemy.update(window)
So:

Code: Alles auswählen

for i in range (1,6)
   for enemy in enemies(i):
      enemy.update(window)/code]
Benutzeravatar
__blackjack__
User
Beiträge: 14016
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patsche: Man nummeriert keine Namen. Du willst hier nicht `enemies1` bis `enemies5` sondern eine Liste wo diese Gruppen drin stecken. Also bei der Erstellung schon:

Code: Alles auswählen

        enemy_groups = [
            [
                Enemy(
                    random.randint(100, 1800),
                    random.randint(-4500, -200),
                    1,
                    1,
                    alien1_img,
                )
                for _ in range(enemy_max)
            ],
            [
                Enemy(
                    random.randint(700, 720),
                    random.randint(-5300, -5200),
                    1,
                    10,
                    boss_img,
                )
                for _ in range(1)
            ],
            [
                Enemy(
                    random.randint(100, 1800),
                    random.randint(-4500, -200),
                    1,
                    2,
                    alien2_img,
                )
                for _ in range(7)
            ],
            [
                Enemy(
                    random.randint(100, 1800),
                    random.randint(-7000, -200),
                    2,
                    1,
                    alien3_img,
                )
                for _ in range(10)
            ],
            [
                Enemy(
                    random.randint(100, 1800),
                    random.randint(-7000, -200),
                    2,
                    2,
                    alien4_img,
                )
                for _ in range(10)
            ],
        ]
Wobei man hier vielleicht auch mal schauen könnte ob man die Unterschiede der einzelnen Gruppen nicht auch noch mal in eine Datenstruktur herausziehen könnte.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 14016
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Etwas kompakter wenn man die Daten heraus zieht:

Code: Alles auswählen

        enemy_groups = [
            [
                Enemy(
                    random.randint(min_x, max_x),
                    random.randint(min_y, max_y),
                    speed,
                    life,
                    image,
                )
                for _ in range(count)
            ]
            for (min_x, max_x), (min_y, max_y), speed, life, image, count in [
                ((100, 1800), (-4500, -200), 1, 1, alien1_img, enemy_max),
                ((700, 720), (-5300, -5200), 1, 10, boss_img, 1),
                ((100, 1800), (-4500, -200), 1, 2, alien2_img, 7),
                ((100, 1800), (-7000, -200), 2, 1, alien3_img, 10),
                ((100, 1800), (-7000, -200), 2, 2, alien4_img, 10),
            ]
        ]
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Patsche
User
Beiträge: 54
Registriert: Samstag 23. Oktober 2021, 00:17

Wow.....vielen Dank für die Hile @__blackjack__ und natürlich an die anderen Helfer.
Ich werde mir das mal genauer ansehen und ausprobieren.

Mir raucht schon der Kopf. Mir kommen immer wieder neue Ideen, wo man erstmal die Doku lesen und es dann ausprobieren muss.
Habe jetzt entdeckt, dass es ein Plugin für gif's in pygame gibt. (gif-pygame)
https://pypi.org/project/gif-pygame/
Das funktioniert sehr gut.

Bin gerade dabei ein explosions-gif ins Spiel zu bauen.
Wenn Bullet mit Enemy kollidiert, dann soll das gif gezeichnet werden.
Aber leider wird es halt nur für den Zeitpunkt der Kollision abgespielt und hört dann halt auf.
Möchte da auch nicht mit irgendwelchen Zeiten jonglieren.
Aber ein Event gibt es da auch nicht....
Antworten