Einer Methode die aktuelle Zeit ü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!

Ich habe hier folgende Funktion der Klasse "Bullet":

Code: Alles auswählen

            def collision(self, window):
                if self.rect.colliderect(player):
                    self.rect.center = (random.randint(0 + BORDER, aufloesung.current_w - BORDER), 0)
                    player.life -= self.damage
                    collidetime = time.time()
                    print (collidetime)
                    player.hurt = True
                    if player.life <= 0:
                        explosion_gif.render(window, (player.rect))
                        player.rect.x = -500
                        player.rect.y = -500
                    player.hurt == True
                    player.image = player_hurt_img
                    print(time.time())
                    if time.time() > collidetime + 0.5:
                        print("YES")
                        player.image = player_img
                        player.hurt = False
Am Ende der Klasse habe ich eine Update Methode:

Code: Alles auswählen

            def update(self, window):
                self.movement(window)
                self.collision(window)
                self.draw(window)

Jetzt habe ich das Problem, dass time.time() nur ein Zeitstempel ist und sich somit das player.image nicht mehr zurück ändert. Muss ich der Klasse etwas übergeben? Oder der Methode?
Wenn ich es innerhalb der Hauptschleife des Spiels mache, dann funktioniert es, jedoch nicht in der Klasse.
Benutzeravatar
__blackjack__
User
Beiträge: 13684
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Die Klasse sieht komisch eingerückt aus, das ``class`` sollte direkt am Zeilenanfang stehen.

Die Funktion ist eine Methode und die sollte nicht auf `aufloesung`, `player`, und `explosion_gif` einfach so magisch zugreifen können.

Eine `Bullet.collision()` sollte nichts direkt am internen Zustand von einem `Player` machen. Die kann dem `Player`-Objekt mitteilen, dass es getroffen wurde, und entsprechend selbst reagieren sollte, aber eine Bullet setzt beim Player kein neues Bild.

Statt `time.time()` sollte man für so etwas `time.monotonic()` verwenden. Oder gar keine reale Zeit und das in Anzahl der `update()`-Aufrufe lösen, denn die Hauptschleife sollte ja mit einem Clock-Objekt auf eine FPS-Anzahl begrenzt sein, womit ein `update()` gleich einem “Frame“ ist.

Ansonsten steht die Lösung ja bereits im Titel des Themas: Wenn man eine aktuelle Zeitangabe aus der Hauptschleife haben möchte, muss man die den Methoden übergeben. Wenn man dann irgendwann anfängt zu viel Spielzustand übergeben zu müssen, macht es Sinn den Zustand des Spiels in eine Klasse zu stecken.
„Incorrect documentation is often worse than no documentation.“ — Bertrand Meyer
Benutzeravatar
Patsche
User
Beiträge: 54
Registriert: Samstag 23. Oktober 2021, 00:17

__blackjack__ hat geschrieben: Mittwoch 27. November 2024, 21:18 Die Klasse sieht komisch eingerückt aus, das ``class`` sollte direkt am Zeilenanfang stehen.

So sieht der Code über der ersten Klasse aus:

Code: Alles auswählen

#!/usr/bin/env python3

import pygame, gif_pygame, random, time

# initialisieren von pygame
pygame.init()
print("---- Initialisierung gestartet -----")
print()
print()

# Displayerkennung
print("---- Displayerkennung gestartet -----")
aufloesung = pygame.display.Info()
print("Auflösung: ", aufloesung.current_w, " x ", aufloesung.current_w)
print()

# Gamepaderkennung
print("---- Gamepads werden gesucht -----")
if pygame.joystick.get_count() > 0:
    joystick_0 = pygame.joystick.Joystick (0)
    joystick_0.init()
    print("Gamepad(s) erkannt")
    print("Anzahl: ", pygame.joystick.get_count())
    print()
    name = joystick_0.get_name()
    print("Gamepad 1: ", name)
    print()
    print()
else:
    print("KEIN Gamepad gefunden")
    print()
    print()
print("---- Spiel wurde gestartet -----")
print()

## Mouse Cursor hidden in game
pygame.mouse.set_visible(False)

# Load graphics
bg = pygame.image.load("img/bg.jpg")
player_img = pygame.image.load("img/cruiser.png")
player_hurt_img = pygame.image.load("img/cruiser_hurt.png")
alien1_img = pygame.image.load("img/alien1.png")
alien2_img = pygame.image.load("img/alien2.png")
alien3_img = pygame.image.load("img/alien3.png")
alien4_img = pygame.image.load("img/alien4.png")
boss_img = pygame.image.load("img/alien_boss.png")
boss_hurt_img = pygame.image.load("img/alien_boss_hurt.png")
alien_hurt_img = pygame.image.load("img/alien_hurt.png")
bullet_img = pygame.image.load("img/bullet.png")
bullet2_img = pygame.image.load("img/bullet2.png")
bullet_alien_img = pygame.image.load("img/bullet_alien.png")
bullet_random_img = pygame.image.load("img/bullet_random.png")
bomb_img = pygame.image.load("img/bomb.png")
bullet_speed_img = pygame.image.load("img/bullet_speed.png")
explosion_gif = gif_pygame.load("img/explosion2.gif")
big_explosion_gif = gif_pygame.load("img/explosion.gif")

# Define constants
FPS = 60
BORDER = 20


def main():
    pygame.init()
    try:

        # Open a window
        window = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)

        # set windowtitel
        pygame.display.set_caption("Space Invaders by Maro Hutzfeld")

        # set variable to run the game
        game_active = True

        # activate screenrate
        clock = pygame.time.Clock()

        # Load music and soundeffekts
        bg_sound = pygame.mixer.Sound('sound/bgmusic.mp3')
        laser_sound = pygame.mixer.Sound('sound/lasershot.mp3')
        alien_died_sound = pygame.mixer.Sound('sound/alien_died.mp3')
        attack_sound = pygame.mixer.Sound('sound/attack.mp3')
        gameover_sound = pygame.mixer.Sound('sound/gameover.mp3')

        # Define variables
        starttime = time.time()
        timedifference = 1
        gametime = 0
        alien_died = 0
        collidetime = 0
        hurttime = 0.05
        hits = 0
Deswegen ist die Klasse so weit eingerückt.
__blackjack__ hat geschrieben: Mittwoch 27. November 2024, 21:18 Eine `Bullet.collision()` sollte nichts direkt am internen Zustand von einem `Player` machen. Die kann dem `Player`-Objekt mitteilen, dass es getroffen wurde, und entsprechend selbst reagieren sollte, aber eine Bullet setzt beim Player kein neues Bild.
Das hatte ich mir auch gedacht, aber die Klasse Player wird vor der Klasse Bullet erstellt. Und scheinbar hat es dann keinen Effekt, wenn Player mit Bullet kollidiert.
Da passiert dann nicht, weshalb ich es dann so gemacht habe.
Ich wollte in die jeweiligen Kollisionen nur Änderungen eintrage, welches die eigene Klasse betrifft. Das ging leider nicht.

__blackjack__ hat geschrieben: Mittwoch 27. November 2024, 21:18 Statt `time.time()` sollte man für so etwas `time.monotonic()` verwenden. Oder gar keine reale Zeit und das in Anzahl der `update()`-Aufrufe lösen, denn die Hauptschleife sollte ja mit einem Clock-Objekt auf eine FPS-Anzahl begrenzt sein, womit ein `update()` gleich einem “Frame“ ist.
Danke für Hinweis.
__blackjack__ hat geschrieben: Mittwoch 27. November 2024, 21:18 Ansonsten steht die Lösung ja bereits im Titel des Themas: Wenn man eine aktuelle Zeitangabe aus der Hauptschleife haben möchte, muss man die den Methoden übergeben. Wenn man dann irgendwann anfängt zu viel Spielzustand übergeben zu müssen, macht es Sinn den Zustand des Spiels in eine Klasse zu stecken.
Und da weiß ich nicht, wie ich das genau bewerkstellige. Kannst du mir da mal zeigen, wie das geht?
Ich hatte es so probiert, dass ich in der Hauptschleife eine Variabl "actual_time" erstellt habe und dann in der Methode stehen hatte:

Code: Alles auswählen

def collision(self, actual_time, window):
Aber das funktionierte auch nicht. "actual_time" war innerhalb der Methode auch nur ein Zeitstempel.....



Edit:
Ich habe den Fehler für die fehlende Kollidierung für den Player gefunden.
Die Klasse Bullet bemerkt die Kollision und setzt die Kugel zurück.
Aber der Player regisitriert davon nichts.
Benutzeravatar
__blackjack__
User
Beiträge: 13684
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patsche: Eine Klasse sollte halt nicht in einer Funktion definiert werden. Dann kann man die ja nur innerhalb dieser Funktion benutzen um Objekte damit zu erstellen, und zum Beispiel nicht ausserhalb eines normalen Programmablaufs um mal Verhalten zu testen. Manuell oder automatisiert mit Code. Und man hat da auch plötzlich ”magischen” zugriff auf alles mögliche *in* der Funktion auf das man gar keinen Zugriff haben sollte.

Alles vor ``FPS = 60`` sollte dafür in der `main()`-Funktion stehen.

Naja genau so geht das die aktuelle Zeit (englisch „current time“, weil „actual time“ wäre die *tatsächliche* Zeit, ist ein „false friend“) an eine Methode zu übergeben. Und natürlich ist das nur ein Zeitstempel. `time.time()` beziehungsweise `time.monotonic()` liefert die Zeit zum Zeitpunkt des Aufrufs der Funktion.

Ich würde die Kollisionsfolgen weder in der Bullet- noch in der Player-Klasse festlegen. Das feststellen einer Kollision und Informieren der beteiligten Objekte ist IMHO Aufgabe der “Spielwelt“, also der (Spiel)Hauptschleife in diesem Beispiel.
„Incorrect documentation is often worse than no documentation.“ — Bertrand Meyer
Benutzeravatar
Patsche
User
Beiträge: 54
Registriert: Samstag 23. Oktober 2021, 00:17

Ok. Wiedereinmal vielen Dank. Ich werde mal sehen, wie ich das umbaue.
Also sollten die Klassen vor die Hauptschleife des Programmes?

Dann habe ich zum Beispiel Probleme den Spieler zu setzen, weil ich dort schon auf Funktionen von pygame zurückgreife, die erst in der Hauptschleife initialisiert werden.
Die Auflösung zum Beispiel wird automatisch erkannt.

Und wenn ich die ganzen Grafiken erst in der Hauptschleife lade, dann können die Klassen auch nicht mehr darauf zugreifen, wenn ich sie davor setze.
Alles ziemlich verschachtelt.
Benutzeravatar
__blackjack__
User
Beiträge: 13684
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patsche: Eine Klasse die vor `main()` steht führt ja erst einmal nichts aus. Da wird nur die ``class``-Anweisung ausgeführt welche die Klasse definiert. Das in den Methoden davon ausgegangen wird, dass beispielsweise `pygame.init()` aufgerufen werden musste, bevor so eine Methode korrekt funktioniert, macht ja nichts, denn die Methode wird ja nicht vorher ausgeführt.

Zur Auflösung hatte ich glaube ich schon mal was geschrieben. An den Stellen wo Du das benutzt, hast Du das Surface auf dem gemalt wird, und das kennt seine eigene Grösse. Die kann man da als Breite und Höhe abfragen, oder auch als `Rect`. Eine globale Auflösung braucht man da nicht.

Ansonsten sollten Funktionen und Methoden alles was sie benötigen, ausser Konstanten, beim Aufruf übergeben bekommen und nicht magisch irgendwo ausserhalb global vorfinden. Die Grafiken die ein „Sprite“ braucht, übergibt man beim erstellen und bindet die ans Objekt, dann kann man innerhalb der Methoden darüber dann auch zugreifen, und man sieht auch leichter welche Grafik zu welchem Objekt gehört.
„Incorrect documentation is often worse than no documentation.“ — Bertrand Meyer
Antworten