Spielerei mit Pygame

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Hallo zusammen. Ich habe eine Frage an das untere Beispiel.
Wenn ich in Zeile 66 ''creen.fill(BLACK)" lösche bzw. hinzufüge, habe ich zwei unterschiedliche Animationen. Einmal hüpft der Ball richtig und einmal habe ich "Schneerieseln" richtig. Geht es irgendwie, dass ich beides zusammen habe? Also hüpfenden Ball mit Schneerieseln. :-)


Code: Alles auswählen

mport pygame
import random

BLUE    = ( 0, 0, 255)
ORANGE  = ( 255, 140, 0)
RED     = ( 255, 0, 0)
GREEN   = ( 0, 255, 0)
BLACK   = ( 0, 0, 0)
WHITE   = ( 255, 255, 255)
YELLOW  = ( 255, 255, 0)

FENSTERBREITE = 1000
FENSTERHOEHE = 700

KASTENBREITE = 300
KASTENHOEHE = 300
KASTENANFANG_X = 50
KASTENANFANG_Y = 50

def zeichne_kasten(screen):
    pygame.draw.rect(screen, WHITE, (KASTENANFANG_X, KASTENANFANG_Y, KASTENBREITE, KASTENHOEHE),1)


def zeichne_punkt(screen):
    pygame.draw.ellipse(screen, WHITE, [random.randint(50,350), random.randint(50,350), 3, 3])


def ball_bewegen(ballpos_x, ballpos_y, BALL_DURCHMESSER, bewegung_x, bewegung_y, screen, farbe):

    pygame.draw.ellipse(screen, farbe, [ballpos_x, ballpos_y, BALL_DURCHMESSER, BALL_DURCHMESSER])

    ballpos_x += bewegung_x
    ballpos_y += bewegung_y

    if ballpos_y > KASTENANFANG_Y + KASTENHOEHE - BALL_DURCHMESSER or ballpos_y < KASTENANFANG_Y:
        bewegung_y = bewegung_y * -1

    if ballpos_x > KASTENANFANG_X + KASTENBREITE - BALL_DURCHMESSER or ballpos_x < KASTENANFANG_X:
        bewegung_x = bewegung_x * -1

    return (ballpos_x, ballpos_y, bewegung_x, bewegung_y)


def main():
    pygame.init()
    screen = pygame.display.set_mode((FENSTERBREITE, FENSTERHOEHE))
    pygame.display.set_caption("Unser erstes Pygame-Spiel")
    spielaktiv = True
    clock = pygame.time.Clock()

    farbenliste = [BLUE, ORANGE, RED, GREEN, YELLOW]

    ballpos_x_1 = 70
    ballpos_y_1 = 120
    BALL_DURCHMESSER_1 = 20
    bewegung_x_1 = 3
    bewegung_y_1 = 4
    farbe_1 = random.choice(farbenliste)

    while spielaktiv:
        for event in pygame.event.get():
            print(event)
            if event.type == pygame.QUIT:
                spielaktiv = False

        screen.fill(BLACK)

        zeichne_kasten(screen)
        zeichne_punkt(screen)
        ballpos_x_1, ballpos_y_1, bewegung_x_1, bewegung_y_1 = ball_bewegen(ballpos_x_1, ballpos_y_1,
                                            BALL_DURCHMESSER_1, bewegung_x_1, bewegung_y_1, screen, farbe_1)

        pygame.display.flip()
        clock.tick(60)

    pygame.quit()

if __name__ == "__main__":
    main()
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dann musst du statt immer nur einen einzelnen zufaelligen Punkt zu malen dir merken, welche Punkte du schon gemalt hast. Und die immer wieder malen. Und schneerieseln ist das ja auch nicht, sondern eher Insekten auf der Windschutzscheibe. Denn es rieselt ja nichts, dafuer musst du die auch animieren.

Das du keine globalen Variablen fuer deinen Ball-Zustand benutzt, ist ja schon mal sehr schoen. Aber so wie du das machst ist das seeehr umstaendlich und skaliert natuerilch auch nicht. Sowohl fuer deinen Ball, als auch deine Schneeflocken, wird es Zeit fuer Klassen. Dann kannst du zB eine Menge von 100 Schneeflocken haben, die runter rieseln, und dann bei erreichen des unteren Bildschirmrandes sich wieder oben einen neuen zufaelligen Startpunkt aussuchen.
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Hallo __deets__ ,
es hat jetzt geklappt. Der Schnee rieselt jetzt von oben nach unten. :-) Und ich kann jetzt auch 100 Bälle gleichzeitig laufen lassen. Aber du hast recht. Irgendwie scheint es etwas umständlich und unübersichtlich zu sein, z.B. das mit den Startbedingungen festlegen.
Mit den Klassen habe ich bis jetzt keine Erfahrung, muss ich wohl langsam anfangen :-)

Code: Alles auswählen

import pygame
import random

BLUE    = ( 0, 0, 255)
ORANGE  = ( 255, 140, 0)
RED     = ( 255, 0, 0)
GREEN   = ( 0, 255, 0)
BLACK   = ( 0, 0, 0)
WHITE   = ( 255, 255, 255)
YELLOW  = ( 255, 255, 0)

FENSTERBREITE = 640
FENSTERHOEHE = 480

def schnee_rieseln(screen, koordinaten):
    for i in range(1,700):
        if koordinaten[i][1] > FENSTERHOEHE:
            koordinaten[i][1] = 0
            koordinaten[i][0] = random.randint(FENSTERBREITE/2, FENSTERBREITE)
        koordinaten[i][1] += 5
        pygame.draw.ellipse(screen, WHITE, [koordinaten[i][0], koordinaten[i][1], 3, 3])
    return koordinaten


def zeichne_punkte(screen, anzahl_punkte, koordinaten, punkte_zaehler):
    punkte_zaehler += 1
    koordinate_x = random.randint(50,350)
    koordinate_y = random.randint(50,350)
    koordinaten.append((koordinate_x, koordinate_y))
    print("Anzahl der Punkte: ", anzahl_punkte)
    for i in range(1,anzahl_punkte):
        pygame.draw.ellipse(screen, WHITE, [koordinaten[i][0], koordinaten[i][1], 3, 3])

    return  koordinaten, punkte_zaehler


def baelle_bewegen(ballpos_x, ballpos_y, BALL_DURCHMESSER, bewegung_x, bewegung_y, screen, farbe):
    for j in range(1,100):
        pygame.draw.ellipse(screen, farbe[j], [ballpos_x[j], ballpos_y[j], BALL_DURCHMESSER[j], BALL_DURCHMESSER[j]])

        ballpos_x[j] += bewegung_x[j]
        ballpos_y[j] += bewegung_y[j]

        if ballpos_y[j] > FENSTERHOEHE - BALL_DURCHMESSER[j] or ballpos_y[j] < 0:
            bewegung_y[j] = bewegung_y[j] * -1

        if ballpos_x[j] > FENSTERBREITE - BALL_DURCHMESSER[j] or ballpos_x[j] < 0:
            bewegung_x[j] = bewegung_x[j] * -1

    return (ballpos_x, ballpos_y, bewegung_x, bewegung_y)


def main():
    pygame.init()
    screen = pygame.display.set_mode((FENSTERBREITE, FENSTERHOEHE))
    pygame.display.set_caption("Pygame-Spiel")
    spielaktiv = True
    clock = pygame.time.Clock()

    farbenliste = [BLUE, ORANGE, RED, GREEN, YELLOW]

    ballpos_x = [""]
    ballpos_y = [""]
    BALL_DURCHMESSER = [""]
    bewegung_x = [""]
    bewegung_y = [""]
    farbe = [""]

    #Listen befüllen, Startbedingungen für die Baelle
    for i in range(1,100):
        BALL_DURCHMESSER.append(random.randint(10, 30))
        ballpos_x.append(random.randint(1, FENSTERBREITE-BALL_DURCHMESSER[i]))
        ballpos_y.append(random.randint(1, FENSTERHOEHE-BALL_DURCHMESSER[i]))
        #damit ein Ball nicht auf der Stelle liegen bleibt
        while True:
            bewegung_x_zufallszahl = random.randint(-3, 3)
            bewegung_y_zufallszahl = random.randint(-3, 3)
            if bewegung_x_zufallszahl != 0 or bewegung_y_zufallszahl != 0:
                break
        bewegung_x.append(bewegung_x_zufallszahl)
        bewegung_y.append(bewegung_y_zufallszahl)
        farbe.append(random.choice(farbenliste))

    punkte_zaehler = 0
    punkte_koordinaten = [("","")]

    schneeflocke_koordinaten = [["",""]]
    for k in range(1,700):
        schneeflocke_x = random.randint(FENSTERBREITE/2, FENSTERBREITE)
        schneeflocke_y = random.randint(0, FENSTERHOEHE)
        schneeflocke_koordinaten.append([schneeflocke_x, schneeflocke_y])

    while spielaktiv:
        for event in pygame.event.get():
            print(event)
            if event.type == pygame.QUIT:
                spielaktiv = False

        screen.fill(BLACK)

        punkte_koordinaten, punkte_zaehler = zeichne_punkte(screen, punkte_zaehler, punkte_koordinaten, punkte_zaehler)

        ballpos_x, ballpos_y, bewegung_x, bewegung_y = \
               baelle_bewegen(ballpos_x, ballpos_y, BALL_DURCHMESSER, bewegung_x, bewegung_y, screen, farbe)

        schneeflocke_koordinaten = schnee_rieseln(screen, schneeflocke_koordinaten)

        pygame.display.flip()
        clock.tick(30)

    pygame.quit()
    quit()

if __name__ == "__main__":
    main()
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Du benutzt Listen falsch. Listen haben eine Länge, da braucht man keine fixen Zahlen für die for-Schleifen benutzen, oder anzahl_punkte extra übergeben.
Zudem sind Schleifen über den Index unnötig umständlich, weil man direkt über die Elemente der Liste iterieren kann:

Code: Alles auswählen

def schnee_rieseln(screen, koordinaten):
    for koordinate in koordinaten:
        if koordinate[1] > FENSTERHOEHE:
            koordinate[1] = 0
            koordinate[0] = random.randint(FENSTERBREITE/2, FENSTERBREITE)
        koordinate[1] += 5
        pygame.draw.ellipse(screen, WHITE, [koordinate[0], koordinate[1], 3, 3])

def zeichne_punkte(screen, koordinaten):
    koordinate_x = random.randint(50,350)
    koordinate_y = random.randint(50,350)
    koordinaten.append((koordinate_x, koordinate_y))
    print("Anzahl der Punkte: ", anzahl_punkte)
    for koordinate in koordinaten:
        pygame.draw.ellipse(screen, WHITE, [koordinate[0], koordinate[1], 3, 3])
Dass Du erst ab Index 1 losläufst und dazu auch noch in den Listen an Stelle 0 einen leeren String nimmst, ist doch völlig verquer.
Warum hast Du bei den Bällen die Informationen zu einem Ball über sechs Listen verteilt?

Code: Alles auswählen

def baelle_bewegen(screen, baelle):
    for ball in baelle:
        pos_x, pos_y, bewegung_x, bewegung_y, durchmesser, farbe = ball
        pygame.draw.ellipse(screen, farbe, [pos_x, pos_y, durchmesser, durchmesser])
        ball[0] += bewegung_x
        ball[1] += bewegung_y
        if ball[0] > FENSTERBREITE - durchmesser or ball[0] < 0:
            ball[2] *= -1
        if ball[1] > FENSTERHOEHE - durchmesser or ball[1] < 0:
            ball[3] *= -1
Besser wäre es natürlich hier, statt den Indizes auf Instanzattribute einer Ballklasse zuzugreifen.

Die Initialisierung wird dadurch auch viel einfacher:

Code: Alles auswählen

    baelle = []
    for _ in range(100):
        durchmesser = random.randint(10, 30)
        #damit ein Ball nicht auf der Stelle liegen bleibt
        while True:
            bewegung_x_zufallszahl = random.randint(-3, 3)
            bewegung_y_zufallszahl = random.randint(-3, 3)
            if bewegung_x_zufallszahl != 0 or bewegung_y_zufallszahl != 0:
                break
        baelle.append([
            random.randint(1, FENSTERBREITE - durchmesser),
            random.randint(1, FENSTERHOEHE - durchmesser),
            bewegung_x_zufallszahl,
            bewegung_y_zufallszahl
            durchmesser,
            random.choice(farbenliste)
        ])
    punkte_koordinaten = []
    schneeflocke_koordinaten = [
        [random.randint(FENSTERBREITE/2, FENSTERBREITE), random.randint(0, FENSTERHOEHE)]
        for _ in range(700)
    ]
Und da die Listen nur verändert werden, braucht man sie auch nicht zurückzugeben:

Code: Alles auswählen

    while spielaktiv:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                spielaktiv = False

        screen.fill(BLACK)
        zeichne_punkte(screen, punkte_koordinaten)
        baelle_bewegen(screen, baelle)
        schnee_rieseln(screen, schneeflocke_koordinaten)
        pygame.display.flip()
        clock.tick(30)
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Hallo Sirius3, viel Dank für deine ausführlich Hilfestellung. Der Code ist ist jetzt um einiges schlanker und übersichtlicher geworden.

Code: Alles auswählen

Und da die Listen nur verändert werden, braucht man sie auch nicht zurückzugeben: 
Ich hatte gedacht, man muss die Listen übergeben, weil sie nur lokal in den einzelnen Funktionen vorhanden sind und in der main() unbekannt sind.
Hab die Initialisierung auch in eine Funktion ausgelagert. Da musste ich aber die Listen zurückgeben, sonst lief das nicht.

Wenn ich Anzahl neu erzeugter Punkte pro Funktionsdurchlauf auf 100 hochschraube, dann geht die Ansicht nach wenigen Sekunden in "Zeitlupenmodus" über.
Ist ein moderner Rechner etwa nicht in der Lage, "ein paar popelige Punkte und Kreise" vernünftig zu animieren? :-)

Code: Alles auswählen

def zeichne_punkte(screen, koordinaten):
    for _ in range(1,100):
        koordinate_x = random.randint(50,350)
        koordinate_y = random.randint(50,350)
        koordinaten.append((koordinate_x, koordinate_y))
        for koordinate in koordinaten:
            pygame.draw.ellipse(screen, WHITE, [koordinate[0], koordinate[1], 3, 3])
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Doch, das sollte ein moderner Rechner schaffen. Aber da du nicht den gesamten Code zeigst, kann man nur raten. Wenn du mit 30 FPS 100 Schneeflocken erzeugst, dann sind das nach 1 Sekunde 3000, und nach 10 Sekunden 30000, und da wird es dann schon ein bisschen enger. Und ich sehe eben nirgendwo, dass du auch Schneeflocken zerstoerst. Und es reicht auch nicht, wenn das passiert, wenn die am Ende angekommen sind, denn da haengt es von der Zeit ab, die die brauchen, um aus dem Bildschirm zu fallen, wieviele Ueberhang du erzeugst.
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Das ist der aktuelle Code:

Code: Alles auswählen

import pygame
import random

BLUE    = ( 0, 0, 255)
ORANGE  = ( 255, 140, 0)
RED     = ( 255, 0, 0)
GREEN   = ( 0, 255, 0)
BLACK   = ( 0, 0, 0)
WHITE   = ( 255, 255, 255)
YELLOW  = ( 255, 255, 0)

FENSTERBREITE = 640
FENSTERHOEHE = 480

FARBENLISTE = [BLUE, ORANGE, RED, GREEN, YELLOW]

def var_initialisieren():
    baelle = []
    for _ in range(100):
        durchmesser = random.randint(10, 30)
        # damit ein Ball nicht auf der Stelle liegen bleibt
        while True:
            bewegung_x_zufallszahl = random.randint(-3, 3)
            bewegung_y_zufallszahl = random.randint(-3, 3)
            if bewegung_x_zufallszahl != 0 or bewegung_y_zufallszahl != 0:
                break
        baelle.append([
            random.randint(1, FENSTERBREITE - durchmesser),
            random.randint(1, FENSTERHOEHE - durchmesser),
            bewegung_x_zufallszahl,
            bewegung_y_zufallszahl,
            durchmesser,
            random.choice(FARBENLISTE)
        ])
    punkte_koordinaten = []
    schneeflocke_koordinaten = [
        [random.randint(FENSTERBREITE / 2, FENSTERBREITE), random.randint(0, FENSTERHOEHE)]
        for _ in range(700)
    ]
    return baelle, punkte_koordinaten, schneeflocke_koordinaten


def schnee_rieseln(screen, koordinaten):
    for koordinate in koordinaten:
        if koordinate[1] > FENSTERHOEHE:
            koordinate[1] = 0
            koordinate[0] = random.randint(FENSTERBREITE/2, FENSTERBREITE)
        koordinate[1] += 5
        pygame.draw.ellipse(screen, WHITE, [koordinate[0], koordinate[1], 3, 3])


def zeichne_punkte(screen, koordinaten):
    for _ in range(1,100):
        koordinate_x = random.randint(50,350)
        koordinate_y = random.randint(50,350)
        koordinaten.append((koordinate_x, koordinate_y))
        for koordinate in koordinaten:
            pygame.draw.ellipse(screen, WHITE, [koordinate[0], koordinate[1], 3, 3])


def baelle_bewegen(screen, baelle):
    for ball in baelle:
        pos_x, pos_y, bewegung_x, bewegung_y, durchmesser, farbe = ball
        pygame.draw.ellipse(screen, farbe, [pos_x, pos_y, durchmesser, durchmesser])
        ball[0] += bewegung_x
        ball[1] += bewegung_y
        if ball[0] > FENSTERBREITE - durchmesser or ball[0] < 0:
            ball[2] *= -1
        if ball[1] > FENSTERHOEHE - durchmesser or ball[1] < 0:
            ball[3] *= -1


def main():
    pygame.init()
    screen = pygame.display.set_mode((FENSTERBREITE, FENSTERHOEHE))
    pygame.display.set_caption("Pygame-Spiel")
    spielaktiv = True
    clock = pygame.time.Clock()

    baelle, punkte_koordinaten, schneeflocke_koordinaten = var_initialisieren()

    while spielaktiv:
        for event in pygame.event.get():
            print(event)
            if event.type == pygame.QUIT:
                spielaktiv = False

        screen.fill(BLACK)
        zeichne_punkte(screen,punkte_koordinaten)
        baelle_bewegen(screen, baelle)
        schnee_rieseln(screen, schneeflocke_koordinaten)
        pygame.display.flip()
        clock.tick(10)

    pygame.quit()
    quit()

if __name__ == "__main__":
    main()
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist ja noch schlimmer: Du fügst nicht nur in jedem Frame 100 Punkte hinzu, sondern malst für jeden Frame 100mal alle Punkte.
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Mist, jetzt sehe ichs auch :shock:

Wenn ich die Punkte nicht 100 Mal pro Funktionsaufruf zeichne, sieht es schon deutlich entspannter aus:

Code: Alles auswählen

def zeichne_punkte(screen, koordinaten):
    for _ in range(1,100):
        koordinate_x = random.randint(50,350)
        koordinate_y = random.randint(50,350)
        koordinaten.append((koordinate_x, koordinate_y))
    for koordinate in koordinaten:
            pygame.draw.ellipse(screen, WHITE, [koordinate[0], koordinate[1], 3, 3])
Antworten