Immer nur EIN Tastatur-Event erkannt.

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
Sylvia
User
Beiträge: 5
Registriert: Samstag 28. September 2019, 08:35

Samstag 28. September 2019, 08:43

Hallo zusammen,

ich mache gerade meine ersten Gehversuche mit Python und Pygame. Habe versucht Snake zu programmieren und habe das Problem, dass die folgende Schleife mir immer nur EIN einziges Tastatur-Event erkennt.

Code: Alles auswählen

for event in pygame.event.get():
        if event.type == pygame.QUIT:
            ende = True
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                snakeDirection=(+1, 0)
            if event.key == pygame.K_LEFT:
                snakeDirection=(-1, 0)
            if event.key == pygame.K_UP:
                snakeDirection=(0, -1)
            if event.key == pygame.K_DOWN:
                snakeDirection=(0, 1)
Da ja leider KEYUP auch ein Tastaturevent ist, kann ich meine Schlange nur alle zwei Schritte bewegen. Mein Workaround ist nun, dass ich die Abfrage der Events zwei mal hintereinander, getrennt durch einen kurzen Delay ausführe.

Code: Alles auswählen

handleEvent()
pygame.time.wait(100)
handleEvent()
Das funktioniert, aber guter Stil ist ja irgendwie anders. Deshalb würde ich gerne näher verstehen, was hier passiert und ob es hierfür eine schönere Lösung gibt.

Danke.
__deets__
User
Beiträge: 6386
Registriert: Mittwoch 14. Oktober 2015, 14:29

Samstag 28. September 2019, 11:59

Das sieht doch eigentlich gut aus. Drumrum sollte doch eine while-Schleife laufen, die dann die FPS bestimmt. Und in jedem Zeitschritt addierst du deine snakeDirection auf eine Position auf, und da entsteht der neue Kopf.
Benutzeravatar
__blackjack__
User
Beiträge: 4192
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Samstag 28. September 2019, 12:13

@Sylvia: Hinweis am Rande: Namenskonvention bei Python ist kleinbuchstaben_mit_unterstrichen für alles ausser Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Also `snake_direction` statt `snakeDirection` und `handle_event()` statt `handleEvent()`.
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
Sylvia
User
Beiträge: 5
Registriert: Samstag 28. September 2019, 08:35

Samstag 28. September 2019, 13:02

@_deets_: Das beantwortet nicht meine Frage. Ich weiß, dass der Code funktioniert. Ich würde aber gern verstehen, warum ich immer nur EIN Key Event bekomme, also ein zweimaliger Aufruf von handle_event() nötig ist. Und ich würde gern wissen, ob es eine elegante Lösung gibt.
__deets__
User
Beiträge: 6386
Registriert: Mittwoch 14. Oktober 2015, 14:29

Samstag 28. September 2019, 13:08

Dann habe ich die Frage (immer noch nicht) verstanden. Ich sehe da erstmal keinen Grund warum da nur ein event kommen sollte. Die for-Schleife sollte alle konsumieren. Dann müsstest du vielleicht mal den ganzen Code zeigen, denn so ist mir nicht klar was da nicht so passiert wie gewünscht.
__deets__
User
Beiträge: 6386
Registriert: Mittwoch 14. Oktober 2015, 14:29

Samstag 28. September 2019, 13:17

Das kleine Spiel hier tut fuer mich das, was ich von snake erwarten wuerde:

Code: Alles auswählen

import sys, pygame

def handle_event():
    running = True
    direction = None
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                direction = (+1, 0)
            if event.key == pygame.K_LEFT:
                direction = (-1, 0)
            if event.key == pygame.K_UP:
                direction = (0, -1)
            if event.key == pygame.K_DOWN:
                direction = (0, 1)
    return running, direction


def main():
    pygame.init()

    size = width, height = 320, 240

    screen = pygame.display.set_mode(size)

    screen.fill((0, 0, 0))

    running = True
    last_direction = (0, -1)
    pos = (width // 2, height // 2)

    while running:
        running, direction = handle_event()
        if direction is not None:
            last_direction = direction
        pos = (pos[0] + last_direction[0], pos[1] + last_direction[1])

        screen.set_at(pos, (255, 255, 255))
        pygame.display.flip()

if __name__ == '__main__':
    main()
Ohne Kollisionsabfrage, und auch mit nur einer immer laenger werdenden Schlange statt Futter & beschraenkter Groesse. Aber das ist ja auch nicht das Problem.
Sylvia
User
Beiträge: 5
Registriert: Samstag 28. September 2019, 08:35

Samstag 28. September 2019, 18:07

@_deets_: Dieser Code läuft bei mir nicht. Es kommt keine Fehlermeldung, aber ich sehe nur eine weiße Linie, die nicht auf die Pfeiltasten reagiert. Das Fenster lässt sich nicht regulär schließen.

Ich sehe ebenfalls keinen Grund, warum nur ein Event kommen sollte. Genau deshalb dieser Post. Aber es ist tatsächlich so. Dies habe ich herausgefunden, als ich mir die Events habe ausgeben lassen.

Hier meine für Debugging-Zwecke veränderte handleEvent()

Code: Alles auswählen

def handleEvent():
     global ende, snakeDirection, n
    for event in pygame.event.get():
        print(event)
        if event.type == pygame.QUIT:
            ende = True
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                snakeDirection=(+1, 0)
                print("                             >")
            if event.key == pygame.K_LEFT:
                snakeDirection=(-1, 0)
                print("                             <")
            if event.key == pygame.K_UP:
                snakeDirection=(0, -1)
                print("                             ^")
            if event.key == pygame.K_DOWN:
                snakeDirection=(0, 1)
                print("                             v")
    n +=1
    print(n)
Und die zugehörige Ausgabe, wenn sich SCHNELL hintereinander Tasten betätige:

Code: Alles auswählen

pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
Level loaded ... (50  x 30)
<Event(1-ActiveEvent {'gain': 0, 'state': 1})>
1
2
(0, -1)
3
<Event(2-KeyDown {'unicode': '', 'key': 273, 'mod': 4096, 'scancode': 111, 'window': None})>
                             ^
4
(0, -1)
<Event(3-KeyUp {'key': 273, 'mod': 4096, 'scancode': 111, 'window': None})>
5
<Event(2-KeyDown {'unicode': '', 'key': 275, 'mod': 4096, 'scancode': 114, 'window': None})>
                             >
6
(1, 0)
<Event(3-KeyUp {'key': 275, 'mod': 4096, 'scancode': 114, 'window': None})>
7
<Event(2-KeyDown {'unicode': '', 'key': 273, 'mod': 4096, 'scancode': 111, 'window': None})>
                             ^
8
(0, -1)
<Event(3-KeyUp {'key': 273, 'mod': 4096, 'scancode': 111, 'window': None})>
9
<Event(2-KeyDown {'unicode': '', 'key': 275, 'mod': 4096, 'scancode': 114, 'window': None})>
                             >
10
(1, 0)
<Event(3-KeyUp {'key': 275, 'mod': 4096, 'scancode': 114, 'window': None})>
11
<Event(2-KeyDown {'unicode': '', 'key': 276, 'mod': 4096, 'scancode': 113, 'window': None})>
                             <
12
(-1, 0)
<Event(3-KeyUp {'key': 276, 'mod': 4096, 'scancode': 113, 'window': None})>
13
<Event(2-KeyDown {'unicode': '', 'key': 273, 'mod': 4096, 'scancode': 111, 'window': None})>
                             ^
14
(0, -1)
<Event(3-KeyUp {'key': 273, 'mod': 4096, 'scancode': 111, 'window': None})>
15
<Event(2-KeyDown {'unicode': '', 'key': 275, 'mod': 4096, 'scancode': 114, 'window': None})>
                             >
16
(1, 0)
<Event(3-KeyUp {'key': 275, 'mod': 4096, 'scancode': 114, 'window': None})>
17
<Event(2-KeyDown {'unicode': '', 'key': 276, 'mod': 4096, 'scancode': 113, 'window': None})>
                             <
18
(-1, 0)
<Event(3-KeyUp {'key': 276, 'mod': 4096, 'scancode': 113, 'window': None})>
19
<Event(2-KeyDown {'unicode': '', 'key': 273, 'mod': 4096, 'scancode': 111, 'window': None})>
                             ^
20
(0, -1)
<Event(3-KeyUp {'key': 273, 'mod': 4096, 'scancode': 111, 'window': None})>
21
<Event(2-KeyDown {'unicode': '', 'key': 275, 'mod': 4096, 'scancode': 114, 'window': None})>
                             >
22
(1, 0)
<Event(3-KeyUp {'key': 275, 'mod': 4096, 'scancode': 114, 'window': None})>
23
<Event(2-KeyDown {'unicode': '', 'key': 276, 'mod': 4096, 'scancode': 113, 'window': None})>
                             <
24
(-1, 0)
<Event(3-KeyUp {'key': 276, 'mod': 4096, 'scancode': 113, 'window': None})>
25
<Event(2-KeyDown {'unicode': '', 'key': 273, 'mod': 4096, 'scancode': 111, 'window': None})>
                             ^
26
(0, -1)
<Event(3-KeyUp {'key': 273, 'mod': 4096, 'scancode': 111, 'window': None})>
27
<Event(2-KeyDown {'unicode': '', 'key': 275, 'mod': 4096, 'scancode': 114, 'window': None})>
                             >
28
(1, 0)
<Event(3-KeyUp {'key': 275, 'mod': 4096, 'scancode': 114, 'window': None})>
<Event(12-Quit {})>
29
<Event(2-KeyDown {'unicode': '', 'key': 273, 'mod': 4096, 'scancode': 111, 'window': None})>
                             ^
30
(0, -1)
Wie man sieht, wird wirklich immer nur pro Durchlauf von handleEvent() (vgl. Nummerierung, Ausgabe von n) ein einziges Key Event erkannt, obwohl der Logik nach die Schleife alle Events abholen sollte. Es sei denn, da gäbe es einen Puffer, der die ganzen Events nur nacheinander ausgibt, jeweils getrennt durch eine Wartezeit ... Darum ging es mir in diesem Post. Siehe Überschrift.
Sirius3
User
Beiträge: 10548
Registriert: Sonntag 21. Oktober 2012, 17:20

Samstag 28. September 2019, 18:15

Wie sieht denn der Rest des Programm aus?
Benutzeravatar
__blackjack__
User
Beiträge: 4192
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Samstag 28. September 2019, 18:41

@Sylvia: Das Programm von __deets__ läuft viel zu schnell deswegen sieht man nur die Linie die scheinbar sofort gezeichnet wird und die nach oben endlos aus dem Bild läuft. Wenn man nach rechts oder links und sofort nach unten drückt, dann rast der Punkt entweder rechts oder links noch mal durch den sichtbaren Bereich. Und beenden kann man das durch das ”Schliessen-Kreuz” in der Fensterleiste. Und das sollte auch funktionieren.

Hier eine Variante die etwas gebremster läuft:

Code: Alles auswählen

#!/usr/bin/env python3
import pygame


def handle_event(direction):
    should_stop = False
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            should_stop = True
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                direction = (+1, 0)
            if event.key == pygame.K_LEFT:
                direction = (-1, 0)
            if event.key == pygame.K_UP:
                direction = (0, -1)
            if event.key == pygame.K_DOWN:
                direction = (0, 1)
    return should_stop, direction


def main():
    pygame.init()

    size = 320, 240
    screen = pygame.display.set_mode(size)

    screen.fill((0, 0, 0))

    direction = (0, -1)
    position = screen.get_rect().center

    while True:
        should_stop, direction = handle_event(direction)
        if should_stop:
            break
        position = (position[0] + direction[0], position[1] + direction[1])

        screen.set_at(position, (255, 255, 255))
        pygame.display.flip()
        pygame.time.wait(100)


if __name__ == "__main__":
    main()
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
__deets__
User
Beiträge: 6386
Registriert: Mittwoch 14. Oktober 2015, 14:29

Samstag 28. September 2019, 19:35

Ich hab’s nachträglich auch mit ner fps Clock ausgestattet. War aber bei mir ok unter macOS.

@Svenja: ich bin mir nicht sicher warum du erwartest mehrere Events zu bekommen. Das man genau zwei Tasten innerhalb einer Millisekunde drückt ist eher unwahrscheinlich. Da müsstest du schon lange warten zwischendurch, dann sammeln die sich.
Sylvia
User
Beiträge: 5
Registriert: Samstag 28. September 2019, 08:35

Sonntag 29. September 2019, 08:56

Den gesamten Code kann ich hier aus Urheberrechtsgründen nicht posten, weil Teile davon aus einem Buch stammen.

Vielleicht hat mein Problem ja etwas mit dem Betriebssystem zu tun? Ich verwende Ubuntu.

Ich erwarte deshalb mehrere Ereignisse, weil ich erstens zwischen den Aufrufen von handle_event() einen delay von 300 ms drin habe und auch probiert habe, die ganze Hand auf der Tastatur zu platzieren. Da sollte ich problemlos innerhalb von 300ms sehr viele Key Events haben. Tatsächlich ist das aber nicht so, wie man an meinen Debugging-Versuchen sieht.

Und wird man, wenn man einen weiblichen Namen benutzt, in solch einem Forum tatsächlich für dämlich gehalten? Dass man ein Fenster beim Kreuzchen schließt, ist mir gerade noch so bekannt. Dass ich es nicht schließen kann, habe ich erwähnt, weil es ein Zeichen dafür ist, dass der von _deets_ gepostete Code nicht korrekt ist. Korrekte Programme lassen sich mit dem Schließen-Symbol beenden. Programme, die z.B. irgendwo in einer Endlosschleife hängen und das Event QUIT nicht abfragen, kann man nicht schließen.
__deets__
User
Beiträge: 6386
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sonntag 29. September 2019, 09:48

Man kann sich die gelieferten Programme anschauen und erkennen, dass sie sehr wohl eine QUIT-Behandlung machen. Und zusammen mit der Aussage von jemand anderem, als dem Autor, bei dem das funktioniert, sich die Frage stellen, ob das ein Hinweis auf die Ursache fuer die eigenen Probleme bei der Ereignisbearbeitung handelt, wenn das bei einem selbst nicht funktioniert.

Oder den Leuten die einem helfen wollen Sexismus unterstellen. Das geht auch.

pygame und SDL sind Open Source. Schau's dir halt an, ob die wirklich so strunzdumm sind, eine API zur Verarbeitung einer Reihe von Ereignissen anzubietet, obwohl das unterliegende System aus irgendwelchen Gruenden nur eines ausliefern kann pro Zeitschritt.
Benutzeravatar
__blackjack__
User
Beiträge: 4192
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Sonntag 29. September 2019, 10:24

@Sylvia: Ich habe das auch auf einem Ubuntu ausgeführt und genau beschrieben wie es sich bei mir verhält, und das ich das bei mir halt über das Kreuzchen in der Fensterleiste ganz normal beenden kann.

Das hat nichts mit dem Geschlecht das man Deinem Benutzernamen zuordnen könnte zu tun. Ich weiss ja nicht wer Du bist, weder Geschlecht, noch Alter, noch wie es mit Programmierkenntnissen aussieht.

Was ich weiss ist das ich das Verhalten des Programms das Du beschreibst nicht nachvollziehen kann, weder logisch, noch mit tatsächlichem Code. Und wenn ein eher einfacher Code bei zwei Leuten etwas unterschiedliches macht, muss man in der Regel anfangen auf die Details zu achten und sowohl die Rahmenbedingungen als auch das jeweilige Vorgehen präzise beschreiben.

Ich hatte das übrigens mit Pygame 1.9.4 laufen lassen und jetzt auch mal auf 1.9.6 aktualisiert und damit läuft der Code aus meinem letzten Beitrag immer noch und das Fenster lässt sich auch schliessen.

Wenn ich das durch einen Zähler für die `handle_event()`-Aufrufe erweitere und den ausgebe:

Code: Alles auswählen

#!/usr/bin/env python3
from itertools import count

import pygame


def handle_event(direction):
    should_stop = False
    for event in pygame.event.get():
        print(event)
        if event.type == pygame.QUIT:
            should_stop = True
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                direction = (+1, 0)
            if event.key == pygame.K_LEFT:
                direction = (-1, 0)
            if event.key == pygame.K_UP:
                direction = (0, -1)
            if event.key == pygame.K_DOWN:
                direction = (0, 1)
    return should_stop, direction


def main():
    pygame.init()

    size = 320, 240
    screen = pygame.display.set_mode(size)

    screen.fill((0, 0, 0))

    direction = (0, -1)
    position = screen.get_rect().center

    for i in count(1):
        print("handle_event call", i)
        should_stop, direction = handle_event(direction)
        if should_stop:
            break
        position = (position[0] + direction[0], position[1] + direction[1])

        screen.set_at(position, (255, 255, 255))
        pygame.display.flip()
        pygame.time.wait(100)


if __name__ == "__main__":
    main()
Dann kann ich auch problemlos mehrere Tasten-Ereignisse pro Aufruf bekommen:

Code: Alles auswählen

…
handle_event call 18
handle_event call 19
handle_event call 20
<Event(2-KeyDown {'unicode': '<', 'key': 60, 'mod': 0, 'scancode': 94, 'window': None})>
<Event(2-KeyDown {'unicode': '6', 'key': 54, 'mod': 0, 'scancode': 15, 'window': None})>
<Event(2-KeyDown {'unicode': '', 'key': 304, 'mod': 0, 'scancode': 50, 'window': None})>
<Event(2-KeyDown {'unicode': 'A', 'key': 97, 'mod': 1, 'scancode': 38, 'window': None})>
handle_event call 21
handle_event call 22
<Event(3-KeyUp {'key': 304, 'mod': 0, 'scancode': 50, 'window': None})>
handle_event call 23
<Event(3-KeyUp {'key': 97, 'mod': 0, 'scancode': 38, 'window': None})>
handle_event call 24
<Event(3-KeyUp {'key': 60, 'mod': 0, 'scancode': 94, 'window': None})>
<Event(3-KeyUp {'key': 54, 'mod': 0, 'scancode': 15, 'window': None})>
handle_event call 25
handle_event call 26
…
Allerdings: Maximal vier Stück, auch wenn ich mit der ganzen Hand deutlich mehr drücke. Man sieht also das ich keine Gamer-Tastatur verwende, denn das ist eine Beschränkung der Tastatur, das die nur vier gedrückte Tasten gleichzeitig erfasst. (Ist eine gute alte mechanische Cherry-Tastatur die so alt ist, das sie von Haus aus nur einen PS/2-Anschluss hat.)

``global`` ist übrigens ein Schlüsselwort das man nicht verwendet. Funktionen und Methoden sollten alles was sie ausser Konstanten benötigen als Argument(e) übergeben bekommen und nicht magisch irgendwo aus der Umgebung verwenden. Damit koppelt man Funktionen/Methoden auf sehr undurchsichtige Weise, kann sie nicht mehr wirklich einzeln betrachten, oder testen, oder einfach in ein anderes Modul verschieben, und so weiter.
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
Sylvia
User
Beiträge: 5
Registriert: Samstag 28. September 2019, 08:35

Sonntag 29. September 2019, 13:23

Danke _blackjack_. Dein Post hat mich auf die Idee gebracht es an einem anderen Rechner (Elite Book) zu probieren. Dort liefert mir der gleiche Code mit der gleichen pygame-Version (1.9.6) offenbar unbegrenzt viele KEY-Events pro Schleifendurchlauf.

Scheint also ein Hardware-Problem zu sein.

Allerdings lässt sich der von _deets_ gepostete Code auch am Elite Book mit Windows und Thonny nicht regulär über den Schließen-Button beenden, obwohl ich keinen Fehler im Code sehe. Wenn ich es in Ubuntu direkt über die Konsole starte, nicht in Thonny, dann lässt es sich jedoch ganz normal schließen. Sehr merkwürdig ...
__deets__
User
Beiträge: 6386
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sonntag 29. September 2019, 18:04

Wenn man GUI Skripte in einer IDE ausführt kommt es leider oft dazu, das sich die Ereignisschleifen der IDE und des GUI toolkits in die Quere kommen. Das gilt für tkinter, Qt - aber auch pygame. Sollte man also nicht machen, sondern immer die Konsole verwenden. Das könnte dein Problem erklären.
Antworten