[Pygame] Objektorientierte Steuerung von 2 Klassen gleichzeitig

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

Ich habe mal wieder ein Problem und weiß nicht, ob es meine Unfähigkeit ist, oder ob es ein Problem innerhalb von Pygame ist.
Hier ist der Code:

Code: Alles auswählen

# Importieren der Pygame-Bibliothek
import pygame

# 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()

# Grafiken laden und zuweisen
bg_original = pygame.image.load("Grafik/map.png")
bg = pygame.transform.scale(bg_original, (aufloesung.current_w * 12, aufloesung.current_w * 12))


### Musik/Soundeffekte einrichten
##pygame.mixer.music.load('**************')
##pygame.mixer.music.play(-1,0.0)
##pygame.mixer.music.set_volume(.1)

# Fenster öffnen
screen = pygame.display.set_mode((0,0), pygame.FULLSCREEN)

# Titel für Fensterkopf
pygame.display.set_caption("********** by Marco Hutzfeld")

# solange die Variable True ist, soll das Spiel laufen
spielaktiv = True

# Bildschirm Aktualisierungen einstellen
clock = pygame.time.Clock()

# Klassen erstellen
class Spieler ():
    def __init__ (self, x, y, leben):
        self.x = x
        self.y = y
        self.leben = leben        
        img = pygame.image.load("Grafik/Link/Link.png")
        self.image = pygame.transform.scale(img, (32, 54))
        self.rect = self.image.get_rect()        

    def update(self):
        self.richtung = [0, 0, 0, 0, 0]
        self.schritte_oben = 0
        self.schritte_links = 0
        self.schritte_unten = 0
        self.schritte_rechts = 0                                

        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_w:
                    print("Figur hoch w")
                    self.richtung = [1, 0, 0, 0, 0]
                    self.schritte_oben += 1

                elif event.key == pygame.K_a:
                    print("Figur links a")
                    self.richtung = [0, 1, 0, 0, 0]
                    self.schritte_links += 1
                    img = pygame.image.load("Grafik/Link/Link_Links.png")
                    self.image = pygame.transform.scale(img, (32, 54))
                    self.rect = self.image.get_rect()

                elif event.key == pygame.K_s:
                    print("Figur runter s")
                    self.richtung = [0, 0, 1, 0, 0]
                    self.schritte_unten += 1
                    img = pygame.image.load("Grafik/Link/unten1.png")
                    self.image = pygame.transform.scale(img, (32, 54))
                    self.rect = self.image.get_rect()

                elif event.key == pygame.K_d:
                    print("Figur rechts d")
                    self.richtung = [0, 0, 0, 1, 0]
                    self.schritte_rechts += 1
                    img = pygame.image.load("Grafik/Link/Link.png")
                    self.image = pygame.transform.scale(img, (32, 54))
                    self.rect = self.image.get_rect()            

        screen.blit (self.image, (self.x, self.y))        

class Karte ():
    def __init__ (self, x, y, bewegen_x, bewegen_y, geschw):
        img = pygame.image.load("Grafik/map.png")
        self.image = pygame.transform.scale(img, (aufloesung.current_w * 12, aufloesung.current_w * 12))
        self.rect = self.image.get_rect()
        self.x = x
        self.y = y
        self.bewegen_x = bewegen_x
        self.bewegen_y = bewegen_y
        self.geschw = geschw

    def update(self):
        for event in pygame.event.get():               
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_w:
                    print("Karte w gedrückt")
                    self.bewegen_y += self.geschw
                elif event.key == pygame.K_s:
                    print("Karte s gedrückt")
                    self.bewegen_y -= self.geschw
                elif event.key == pygame.K_a:
                    print("Karte a gedrückt")
                    self.bewegen_x += self.geschw
                elif event.key == pygame.K_d:
                    print("Karte d gedrückt")
                    self.bewegen_x -= self.geschw

            if event.type == pygame.KEYUP:
                if event.key == pygame.K_w:
                    print("Karte w losgelassen")
                    self.bewegen_y = 0
                elif event.key == pygame.K_s:
                    print("Karte s losgelassen")
                    self.bewegen_y = 0
                elif event.key == pygame.K_a:
                    print("Karte a losgelassen")
                    self.bewegen_x = 0
                elif event.key == pygame.K_d:
                    print("Karte d losgelassen")
                    self.bewegen_x = 0

        if self.bewegen_x != 0:
            self.x += self.bewegen_x

        if self.bewegen_y != 0:
            self.y += self.bewegen_y

        if self.x >= -120:
            self.bewegen_x = 0
            self.x = -120

        if self.y >= -400:
            self.bewegen_y = 0
            self.y = -400


        screen.blit (self.image, (self.x, self.y))
        
def main():
    spieler1 = Spieler(aufloesung.current_w // 2, aufloesung.current_h // 2, 3)
    karte1 = Karte(-500, -500, 0, 0, 2)

    # Schleife Hauptprogramm
    while spielaktiv:    

          
        karte1.update()
        spieler1.update()
                        
        
        # Fenster aktualisieren
        pygame.display.flip()

        # Refresh-Zeiten festlegen
        clock.tick(60)

    pygame.quit()
    quit()

if __name__ == '__main__':
    main()
Ich bin noch lange nicht fertig und ein paar Sachen sind derzeit nicht implementiert.
Nun zu meinem Problem:
Ich möchte mit den Tasten w,a,s,d sowohl die Karte im Hintergrund verschieben, als auch die Anzeige für die Figur verändern. Die Figur bleibt dafür immer in der Mitte des Bildschirmes stehen.
Später wird die Figur dann animiert und tut nur so, als würde sie laufen.
Hoffe es ist verständlich, was ich meine.

Im Hauptprogramm rufe ich dafür die entsprechenden Methoden ab.

Doch das Problem ist, dass Tastaturabfragen sehr oft verschluckt werden. Entweder verändert sich die Figur nicht, oder die Karte nicht.
Das Problem habe ich erst, seit ich Klassen nutze.

Wo ist mein Fehler?

Ich könnte die Figur auch in der Kartenklasse ändern, aber das wäre ja auch nicht Sinn der Sache.....
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patsche: Hat nicht wirklich etwas mit Klassen zu tun: Wenn Du an zwei verschiedenen Stellen Schleifen hast die Ereignisse *konsumieren* bekommt die jeweils andere Stelle im Code diese Ereignisse nicht mehr mit.

Mögliche Lösung: eine Schleife die die Ereignisse durchgeht und jedes Ereignis an eine Methode auf jedem Objekt übergibt die irgendwas aufgrund von Ereignissen machen. Also in diesem Fall an Spieler- und Karten-Objekte.

Die beiden Objekte greifen mindestens auf `screen` einfach so magisch zu. Das Hauptprogramm sollte wirklich endlich in einer Funktion verschwinden damit so etwas nicht passiert. Und die `__init__()` sollte alle Attribute anlegen. Da sollten in `update()` nicht plötzlich neue hinzukommen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Patsche
User
Beiträge: 43
Registriert: Samstag 23. Oktober 2021, 00:17

__blackjack__ hat geschrieben: Freitag 21. Juli 2023, 15:47 @Patsche: Hat nicht wirklich etwas mit Klassen zu tun: Wenn Du an zwei verschiedenen Stellen Schleifen hast die Ereignisse *konsumieren* bekommt die jeweils andere Stelle im Code diese Ereignisse nicht mehr mit.
Ah okay.....ich nahm das halt an, weil es ohne Klassen kein Problem ist. Da kann ich ohne Probleme Schleifen für mehrere Spielereingaben abfragen. Nach der Umstellung eben nicht mehr.
__blackjack__ hat geschrieben: Freitag 21. Juli 2023, 15:47 Mögliche Lösung: eine Schleife die die Ereignisse durchgeht und jedes Ereignis an eine Methode auf jedem Objekt übergibt die irgendwas aufgrund von Ereignissen machen. Also in diesem Fall an Spieler- und Karten-Objekte.
Das muss ich mir mal angucken, wie das geht. Danke für den Hinweis.

__blackjack__ hat geschrieben: Freitag 21. Juli 2023, 15:47 Die beiden Objekte greifen mindestens auf `screen` einfach so magisch zu. Das Hauptprogramm sollte wirklich endlich in einer Funktion verschwinden damit so etwas nicht passiert.
Magst du mir vielleicht nochmal zeigen, wie das richtig gehört?
Ich bin einfach noch unvertraut mit der ganzen Materie. In meinem Leben habe ich mich bisher wenig mit Programmier beschäftigt. Ich weiß du meinst es gut, aber das ist für einen Anfänger alles viel, der das nicht beruflich macht. Deswegen gib mir bitte Zeit, das zu verstehen und zu lernen.

__blackjack__ hat geschrieben: Freitag 21. Juli 2023, 15:47 Und die `__init__()` sollte alle Attribute anlegen. Da sollten in `update()` nicht plötzlich neue hinzukommen.
Aaaah....ich gucke halt zahlreiche Videos auf Youtube zu Pygame und da machen das viele.
Habe ich mir nichts bei gedacht und mir gedacht, ich in der __init__() nur Werte übergeben muss, die irgendwie wichtig für die Übergabe an andere Stellen außerhalb der Klasse müssten.
Werde ich ändern.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

das was mit "[...]Objekte greifen mindestens auf `screen` einfach so magisch zu." gemeint ist, ist dass `screen` auf Modulebene (der Code ohne Einrückungen) definiert ist. Damit ist `screen` global verfügbar und das ist ein Zustand den man nicht will. Auf Modulebene werden nur Klassen, Funktionen und Konstanten definiert.
`screen` würde man vermutlich in der `main`-Funktion erstellen und wenn das eine Klasse benötigt oder eine andere Funktion, dann übergibt man `screen` als Argument.

Ein nicht lauffähiger Code, der die Erklärung beschreibt und du auf deinen anwenden kannst:

Code: Alles auswählen

class Spieler:
    def __init__(self, screen):
        self.screen = screen

    def update_irgendwas(self):
        # Z.B. hier irgendwas mit self.screen machen



def main():
    screen = pygame.display.set_mode((0,0), pygame.FULLSCREEN)
    spieler = Spieler(screen)
    ...

if __name__ == '__main__':
    main()

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

Ich würde es eher an die Methode übergeben die dann am Ende zeichnet. Weil der Bildschirm ja nicht den Zustand vom Spieler ausmacht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Code: Alles auswählen

elif event.key == pygame.K_a:
                    print("Figur links a")
                    self.richtung = [0, 1, 0, 0, 0]
                    self.schritte_links += 1
                    img = pygame.image.load("Grafik/Link/Link_Links.png")
                    self.image = pygame.transform.scale(img, (32, 54))
                    self.rect = self.image.get_rect()
Du möchtest nicht wirklich das print() da drin haben, die Ausgabe in die Konsole ist langsam.
Du möchtest nicht wirklich bei jedem Tastendruck ein Image von welchem Datenträger auch immer einlesen. Das macht man zu Beginn einmal und dann sind alle Images im Speicher.
Das gleiche gilt für das transform_scale(). Das macht man auch zu Spielbeginn einmalig, nach dem einlesen der Images.

Setz das mal um und du wirst eine wesentliche Steigerung der Geschwindigkeit bemerken.

Ich gehe davon aus, du interessierst dich für Spiele-Entwicklung, egal in welcher Programmiersprache.

Schau dir mal diese Webseite der Harvard Uni an: https://cs50.harvard.edu/games/2018/

Da werden verschiedenste Spiele, die fast jeder kennt, programmiert. Benutzt wird dazu LUA unter Verwendung der Bibliothek Löve: https://love2d.org/

Ich habe damit viel Spaß gehabt und eine Menge gelernt, was man bei Spieleentwicklung beachten muss.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
Patsche
User
Beiträge: 43
Registriert: Samstag 23. Oktober 2021, 00:17

Vielen Dank für die Hinweise.
Für mich ist das mit den ganzen Klassen wohl immer noch nicht ganz verständlich. Grob verstehe ich es, aber die Übergabe von Daten von einer in eine andere Klasse verstehe ich noch nicht ganz. Scheint wohl auch kein guter Stil zu sein?
Aber anders geht es hier ja irgendwie nicht.....
Auch die Steuerungseingabe werde ich überarbeiten müssen. Mit einer For-Schleife in 2 Klassen gleichzeitig ist es sehr ineffektiv.
Dafür nutze ich jetzte einfach die Funktion

Code: Alles auswählen

pygame.key.get_pressed()
Dann muss ich keine Events mehr abfragen.

Ich bin jetzt erstmal wieder einen Schritt zurück gegangen um die Basics mit Klassen zu verstehen.
Ich habe jetzt 2 Dateien erstellt.
Datei 1:

Code: Alles auswählen

import pygame
import player


class Game:
    def __init__(self):
        pygame.init()
        self.window_width = 800
        self.window_height = 600
        self.window = pygame.display.set_mode((self.window_width, self.window_height))
        pygame.display.set_caption("PySnake by Maro Hutzfeld")
        self.clock = pygame.time.Clock()
        self.player = player.Player(self, 32, 32, 20, 20, 100)
        self.run()

    def run(self):
        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False

                if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                    running = False

            self.delta_time = self.clock.tick(60) / 1000
            self.window.fill((0,0,0))
            self.player.update()
            pygame.display.update()


        pygame.quit()    
        
game = Game()
Datei 2:

Code: Alles auswählen

import pygame

class Player:
    def __init__(self, game, x, y, breite, hoehe, speed):
        self.x = x
        self.y = y
        self.breite = breite
        self.hoehe = hoehe
        self.speed = speed
        self.game = game
        self.surface = game.window
        

    def update(self):
        self.movement()
        self.draw()

    def draw(self):
        pygame.draw.rect(self.surface, "red", (self.x, self.y, self.breite, self.hoehe))

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

        if keys[pygame.K_w]:
            self.y -= self.speed * self.game.delta_time

        elif keys[pygame.K_a]:
            self.x -= self.speed * self.game.delta_time

        elif keys[pygame.K_s]:
            self.y += self.speed * self.game.delta_time

        elif keys[pygame.K_d]:
            self.x += self.speed * self.game.delta_time
Es fehlt noch die die Definition der main Funktion. Das Programm wirft jetzt einen schwarzen Bildschirm aus mit einem roten Rechteck, weches ich mit den Tasten WASD Steuern kann. Und das alles hoffentlich Regel- und Klassenkonform.

Aber auch hier habe ich jetzt wieder ein Problem.
Ich möchte jetzt einfach einen 2. Spieler hinzufügen.
Ich dachte ich könnte in Datei 1 einfach

Code: Alles auswählen

self.player2 = player.Player(self, 200, 200, 20, 20, 100)
hinzufügen, aber dieser wird gar nicht gezeichnet.....

Ich finde das alles sehr schwer zu greifen, was man zuerst nennen muss, wann was übergeben wird usw.
Da kam ich mit meinen 1000 Variablen ohne Klassen besser zurecht. Das war zwar viel Schreibarbeit, aber das hat funktioniertund ich hatte Erfolgserlebnisse, die jetzt leider ausbleiben.
Mit der Objektorientierten Programmierung verzweifel ich etwas....
Das nur so am Rande mal erwähnt :

Entschuldigt, wenn ich dem Einen oder Anderen auf den Sack gehe. Ich gebe mir wirklich Mühe und mache das nicht, um irgendjemanden zu nerven. Ich will das einfach nur verstehen und begreifen. :!: :idea:
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patsche: Grundsätzlich brauchst Du die Schleife schon, denn `get_pressed()` funkioniert nur wenn die Ereignisse irgendwo abgearbeitet werden. Und man bekommt mit der Funktion immer nur einen kurzen Blick auf den Zustand zu dem Zeitpunkt wo man die Funktion aufruft, das heisst man kann Tastendrücke verpassen. Auf Taste drücken und wieder loslassen kann man so auch nicht reagieren.

Das mit dem aufteilen auf Module würde ich noch sein lassen. In einem Modul sollte mehr als eine Klasse sein.

Die `__init__()` sollte ein Objekt initialisieren, so dass sie der Aufrufer, der ein Objekt davon erstellt, es dann irgendwie benutzen kann. Dazu muss die ”endlich” sein, und nicht von sich aus einfach das gesamte Spiel ablaufen lassen. `game` wird ja auch gar nicht verwendet.

Aber auch sonst ist `Game` hier keine sinnvolle Klasse. Klassen die nur aus einer `__init__()` und *einer* Methode bestehen, die beide nacheinander aufgerufen werden, sind in der Regel ein Zeichen, dass da einfach eine Funktion etwas umständlicher geschrieben wurde als es sein müsste.

Code: Alles auswählen

#!/usr/bin/env python3
import pygame


class Player:
    def __init__(self, rect, speed):
        self.rect = rect
        self.speed = speed

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

        if keys[pygame.K_w]:
            self.rect.y -= self.speed * delta_time

        elif keys[pygame.K_a]:
            self.rect.x -= self.speed * delta_time

        elif keys[pygame.K_s]:
            self.rect.y += self.speed * delta_time

        elif keys[pygame.K_d]:
            self.rect.x += self.speed * delta_time

    def draw(self, surface):
        pygame.draw.rect(surface, "red", self.rect)

    def update(self, delta_time, surface):
        self.movement(delta_time)
        self.draw(surface)


def main():
    pygame.init()
    try:
        window_width = 800
        window_height = 600
        window = pygame.display.set_mode((window_width, window_height))
        pygame.display.set_caption("PySnake by Maro Hutzfeld")
        clock = pygame.time.Clock()
        player = Player(pygame.Rect(32, 32, 20, 20), 100)
        while True:
            for event in pygame.event.get():
                if (
                    event.type == pygame.QUIT
                    or event.type == pygame.KEYDOWN
                    and event.key == pygame.K_ESCAPE
                ):
                    return

            delta_time = clock.tick(60) / 1000
            window.fill((0, 0, 0))
            player.update(delta_time, window)
            pygame.display.update()
    finally:
        pygame.quit()


if __name__ == "__main__":
    main()
Damit eine zweite Spielfigur auch gezeichnet wird, muss man genau wie bei der ersten regelmässig die `update()`-Methode aufrufen. Denn dort passiert im Code ja das zeichnen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Patsche
User
Beiträge: 43
Registriert: Samstag 23. Oktober 2021, 00:17

Danke für die Hilfe.
Ich habe mal deinen Code etwas abgeändert, sodass die Figur jetzt dauerhaft läuft.
Auch habe ich eine Klasse namens Coin angelegt.
Jetzt weiß ich aber wieder nicht, wie ich die Kollisionsabrage Klassenübergreifend mache.
Hier mal der Code:

Code: Alles auswählen

#!/usr/bin/env python3
import pygame, random


class Player:
    def __init__(self, rect, move, speed):
        self.rect = rect
        self.move = move
        self.speed = speed

    def movement(self, delta_time):
        if self.move == 0:
            self.rect.move_ip(self.speed * delta_time, 0)

        if self.move == 1:
            self.rect.move_ip(-self.speed * delta_time, 0)

        if self.move == 2:
            self.rect.move_ip(0, -self.speed * delta_time)

        if self.move == 3:
            self.rect.move_ip(0, self.speed * delta_time)
        
        keys = pygame.key.get_pressed()

        if keys[pygame.K_w]:
            self.move = 2

        if keys[pygame.K_a]:
            self.move = 1

        if keys[pygame.K_s]:
            self.move = 3

        if keys[pygame.K_d]:
            self.move = 0

    def draw(self, surface):
        pygame.draw.rect(surface, "red", self.rect)

    def update(self, delta_time, surface):
        self.movement(delta_time)
        self.draw(surface)


class Coin:
    def __init__(self, rect):
        self.rect = rect
        
    def update(self, surface):
        self.draw(surface)
        self.kollision(surface)       

    def draw(self, surface):
        pygame.draw.rect(surface, "blue", self.rect)

    def kollision(self):
        if self.rect.colliderect(self.player.rect):
            print("KNALL COIN")
            self.rect = pygame.Rect(random.randint(20, 700), random.randint(20, 500), self.rect)

def main():
    pygame.init()
    try:
        window_width = 800
        window_height = 600
        window = pygame.display.set_mode((window_width, window_height))
        pygame.display.set_caption("PySnake by Maro Hutzfeld")
        clock = pygame.time.Clock()
        player = Player(pygame.Rect(32, 32, 20, 20), 0, 100)
        coin = Coin(pygame.Rect(random.randint(20, 700), random.randint(20, 500), 20, 20))
        while True:
            for event in pygame.event.get():
                if (
                    event.type == pygame.QUIT
                    or event.type == pygame.KEYDOWN
                    and event.key == pygame.K_ESCAPE
                ):
                    return

            delta_time = clock.tick(60) / 1000
            window.fill((0, 0, 0))
            player.update(delta_time, window)
            coin.update(window)
            pygame.display.update()
    finally:
        pygame.quit()

if __name__ == "__main__":
    main()
Also genau um diese Zeilen:

Code: Alles auswählen

def kollision(self):
        if self.rect.colliderect(self.player.rect):
            print("KNALL COIN")
            self.rect = pygame.Rect(random.randint(20, 700), random.randint(20, 500), self.rect)
Ich raffe es anscheinend nicht..... Immer wenn ich etwas verstanden habe, dann bekomme ich neuen Code gezeigt, den ich wieder nicht verstehe. Dann wurde etwas wieder anders übergeben, als in dem anderen usw.
Naja.
Ich werde weiter am Ball bleiben und hoffe jemand hilft mir weiter.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

`self.player` gibt es halt nicht. Sollte es auch nicht geben denn der Spieler gehört ja nicht zur Münze. Die Attribute eines Objekts beschreiben das Objekt und eine Münze hat ja weder eine einen Spieler als Bestandteil um die Münze zu beschreiben, noch eine wirklich enge Beziehung zu einem Spieler. Da würde man eher das womit die Kollision geprüft wird als Argument übergeben. Es kann ja auch mehr als einen Spieler geben. Spätestens dann müsste das variabler gestaltet sein.

Man könnte sogar fragen ob der Test überhaupt so eng der Münze zuzuordnen ist, oder ob eine Funktion die allgemein zwei Objekte mit `rect`-Attribut bekommt und testet ob die kollidieren. Dann kann man damit auch testen ob Spieler mit anderem Spieler kollidiert, oder auch mit anderen Gegenständen.

Wirklich spezifisch für die Münze ist hier das verhalten wenn eine Kollision mit einem Spieler stattgefunden hat.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Patsche
User
Beiträge: 43
Registriert: Samstag 23. Oktober 2021, 00:17

Ich hatte mir halt gedacht, dass ich innerhalb der Klassen schon die Rechtecke definiere.
Und innerhalb des Objektes Münze möchte ich eine Kollisionsabfrage mit der Klasse Player machen.
Es soll nämlich unabhängig davon sein, ob Spieler 1 oder ein anderer Spieler die Münze berührt.

Wie kann ich denn hier in diesem Beispiel der Klasse Münze, das Rechteck von Klasse Spieler übergeben?
Bei Kollision, soll eine neue Münze an einer anderen Stelle auf dem Spielfeld erscheinen.
Benutzeravatar
Patsche
User
Beiträge: 43
Registriert: Samstag 23. Oktober 2021, 00:17

Darf ich eigentlich innerhalb der main Funktion noch andere Funktionen definieren?
Der Grund ist der, dass ich jetzt eine Funktion mit der Kollisionsabfrage geschrieben haben, aber die Instanzierung der Klassen Player und Coin wird ja richtigerweise erst innerhalb der main-Funktion gemacht.

Wenn ich meine Funktion jetzt oberberhalb definiere, kennt sie die Attribute nicht.....

Code: Alles auswählen

.
.
.
def kollision():
    if player.rect.colliderect(coin.rect):
        print("TREFFER")
        coin.rect = pygame.Rect(random.randint(20, 700), random.randint(20, 500), 20, 20)

def main():
    pygame.init()
    try:
        window_width = 800
        window_height = 600
        window = pygame.display.set_mode((window_width, window_height))
        pygame.display.set_caption("PySnake by Maro Hutzfeld")
        clock = pygame.time.Clock()

        while True:
            for event in pygame.event.get():
                if (
                    event.type == pygame.QUIT
                    or event.type == pygame.KEYDOWN
                    and event.key == pygame.K_ESCAPE
                ):
                    return
            player = Player(pygame.Rect(32, 32, 20, 20), 0, 100)
            coin = Coin(pygame.Rect(random.randint(20, 700), random.randint(20, 500), 20, 20))
            kollision()
.
.
.
Benutzeravatar
Patsche
User
Beiträge: 43
Registriert: Samstag 23. Oktober 2021, 00:17

Wenn ich das richtig verstehe, dann mache ich das auch hier, in dem ich der Funktionen die nötigen Parameter mitgebe.
Das könnte also so aussehen:

Code: Alles auswählen

def kollision(a, b):
    if a.colliderect(b):
        print("TREFFER")
        c = pygame.Rect(random.randint(20, 700), random.randint(20, 500), 20, 20)
        return c
In der main Funktion dann so:

Code: Alles auswählen

kollision(player.rect, coin.rect)
Das klappt so auch, aber ich müsste jetzt c an die Klasse übergeben. Das weiß ich auch wieder nicht, wie man das macht.....
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patsche: Also sind das Problem nicht Klassen, sondern das wir Funktionen ”übersprungen” haben. Das ist ja gerade der Sinn von Funktionen, dass sie einen eigenen Namensraum haben wo weder Namen aus anderen Funktionen bekannt sind, noch Namen die man lokal definiert einen Einfluss auf Code ausserhalb der Funktion haben. Alles was eine Funktion ausser Konstanten benötigt, übergibt man als Argument. Wenn eine Funktion Ergebnisse hat, werden die mit ``return`` an den Aufrufer zurückgegeben. Alles was da drin an Namen definiert wird, interessiert ausserhalb nicht.

Man müsste hier also `player` und `coin` als Argumente übergeben. Ich denke das ist ein bisschen zu wenig und zu speziell um da eine eigene Funktion draus zu machen. Die drei Zeilen kann man auch in die Hauptschleife schreiben.

Die `rect`-Objekte übergeben kann man auch, aber dann macht die Funktion noch weniger Sinn als wenn man die beiden Spielobjekte übergeben würde.

Zu der Frage nach dem `c`: Einfach Zuweisen ginge, allerdings müsste man dann testen ob ein `Rect` zurückgegeben wurde und die Funktion sollte dann auch *explizit* `None` zurückgeben wenn es keine Kollision gab. Oder man gibt im Falle von keiner Kollision das ursprüngliche `Rect`-Objekt zurück. Dann kann man bedingungslos die Zuweisung machen. Also:

Code: Alles auswählen

def kollision(a, b):
    if a.colliderect(b):
        print("TREFFER")
        return pygame.Rect(
            random.randint(20, 700), random.randint(20, 500), 20, 20
        )

    return None

...

    rect = kollision(player.rect, coin.rect)
    if rect is not None:
        coin.rect = rect
oder

Code: Alles auswählen

def kollision(a, b):
    if a.colliderect(b):
        print("TREFFER")
        return pygame.Rect(
            random.randint(20, 700), random.randint(20, 500), 20, 20
        )

    return b

...

    coin.rect = kollision(player.rect, coin.rect)
Aber wie gesagt, würde ich beides nicht machen.

Was nicht so schön ist, ist die Codewiederholung mit der die Münze platziert wird. Das macht unnötig Arbeit beim schreiben und weiterentwickeln des Codes, denn wenn man etwas an der Spielfeldgrösse oder der Münzengrösse ändern will, muss man das im Moment an mehreren Stellen machen, und dabei besteht immer die Gefahr, dass man nicht an alle denkt, oder nicht alle gleichartig ändert.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Nachtrag: Funktionen *in* Funktionen definieren kann man, sollte man aber nicht. Da hat man dann ja wieder das Problem das man mehr Namen hat als man will, und die sind dann auch nicht wirklich testbar und wiederverwendbar. Lokale Funktionen sind was für Closures, also wenn man diese Funktion dann als Rückgabewert verwendet, oder für sehr einfache Funktionen wo ein ``lambda``-Ausdruck aus technischen Gründen nicht geht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Patsche
User
Beiträge: 43
Registriert: Samstag 23. Oktober 2021, 00:17

Super vielen Dank.
Das mit der Funktion ist ja auch nur eine Krücke gewesen.
Ich würde gerne wissen, wie ich das player.rect direkt an die Klasse Coin weiterreichen kann.
Das wäre doch die beste Lösung, oder nicht?
Ich finde dafür aber kein passendes Beispiel im Netz, wie ich das mache.
Ich finde etwas zu Vererbung und Methoden, aber nicht, wie ich Argumente aus einer Klasse in einer Methode einer anderen Klasse verwenden kann.....
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patsche: Was heisst „direkt“? Im Grunde gar nicht beziehungsweise will man das gar nicht. Dann hat man plötzlich in `Coin`-Objekten einen Zustand der sich auf magische Weise von aussen ändert. Und das wäre dann ja auch nur *ein* Player-Rect. Wie soll das aussehen wenn es mehr als einen Spieler gibt?

Die Verwendung des Wortes Klasse ist hier auch etwas unscharf. Man reicht nichts an Klassen weiter und es gibt keine Argumente aus einer Klasse. Klassen sind die Baupläne um Objekte zu erstellen. Die Objekte haben Methoden, denen dann Argumente übergeben werden. Also mindestens das Objekt selbst als erstes Argument was per Konvention `self` heisst. Und wie Werte in eine Methode kommen unterscheidet sich nicht davon wie Werte in eine Funktion kommen, das hat also mit Klassen nicht wirklich etwas zu tun. Die scheinbare Ausnahme des `self`-Arguments ist keine, denn auch da übergibt man das Argument ja eigentlich explizit, nur halt nicht in den Klammern des Aufrufs, sondern vor dem Punktoperator wenn man auf die Methode auf dem Objekt zugreift.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Patsche
User
Beiträge: 43
Registriert: Samstag 23. Oktober 2021, 00:17

Achsoooo.....ich dachte genau dafür sind eben auch Klassen da.
In diesem Fall:
Wenn irgendein Spieler mit der Klasse Coin kollidiert, dann soll die und die Methode ausgeführt werden.
Das geht dann ja gar nicht und ist auch nicht so gewollt.
Sehe ich das jetzt richtig?
Das mache ich dann in der main-Funktion.
Ist das so richtig?
Ich müsste dann ja aber für jedes Instanz eine einzelne Kollisionsabfrage machen.

Ich beschreibe mal, wie ich es mir dachte.
Ich habe eine Klasse Mensch und eine Klasse Apfel.
Jetzt instanziere ich 4 Menschen und 4 Äpfel.
Und egal welcher Mensch mit egal welchem Apfel in Berührung kommt, der soll den Apfel essen.
Genau das wollte ich schon innerhalb der Klassen definieren.
Dafür muss ich ja aber wissen, wo sich der Mensch befindet und genau das kann ich dem Objekt Apfel gar nicht mitteilen, weil das so gar nicht vorgesehen ist.
Ist das so richtig?
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patsche: Den Kollisionstest Spieler mit Apfel (oder Äpfel) macht man einer Stelle die alle Spieler und Äpfel kennt. Also zum Beispiel in der `main()`-Funktion.

Für jedes Objekt eine eigene Kollisionsabfrage zur Laufzeit ja, aber man muss natürlich nicht für jede Kombination extra Code schreiben. Ich weiss nicht ob wir die ”Regel” das man keine Namen nummeriert schon ausführlicher hatten, aber man nummeriert keine Namen. Bei einer festen Anzahl von zwei Spielern und wenn für die nicht alles gleich gemacht werden soll, hat man vielleicht noch `spieler_a` und `spieler_b`, aber in dem 4 Spieler und 4 Äpfel Szenario hat man keine 8 Namen und schreibt man keine 16 Kollisionstest, sondern hat eine Liste mit Spielern und eine Liste mit Äpfeln und zwei Schleifen die dann alle Äpfel gegen alle Spieler testet.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Patsche
User
Beiträge: 43
Registriert: Samstag 23. Oktober 2021, 00:17

__blackjack__ hat geschrieben: Samstag 22. Juli 2023, 22:47 @Patsche: Den Kollisionstest Spieler mit Apfel (oder Äpfel) macht man einer Stelle die alle Spieler und Äpfel kennt. Also zum Beispiel in der `main()`-Funktion.
Ok. Dann habe ich das richtig verstanden.
__blackjack__ hat geschrieben: Samstag 22. Juli 2023, 22:47 Ich weiss nicht ob wir die ”Regel” das man keine Namen nummeriert schon ausführlicher hatten, aber man nummeriert keine Namen.
Abgespeichert.
__blackjack__ hat geschrieben: Samstag 22. Juli 2023, 22:47 .... `spieler_a` und `spieler_b`, aber in dem 4 Spieler und 4 Äpfel Szenario hat man keine 8 Namen und schreibt man keine 16 Kollisionstest, sondern hat eine Liste mit Spielern und eine Liste mit Äpfeln und zwei Schleifen die dann alle Äpfel gegen alle Spieler testet.
Okay. Das hört sich plausibel an. Werde ich versuchen umzusetzen.


Zwei Fragen hätte ich noch:
Wenn jetzt jeder Spieler eine eigene Steuerung bekäme, käme das mit in die Klasse, oder würde man die Steuerung auch in der Mainfunktion erstellen?
Auch weiß ich nicht, ob Spielerimages mit in die Klasse kommen.

Alle Youtubevideos und Tutorial behandeln immer nur ein Spieler und dann wird die Steuerung mit in die Klasse übernommen.
Oder sie verwenden gar keine Klasse.....
Bisher haben mir Klassen auch eher Probleme, als Hilfe gebracht , wobei Klassen an sich eine gute Idee sind. :lol: :lol:
Antworten