Objekt bei jedem Tastendruck fortbewegen in PyGame

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
cypher28
User
Beiträge: 24
Registriert: Mittwoch 8. Dezember 2021, 00:54

Hallo zusammen,

ich bin gerade dabei ein Spielfeld zu erstellen und ein Objekt einfach zu bewegen i. diesem Spielfeld. Alles soweit gut.
Nur möchte ich daß das Objekt sich nicht durchlaufend bewegt bei gedrückter Taste, sondern erst dann wieder einen bestimmten Schritt nach vorne macht, wen. die Taste nochmals gedrückt wird.
Die Spielfigur soll sich quasi jedesmal um 50 px in eine Richtung bewegen, aber nicht fortlaufend bei gedrückter Taste.

Hier mal der Code:

Code: Alles auswählen

import pygame
import sys

pygame.init()
#hintergrundbild laden
background = pygame.image.load("graphics/background.jpg")

#Grösse des Spielfeldes definieren
screen = pygame.display.set_mode([800,600])
clock = pygame.time.Clock()

#SPielname in der Statusleiste
pygame.display.set_caption("Frogger")

def draw():
    screen.blit(background, (0,0))
    pygame.draw.rect(screen, (0,0,0), (x,y,bright, wight))
    pygame.display.update()

#Objekteigenschaften
x = 300
y = 550
speed = 20
bright = 40
wight = 40


leftWall = pygame.draw.rect(screen, (0,0,0), (0,0, 2, 600), 0)
rightWall = pygame.draw.rect(screen, (0,0,0), (799,0, 2, 600), 0)

go = True
while go:
    for event in pygame.event.get():
        if event.type == pygame.QUIT: 
            sys.exit()
    playerRect = pygame.Rect(x,y,40,40)
    pressed = pygame.key.get_pressed()

    if pressed[pygame.K_UP]:
        y -= speed

    if pressed[pygame.K_RIGHT] and not playerRect.colliderect(rightWall):
        x += speed
    if pressed[pygame.K_DOWN]:
        y += speed
    if pressed[pygame.K_LEFT] and not playerRect.colliderect(leftWall):
        x -= speed
       
    draw()
    clock.tick(60)
In dieser Situation bewegt sich das Objekt, solange die jeweiligen Tasten gedruckt sind.

Ich habe es mit einer bool-Variable versucht, weis aber nicht ob der Ansatz richtig ist.

Code: Alles auswählen

....
wasPressed = True
....
if pressed[pygame.K_UP] and wasPressed:
        y -= speed
        wasPressed = False
Es klappt dann beim erstenmal, da aber im ersten durchlauf der bool-Wert auf False gesetzt wird klappt es beim zweiten durchlauf nicht. Was ja auch sinn macht.

Wäre super wenn jemand einen Tip hätte.

Vielen dank im voraus.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@cypher28,

dazu würde ich mir mal die Dokumentation von pygame anschauen. Es gibt über die Funktion "pygame.key.set_repeat()" die Möglichkeit das Verhalten zu bestimmen.

Außerdem gibt "pygame.key.get_pressed()" den Zustand der Taste an und ist damit True solange sie gedrückt ist. Das Verhalten ist also zu erwarten.
Ansonsten sollte der KeyUp Event besser funktionieren. Der wird ausgelöst, wenn eine Taste losgelassen wurde, nicht wenn sie gedrückt wurde.

Mit irgendwelchen boolschen Variablen, wird es nur zu kompliziert. Das braucht man nur wenn man Springen oder ähnliches realisieren will.

sys.exit() würde ich auch nicht verwenden. Dafür hast du doch die Variable "go". Die muss zum Beenden nur auf "False" gesetzt werden.
Benutzeravatar
__blackjack__
User
Beiträge: 14005
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@cypher28: Weitere Anmerkungen: Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben. `draw()` braucht also `screen`, `background`, `x`, `y`, `bright`, und `wight`. Was ganz schön viel ist. Das sollte man überlegen was man davon vielleicht sinnvoll zu Objekten zusammenfassen kann. Die letzten vier beschreiben ja beispielsweise ein Rechteck, da würde sich ein `Rect`-Objekt anbieten.

Was sollen `bright` und `wight` überhaupt bedeuten? Das ist verwirrend. Haben die in irgendeiner (natürlichen) Sprache eine Bedeutung die zu der Bedeutung der Werte im Programm passt?

Es fehlt ein `pygame.quit()` als Gegenstück zum `pygame.init()`.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Es gibt Zahlen die magisch fest im Code stehen, die von anderen Werten abhängig sind. Das heisst man muss wenn man etwas davon ändert, auch die davon abhängigen Werte im Programm suchen und anpassen, statt das die dort gleich aus dem berechnet werden, von dem sie abhängig sind.

Und die Breite/Höhe von Spieler wird unabhängig an zwei Stellen definiert. Und sogar ein `Rect` erstellt, das man `draw()` übergeben könnte, statt der Einzelwerte.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import pygame

BLACK = (0, 0, 0)


def draw(screen, background, rect):
    screen.blit(background, (0, 0))
    pygame.draw.rect(screen, BLACK, rect)
    pygame.display.update()


def main():
    pygame.init()
    try:
        background = pygame.image.load("graphics/background.jpg")

        size = width, height = (800, 600)
        screen = pygame.display.set_mode(size)
        pygame.display.set_caption("Frogger")

        player_rect = pygame.Rect(300, 550, 40, 40)
        speed = 20

        left_wall = pygame.draw.rect(screen, BLACK, (0, 0, 2, height), 0)
        right_wall = pygame.draw.rect(screen, BLACK, (width-1, 0, 2, height), 0)

        clock = pygame.time.Clock()
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return

            pressed = pygame.key.get_pressed()

            if pressed[pygame.K_UP]:
                player_rect.move_ip(0, -speed)

            if pressed[pygame.K_RIGHT] and not player_rect.colliderect(right_wall):
                player_rect.move_ip(speed, 0)

            if pressed[pygame.K_DOWN]:
                player_rect.move_ip(0, speed)

            if pressed[pygame.K_LEFT] and not player_rect.colliderect(left_wall):
                player_rect.move_ip(-speed, 0)

            draw(screen, background, player_rect)
            clock.tick(60)
    finally:
        pygame.quit()


if __name__ == "__main__":
    main()
“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
cypher28
User
Beiträge: 24
Registriert: Mittwoch 8. Dezember 2021, 00:54

OK,

vielen dank für die Antworten.
Die Kommentare hatte ich jetzt nur als Orientierung für mich geschrieben .
Werde mich mal da einarbeiten. Ist das erste mal das ich mich an PyGame, geschweige denn an Python versuche.
cypher28
User
Beiträge: 24
Registriert: Mittwoch 8. Dezember 2021, 00:54

Das mir der Fortbewegung klappt soweit.

Kann ich einem rect auch ein image zuweisen? Ich meine wenn ich z.B. kein Rechteck, sondern ein Bild laden möchte als Player?

Ich denke, dann müsste die "draw" Methode angepasst werden. Nur klappt es nicht leider.

Code: Alles auswählen

def draw(screen, background, player):
    screen.blit(background, (0, 0))
   pygame.draw.rect(screen,BLACK, rect)
    pygame.display.update()
    
Kann ich z.B. anstatt einer Farbe, ein Bild zuweisen? Mir fehlt irgendwie eine Methode Pygame.draw(zeichne Bild xyz)
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Zuweisen kannst du da nichts, du kannst einfach ein Bild malen, das muss dann natuerlich so gross sein wie das rect.

Nachtrag: blit ist doch was du suchst, damit wird ein Bild (Surface) in ein anderes kopiert.
cypher28
User
Beiträge: 24
Registriert: Mittwoch 8. Dezember 2021, 00:54

Also das mit dem Zeichnen habe ich hinbekommen.

Code: Alles auswählen

def draw(screen, background, spieler):
    screen.blit(background, (0, 0))
    screen.blit(spieler, (x,y))
    pygame.display.update()

x = 600
y = 800
Nur kann diese

Code: Alles auswählen

if event.key == pygame.K_UP:
                        player_image.move_ip(0, -speed)
move_ip Methode glaube ich nur an einem rect Object oder desgleichen angewendet werden.

Wie müsste dann die Bewegung meines Objektes aussehen?

ich hätte gedacht das ich mit:

Code: Alles auswählen

x += speed
das Objekt in Richtung x bewegen kann. Nur wird die variable x nicht erkannt, da sie ja ausserhalb der main Methode sich befindet. Es müsste irgendwie ja die Spieler Koordinate in der main Methode vorhanden sein, damit man diese beeinflussen kann.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich kenne deinen gesamten Code nicht, aber angeblich bekommt draw doch player_rect, und das hat doch die Koordinate. Warum haelst du die Daten doppelt?
cypher28
User
Beiträge: 24
Registriert: Mittwoch 8. Dezember 2021, 00:54

Ich glaube ich verstehe das zusammen spiel zwischen dem Konstrukt der Methode "draw()"

Code: Alles auswählen

def draw(screen, background, spieler):
    screen.blit(background, (0, 0))
    screen.blit(spieler, (x,y))
    pygame.display.update()
und dem Aufruf der Methode in der "main()" Methode nicht so ganz.

Code: Alles auswählen

draw(screen, background, player_image)



Hier mal der komplette code.

Code: Alles auswählen

#!/usr/bin/env python3
import pygame

BLACK = (0, 0, 0)


def draw(screen, background, spieler):
    screen.blit(background, (0, 0))
    screen.blit(spieler, (x,y))
    pygame.display.update()

x = 600
y = 800

def main():
    pygame.init()
    try:
        background = pygame.image.load("graphics/background.png")

        size = width, height = (1000, 1000)
        screen = pygame.display.set_mode(size)
        pygame.display.set_caption("Game")

        player_image = pygame.image.load("graphics/player.png")
        speed = 20

        left_wall = pygame.draw.rect(screen, BLACK, (0, 0, 2, height), 0)
        right_wall = pygame.draw.rect(screen, BLACK, (width-1, 0, 2, height), 0)

        clock = pygame.time.Clock()
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return

                if event.type == pygame.KEYDOWN:
     
                    if event.key == pygame.K_UP:
                        print("passt")
                     #   player_image += speed
                     
            draw(screen, background, player_image)
            clock.tick(60)
    finally:
        pygame.quit()


if __name__ == "__main__":
    main()
Mit freundlicher unterstützung von __blackjack__ natürlich. :)
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da ist doch der urspruengliche Code, in dem das player_rect bewegt wurde, weg. Und das wurde doch mal an draw uebergeben. Und enthielt die Koordinate. Warum ist das denn alles verschwunden?
cypher28
User
Beiträge: 24
Registriert: Mittwoch 8. Dezember 2021, 00:54

Ok, dann frage ich mal andersrum.

Wie kann ich denn das Rechteck durch ein eingelesenes Bild ersetzen, so dass ich kein schwarzes Rechteck im Game habe sondern mein eingelesenes Bild?

Das meinte ich mit "Wie kann ich einem Rechteck ein Bild zuweisen".Das einlesen des Bildes kann ich ja wie gewohnt, wie mit "background" tätigen.

Hier mal der überarbeitete code mit der gewünschten Steuerung:

Code: Alles auswählen

import pygame

BLACK = (0, 0, 0)


def draw(screen, background, rect):
    screen.blit(background, (0, 0))
    pygame.draw.rect(screen, BLACK, rect)
    pygame.display.update()


def main():
    pygame.init()
    try:
        background = pygame.image.load("graphics/background.jpg")
        player = pygame.image.load("graphics/player.jpg")

        size = width, height = (800, 600)
        screen = pygame.display.set_mode(size)
        pygame.display.set_caption("Frogger")

        player_rect = pygame.Rect(300, 550, 40, 40)
        speed = 20

        left_wall = pygame.draw.rect(screen, BLACK, (0, 0, 2, height), 0)
        right_wall = pygame.draw.rect(screen, BLACK, (width-1, 0, 2, height), 0)

        clock = pygame.time.Clock()
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return

                if event.type == pygame.KEYDOWN:
     
                    if event.key == pygame.K_UP:
                        player_rect.move_ip(0, -speed)

                    if event.key == pygame.K_DOWN:
                        player_rect.move_ip(0, speed)

                    if event.key == pygame.K_LEFT:
                        player_rect.move_ip(-speed, 0)

                    if event.key == pygame.K_RIGHT:
                        player_rect.move_ip(speed, 0)

            draw(screen, background, player_rect)
            clock.tick(60)
    finally:
        pygame.quit()


if __name__ == "__main__":
    main()
Ich muss doch das Rechteck

Code: Alles auswählen

pygame.draw.rect(screen, BLACK, rect)
irgendwie durch mein eingelesenen "Player" ersetzen, so daß ich den Player sehe und kein Rechteck.

Oder dass Rechteck "durchsichtig" machen, aber irgendwie das Bild anhängen / zuweisen. Damit dann auch Kollisionen erkannt werden können im Spiel!?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Indem du auch dein bild an draw uebergibst, und das dann da mals, an die Stelle, die das player_rect bezeichnet.

Und wenn das unklar ist, mal das Grundlagentutorial auf python.org durcharbeiten, denn Funktionen und wie man an sie etwas uebergibt muessen sitzen.
cypher28
User
Beiträge: 24
Registriert: Mittwoch 8. Dezember 2021, 00:54

Du meinst:

Code: Alles auswählen


def draw(screen, background, rect, player):
    screen.blit(background, (0, 0))
    screen.blit(player, (500, 500))
    pygame.draw.rect(screen, BLACK, rect)
    pygame.display.update()
und in der main Methode mit zeichnen:

Code: Alles auswählen

draw(screen, background, player_rect, player)
Aber, dann funktioniert die Steuerung des players nicht:

Code: Alles auswählen

if event.key == pygame.K_UP:
                        player.x += speed
Weil anscheinend auf die x Koordinate des Players nicht zugegriffen werden kann, welches ja ausserhalb liegt. Wobei ich mich dann frage wozu ich player_rect dann noch brauche.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wieso passt du denn dann die Steuerung an? Warum bleibt das nicht player_rect? player_rect ist doch der Ort, an dem du Position und Ausmass deines Players mitfuehrst, um ihn dann zB kollidieren zu lassen.
cypher28
User
Beiträge: 24
Registriert: Mittwoch 8. Dezember 2021, 00:54

__deets__ hat geschrieben: Donnerstag 16. Dezember 2021, 17:53 Wieso passt du denn dann die Steuerung an?
Die Steuerung musste ich anpassen weil ich wollte, daß das Objekt beim drücken der Taste sich nur einmal bewegt und nicht durchgehend. Was ja auch funktioniert in bezug auf das Rechteck.
__deets__ hat geschrieben: Donnerstag 16. Dezember 2021, 17:53 Warum bleibt das nicht player_rect? player_rect ist doch der Ort, an dem du Position und Ausmass deines Players mitfuehrst, um ihn dann zB kollidieren zu lassen.
Weil player_rect das Rechteck ist und nicht der player.

Code: Alles auswählen

draw(screen, background, player_rect, player)
somit habe ich ja beides auf dem Bildschirm. Mein Rechteck und meinen Player.

Code: Alles auswählen

if event.key == pygame.K_UP:
                        player_rect.move_ip(0, -speed)
Und damit steuere ich mein Rechteck und nicht meinen Player.

Es sei denn es gibt eine Möglichkeit, meinen "Player" abhängig vom Rechteck zu machen, so daß es sich immer mitbewegt.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

player ist ein Bild. Das hat keine Koordinaten. player_rect sind deine Koordinaten. Du brauchst eben beides. Und wenn du das rect nicht malen willst, mal es nicht. Aber es ist eben immer noch die Koordinate.
cypher28
User
Beiträge: 24
Registriert: Mittwoch 8. Dezember 2021, 00:54

Der player_rect ist doch einfach nur das Rechteck. Das steht doch in keiner Beziehung mit meinem eingelesenen Bild?

Ich kann doch hier dem Rechteck kein Bild zuweisen:

Code: Alles auswählen

pygame.draw.rect(screen, BLACK, rect)
Zumindest gibt es in der Pygame doc kein Modul um dort ein Bild anzuhängen.
https://www.pygame.org/docs/ref/draw.ht ... .draw.rect

Entweder bin ich Blind oder auf dem falschen Pfad. :)
Benutzeravatar
__blackjack__
User
Beiträge: 14005
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@cypher28: `blit()` braucht das Bild und die Koordinaten. Du übergibst `draw()` das Bild und die Koordinaten und rufst `blit()` dann mit beidem auf. Das zweite Argument dort muss kein Tupel sein, das kann auch ein `Rect`-Objekt sein. Und Du hast da ja ein passendes für.

`pygame.draw.rect()` ist falsch, denn Du willst ja gar kein Rechteck mehr malen sondern das Bild blitten.
“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
cypher28
User
Beiträge: 24
Registriert: Mittwoch 8. Dezember 2021, 00:54

Du meinst die draw() methode folgt:

Code: Alles auswählen

def draw(screen, background, player, position):
    screen.blit(background, (0, 0))
    screen.blit(player, position)
    pygame.display.update()
Soweit so gut. Objekt wird an der gewünschten Position gezeichnet. Nur wie kann ich das Objekt bewegen.

Hier mal der aktuelle code:

Code: Alles auswählen

import pygame

BLACK = (0, 0, 0)

def draw(screen, background, player, position):
    screen.blit(background, (0, 0))
    screen.blit(player, position)
    pygame.display.update()

def main():
    pygame.init()
    try:
        background = pygame.image.load("graphics/background.jpg")
        player = pygame.image.load("graphics/player.png")

        position_player = (400,500)
        size = width, height = (800, 600)
        screen = pygame.display.set_mode(size)
        pygame.display.set_caption("Frogger")

        player_rect = pygame.Rect(300, 550, 40, 40)
        speed = 20

        left_wall = pygame.draw.rect(screen, BLACK, (0, 0, 2, height), 0)
        right_wall = pygame.draw.rect(screen, BLACK, (width-1, 0, 2, height), 0)

        clock = pygame.time.Clock()
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return

                if event.type == pygame.KEYDOWN:
     
                    if event.key == pygame.K_UP:
                        #player.move_ip(0, -speed)
                        position_player = speed

            draw(screen, background, player, position = position_player)
            clock.tick(60)
    finally:
        pygame.quit()

if __name__ == "__main__":
    main()
Nur wie kann ich die x oder die y Koordinate meines Objekts beeinflussen um mein Objekt zu bewegen?
Die methode "move_ip()" ist nicht anwendbar.

Auch folgendes führt zu einem Fehler auf der Konsole:

Code: Alles auswählen

if event.type == pygame.KEYDOWN:
     
                    if event.key == pygame.K_UP:
                        #player.move_ip(0, -speed)
                        position_player -= speed
Wobei ich hier ja den ganzen Tutel anspreche und nicht gezielt x oder y.

Fehler:

Code: Alles auswählen

TypeError: unsupported operand type(s) for -=: 'tuple' and 'int'
Es sei denn, meine draw() methode ist komplett falsch.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich geb auf. Wir reden offensichtlich komplett aneinander vorbei. Ich kann nur zum x-ten mal sagen, dass du nichts an der positions-Berechnung verändern musst, aber es greift offensichtlich nicht. Vielleicht hat wer anders eine Idee.
Antworten