Pygame kollisionen

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Hallo ich versuche ein spiel zu programmieren, aber scheitere bei der Kollision eines Stachels und dem Spieler. :roll:
Wenn ihr die Bilder dazu braucht kann ich einen 1️⃣ Drive Link schicken, aber ich hoffe dass ihr mir anhand des Codes helfen könnt.🙃
Liebe Grüsse Fire Spike :)

Code: Alles auswählen

import sys
import pygame
import random
import time

WINDOW_TITLE = "Game" #Title of window
WHITE = (255, 255, 255) #White in RGB

FPS = 60 #Frames per second

screenx = 800 #x coordinates of the window
screeny = 1600 #y coordinates of the window

spikex = 816
spikey = 1056
playerx = 700 #x coordinates of the player

icon_paths = {
    "small" : "./Images/Icons/Icon_small.png", #71x71
    "medium" : "./Images/Icons/Icon_medium", #107x107
    "large" : "./Images/Icons/Icon_large" #142x142
}

pygame.init()
screen = pygame.display.set_mode((screeny, screenx))
pygame.display.set_caption(WINDOW_TITLE)

screen.fill(WHITE)
clock = pygame.time.Clock()

spikes = [] # List of all Spikes
del_queue = [] #Queue for spikes to be deleted

last_time = time.time()

class Spike():
    def __init__(self):
        self.step = 20
        self.playerx = 0 #- spikex
        self.playery = random.randrange(0, 1600)

    def down(self):
        self.playerx += self.step
        pygame.draw.polygon(
            screen,
            (255, 0, 0),
            (
                (self.playery, self.playerx),
                (self.playery + 100, self.playerx),
                (self.playery + 50, self.playerx + 100)
            )
        )
        
class Cube():
    def __init__(self, playery):
        self.step = 10
        self.playery = playery
        self.playericon = pygame.image.load(icon_paths["small"])

    def left(self):
        self.playery -= self.step
        self.update()
        
    def right(self):
        self.playery += self.step
        self.update()
        
    def update(self):
        screen.blit(self.playericon, (self.playery, playerx))

player = Cube(650)

while True:
    screen.fill(WHITE)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
            
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                sys.exit()

    if pygame.key.get_pressed()[pygame.K_RIGHT]:
        player.right()

    if pygame.key.get_pressed()[pygame.K_LEFT]:
        player.left()
    
    if time.time() - last_time > 0.1:
        spikes.append(Spike())
        last_time = time.time()
        
    for i in range(len(spikes)):
        spikes[i].down()            
        if spikes[i].playerx > 800:
            del_queue.append(i)
        
    counter = 0
    for i in del_queue:
        del spikes[i-counter]
        counter += 1
            
    del_queue = []
    
    player.update()
    pygame.display.flip()
    clock.tick(FPS)
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Um Kollisionen zu pruefen gibt es pygame.rect, und das sollest du auch benutzen. Und statt dieses wirklich umstaendlichen del_queue-Gefummels ist es viel einfacher, NICHT mit dem beruehmten anti-pattern for i in range(len(spikes)) zu arbeiten, sondern gleich ueber die Liste zu iterieren. Und einfach in einer zweiten Liste die Spikes aufsammeln, die du behalten willst. Last but not least gehoert die Pruefung ob ein Spike noch sichtbar ist in eine Methode des Spikes, und nicht in die main-Funktion - die sollte da agnostisch sein. Und wenn du eh jede Runde einmal player update aufrufst (was ja auch richtig ist), dann ist es aber ein Fehler, dass in left/right zu machen.
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Code optimiert💪🏻
Die Doku redet bei pygame.Rect immer nur über Rechtecke⁉️
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ja und? Das reicht üblicherweise aus. Pixelgenaue Kollision braucht man selten bis nie. Zur Not zb 2 Rechtecke.
Benutzeravatar
__blackjack__
User
Beiträge: 13006
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Egal ob nun pixelgenau oder Rechteck, für beides (und mehr) hat `pygame.sprite` Lösungen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Danke für die Idee mit Sprites. :D
Ich versuchte das umzusetzen, die Kollision funktioniert auch aber nur sehr ungenau :? 😭
Es kam auch noch ein neues Problem :oops: und zwar dass das Surface vom Spike den Player überlappt, das sollte hinter dem Player sein und das Weiss sollte transparent sein.
Bilder der Fehler mit dem Code im Link
Könnt ihr mir helfen❔
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Das mit der Transparents habe ich gelöst könnt ihr mir bitte 🌸 noch bei der Kollision helfen?

Edit:
Link aktualisiert.
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Voy a hacer un experimento

Code: Alles auswählen

import sys
import pygame
import random
import time

WINDOW_TITLE = "Game" # Title of window

WHITE = (255, 255, 255) # White in RGB
RED = (255, 0, 0) # Red in RGB

FPS = 60 # Frames per second

screenx = 800 # x coordinates of the window
screeny = 1600 # y coordinates of the window

playerx = 700 # x coordinates of the player

icon_paths = {
    "small" : "./Images/Icons/Icon_small.png", # small icon
    "medium" : "./Images/Icons/Icon_medium", # medium icon
    "large" : "./Images/Icons/Icon_large" # large icon
}

icon_sizes = {
    "small" : 71,
    "medium" : 107,
    "large" : 142
}

pygame.init()
screen = pygame.display.set_mode((screeny, screenx)) # set window size
pygame.display.set_caption(WINDOW_TITLE) # set window title

screen.fill(WHITE)
clock = pygame.time.Clock()

all_spikes = [] # List of all Spikes
visible_spikes = []# List of all visible spikes

last_time = time.time()

life = True

class Spike():
    def __init__(self):
        self.step = 20
        self.spikex = -100
        self.spikey = random.randrange(0, 1600)
        self.image = pygame.Surface((100,100), pygame.SRCALPHA)
        self.rect = self.image.get_rect(center=(self.spikey, self.spikex))
        self.mask = None
        
    def down(self):
        """
        move the spike down
        """
        self.spikex += self.step
        pygame.draw.polygon(
            self.image,
            RED,
            (
                (0, 0),
                (100, 0),
                (50, 100)
            )
        )
        self.rect = self.image.get_rect(center=(self.spikey, self.spikex))
        self.mask = pygame.mask.from_surface(self.image)
        screen.blit(self.image.convert_alpha(), self.rect)
        
    def is_visible(self):
        if self.spikex > 800:
            return True
        
        else:
            False
            
class Cube(pygame.sprite.Sprite):
    def __init__(self, playery):
        self.icon_size = "small" # size of icon
        self.step = 10
        self.playery = playery
        self.image = pygame.image.load(icon_paths[self.icon_size]).convert_alpha() # load the icon of the player
        self.rect = self.image.get_rect(center=(self.playery, playerx))
        self.mask = pygame.mask.from_surface(self.image)
        
    def left(self):
        """
        move the player to the left
        """
        if not self.playery < 0:
            self.playery -= self.step
            
        self.rect = self.image.get_rect(center=(self.playery, playerx))
        self.mask = pygame.mask.from_surface(self.image)
        
    def right(self):
        """
        move the player to the right
        """
        if not self.playery > screeny - icon_sizes[self.icon_size]:
            self.playery += self.step
            
        self.rect = self.image.get_rect(center=(self.playery, playerx))
        self.mask = pygame.mask.from_surface(self.image)
        
    def update(self):
        """
        update position of player
        """
        screen.blit(self.image, (self.playery, playerx))

player = Cube(650)

pygame.mixer.music.load("./Sounds/Death-Moon.mp3")
pygame.mixer.music.play()

while life:
    screen.fill(WHITE)
    all_spikes = visible_spikes
    visible_spikes = []
        
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
            
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                sys.exit()

    if pygame.key.get_pressed()[pygame.K_RIGHT]:
        player.right()

    if pygame.key.get_pressed()[pygame.K_LEFT]:
        player.left()
    
    player.update()
    
    if time.time() - last_time > 0.1:
        visible_spikes.append(Spike())
        last_time = time.time()
        
    for spike in all_spikes:
        spike.down()
        
    pygame.display.flip()
    for spike in all_spikes:
        if pygame.sprite.collide_mask(spike, player) != None:
            life = False
            pygame.mixer.music.stop()
            
        if not spike.is_visible():
            visible_spikes.append(spike)
            
    clock.tick(FPS)
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Es ist kein Problem wenn ihr nicht hilft, ABER ich würde gerne wissen wieso nicht😐
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das hat keinen besonderen Grund, man hat halt fuer bestimmte Dinge manchal keine Zeit. Dein Programm da oben zB kann ich nicht einfach nehmen und laufen lassen, weil die Bilder fehlen. Ich muss also anfangen, das umzuschreiben, oder Test-Bildchen machen. Und damit kostet das nicht 2 Minuten lesen, sondern 20, und dann muss man ja auch noch das Problem loesen. Manchmal hat man dazu Lust. Manchmal nicht. Glaub mir, du bist nicht der einzige.
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Ich habe ja einen Link mit allem gepostet, du kannst ja einfach den ganzen Ordner herunterladen🙃
https://1drv.ms/u/s!AhT7GNW97XSLogBGFYL ... T?e=LMKwod
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du hast einen Link gepostet, da steht was von "Bildern der Fehler". Nicht von "fehlenden Bildern".
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Hatte mich verschrieben. Im Link ist das vollständige Programm mit allen Dateien die es braucht, auch die Bilder in Images. Und auch das Bild das den Fehler zeigt das ich zu früh sterbe (Bug1.png).
Das ⬇️
Bild
Zuletzt geändert von Fire Spike am Freitag 27. März 2020, 20:50, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Fire Spike hat geschrieben: Sonntag 22. März 2020, 21:20 Bilder der Fehler mit dem Code im Link
Das heisst fuer mich, da sind Bilder drin, die den Fehler im Code zeigen. NICHT, dass da der Code drin ist, geschweige denn die Bilder, die man zum laufen lassen des Codes benoetigt.
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Hatte mich verschrieben🤯
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Wirst du das mal noch unter die 🔍 nehmen oder soll ich die Hoffnung auf eine Antwort aufgeben?😳
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich schaue es mir mal an. Aber garantiere keine Antwort Zeit!
Benutzeravatar
__blackjack__
User
Beiträge: 13006
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Fire Spike: Mal schauen: `sys` wird importiert, aber nirgends verwendet.

Da ist einiges an Kommentaren drin die einfach nur *sehr* offensichtliches ”erklären”, die also absolut überflüssig sind.

Es gibt auch falsche Kommentare. `screenx` und `screeny` sind keine Koordinaten sondern die Breite und Höhe des Fensterinhalts. Das könnte man auch im Namen besser zum Ausdruck bringen. Ausserdem fehlen dort Unterstriche, weil es schwerer ist Wörterzulesenundzuverstehenwenndiedirektaneinanderkleben. Und das sind Konstanten, sollten also KOMPLETT_GROSS geschrieben werden. Und last but not least sind hier X und Y vertauscht, was sehr verwirrend ist. Das Problem scheint sich durch den gesamten Code zu ziehen. X ist die horizontale Position und Y ist die vertikale Position.

`icon_paths` und `icon_sizes` sind auch Konstanten. Unschön ist hier, dass das ”parallele” Datenstrukturen sind, also beide die gleichen Schlüssel haben und die auf Werte abbilden. Es wäre sinnvoll(er) die Daten zu einer Datenstruktur zusammen zu fassen. Wobei mir `icon_sizes` auch nicht so wirklich sinnvoll erscheint, weil die Zahlen die dort stehen den Grössen der Bilder entsprechen und man diese Information hier also nicht fehleranfällig redundant manuell ins Programm schreiben muss.

Auf Modulebene sollte nur Code stehen, der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Daraus folgt dann das Funktionen und Methoden alles was sie ausser Konstanten benötigen als Argument(e) übergeben bekommen. Das betrifft `Spike.down()` und `Cube.update()` die beide `screen` übergeben bekommen müssen.

`pygame.init()` beinhaltet alle Subsysteme, `pygame.mixer.init()` danach ist also überflüssig.

Wenn es um das messen von Zeitdifferenzen geht ist `time.monotonic()` in der Regel die bessere Wahl als `time.time()`.

Die hart kodierte 650 für die X-Koordinate des Spielers ist nicht gut. Warum überhaupt 650 und nicht mittig bei 800?

`pygame.quit()` steht da zu oft im Programm. Das gehört einmal hinter die Hauptschleife. Und wenn man die Musik explizit stoppen will, dann *muss* man das *vor* `pygame.quit()` machen, und *sollte* das auch in *jedem* Fall machen, und nicht nur in einem der drei Fälle wo das Programm beendet werden soll.

Wobei man die drei Fälle auf zwei reduzieren kann in dem man die beiden ``if``\s in der ``for``-Schleife über die Ereignisse zusammenfasst. Da dann dort nur noch ein Flag aufgrund der Bedingung gesetzt wird, kann man sich sogar das ``if`` noch sparen.

Die `collide_mask()`-Funktion liefert laut Dokumentation einen Wahrheitswert. Es ist nicht nett von der Funktion im `False`-Fall `None` zu liefern, aber man sollte nicht auf `None` vergleichen wenn die Dokumentation das nicht garantiert.

Wenn man eine Kollision festgestellt hat, kann man die Schleife über die Spikes abbrechen.

Den folgenden Code muss ich einfach mal zitieren:

Code: Alles auswählen

            if not spike.is_visible():
                visible_spikes.append(spike)
Lies das mal bitte laut jemanden vor. Das kommt Dir nicht irgendwie komisch vor Spiles zu `visible_spikes` hinzuzufügen wenn sie *nicht* „visible“ sind? Hier ist doch offensichtlich etwas falsch benannt oder falsch implementiert. Und zwar `Spike.is_visible()` was genau das Gegenteil von dem liefert was der Name suggeriert.

Die Methode hat auch noch einen weiteren Fehler weil der ``else``-Zweig einen Ausdruck enthält der nichts tut und die Methode dadurch nicht `True` oder `False` liefert, sondern `True` oder `None`.

Man braucht hier auch gar kein ``if``/``else`` denn das Ergebnis hat man ja schon durch die Bedingung die da ausgewertet wird.

Die hart kodierte 800 in der Methode sollte man durch die Konstante mit der Fensterhöhe ersetzen.

Das mit dem zwei Listen `all_spikes` und `visible_spikes` ist unnörig kompliziert. Man würde da besser nur *eine* Liste führen und das am Schleifenende einfach immer alle aus dem Bild gefallenen Spikes raus filtern.

In den Klassen läuft viel zu viel Code unnötigerweise immer wieder aufs neue ab, obwohl der immer das gleiche Ergebnis liefert.

Die Spikes sehen alle gleich aus, aber Du malst nicht nur für jeden Spike immer das gleiche Bild sondern das auch noch jedes mal wenn sich der Spike nach unten bewegt. Es reicht das Bild *einmal* für *alle* Spikes zu malen. Das gleiche gilt für die Maske, die sich ja auch nicht ändert.

Beim auswürfeln der X-Koordinate sollte man die hart kodierte 1600 durch die Konstante ersetzen, welche die Fensterbreite festlegt.

Die X- und Y-Koordinate braucht man nicht speichern weil die bereits im `rect`-Objekt drin steckt.

`Cube.__init__()` ruft die `__init__()` der Basisklasse nicht auf.

Beim `Cube` wird `mask` mehrfach neu berechnet, obwohl sich der Wert dabei nie ändert.

`Cube.update()` heisst IMHO falsch und hat auf jeden Fall einen falschen Docstring, weil das was dort steht in der Methode gar nicht gemacht wird. Zudem wird damit die `Sprite.update()`-Methode überschrieben und das mit einem Inhalt der nicht dazu passt, denn das Blitten wird eigentlich nicht von `Sprite`-Objekten selbst gemacht, sondern von `SpriteGroup`-Objekten.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import random
import time

import pygame

WINDOW_TITLE = "Game"

WHITE = (255, 255, 255)
RED = (255, 0, 0)

FPS = 60

WIDTH = 1600
HEIGHT = 800

PLAYER_Y = HEIGHT - 100
assert PLAYER_Y > 0

ICON_SIZE_TO_PATH = {
    "small": "Images/Icons/Icon_small.png",
    "medium": "Images/Icons/Icon_medium",
    "large": "Images/Icons/Icon_large",
}


def create_spike_image(size):
    image = pygame.Surface((size, size), pygame.SRCALPHA)
    pygame.draw.polygon(image, RED, ((0, 0), (size, 0), (size // 2, size)))
    return image


class Spike:
    STEP = 20
    SIZE = 100
    IMAGE = create_spike_image(SIZE)
    MASK = pygame.mask.from_surface(IMAGE)

    def __init__(self):
        self.image = self.IMAGE.convert_alpha()
        self.mask = self.MASK
        self.rect = self.image.get_rect(
            center=(random.randrange(0, WIDTH), -self.SIZE)
        )

    def down(self, screen):
        """
        Move the spike down.
        """
        self.rect = self.rect.move(0, self.STEP)
        screen.blit(self.image, self.rect)

    def is_visible(self):
        return self.rect.top < HEIGHT


class Cube(pygame.sprite.Sprite):
    STEP = 10
    def __init__(self, x, icon_size):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(
            ICON_SIZE_TO_PATH[icon_size]
        ).convert_alpha()
        self.rect = self.image.get_rect(center=(x, PLAYER_Y))
        self.mask = pygame.mask.from_surface(self.image)

    def left(self):
        if self.rect.right > 0:
            self.rect = self.rect.move(-self.STEP, 0)

    def right(self):
        if self.rect.left < WIDTH:
            self.rect = self.rect.move(self.STEP, 0)

    def blit(self, screen):
        screen.blit(self.image, self.rect)


def main():
    pygame.init()
    pygame.display.set_caption(WINDOW_TITLE)
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    screen.fill(WHITE)
    clock = pygame.time.Clock()

    pygame.mixer.music.load("Sounds/Death-Moon.mp3")
    pygame.mixer.music.play()
    #
    # TODO Use `SpriteGroup` for spikes and player.
    #
    spikes = list()
    last_time = time.monotonic()
    player = Cube(WIDTH // 2, "small")
    player_is_alive = True
    while player_is_alive:
        screen.fill(WHITE)
        for event in pygame.event.get():
            player_is_alive = not (
                event.type == pygame.KEYDOWN
                and event.key == pygame.K_ESCAPE
                or event.type == pygame.QUIT
            )

        pressed_keys = pygame.key.get_pressed()
        if pressed_keys[pygame.K_RIGHT]:
            player.right()
        if pressed_keys[pygame.K_LEFT]:
            player.left()

        player.blit(screen)

        if time.monotonic() - last_time > 0.1:
            spikes.append(Spike())
            last_time = time.monotonic()

        for spike in spikes:
            spike.down(screen)

        pygame.display.flip()

        for spike in spikes:
            if pygame.sprite.collide_mask(spike, player):
                player_is_alive = False
                break

        spikes = [spike for spike in spikes if spike.is_visible()]

        pygame.display.set_caption(str(clock.get_fps()))
        clock.tick(FPS)

    pygame.mixer.music.stop()
    pygame.quit()


if __name__ == "__main__":
    main()
Hier funktioniert das mit der Kollision dann auch richtig. Frag nicht an welcher Änderung es lag. 😀
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Danke für deine Hilfe __blackjack__.
Diese Frage hat weniger mit meinem Spiel zu tun, aber ich stelle sie jetzt einfach hier.
Ich hatte mein Spiel(oben) auf dem Raspberry Pi 4 geschrieben und getestet.
Da waren die FPS immer zwischen 22 und 27.
Als ich jetzt den Raspberry auf 2 GHz übertaktet habe erreicht es plötzlich bis zu 140 FPS.
Was hat das für einen Grund?
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich kann nur raten, aber ich vermute da kommt irgendwie die clock durcheinander.
Antworten