Pygame time.set_timer

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
lemonbutterfly
User
Beiträge: 9
Registriert: Dienstag 13. Januar 2015, 00:44

servus,
die timer anwendung, wie sie zu sehen ist funktioniert für den einzelfall schon richtig gut (verbesserungsvorschläge wären super)
jetzt versuche ich auf den 3 buttons auch 3 verschiedene funktionen laufen zu lassen.
verschiedene laufzeiten funktionieren schonmal ABER die ausgeführte funktion ist immer die gleiche und bis jetzt ist jeder versuch gescheitert, das zu ändern.
dazu möcht ich noch sagen das ich erst ein anfänger in der programmierung bin.

Code: Alles auswählen

import pygame as pg
import sys
import gfx
import sfx
from pygame.locals import*

black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)

pg.init()
show = gfx.Image()
play = sfx.Sound()
clock = pg.time.Clock()
fps = 60
screen = pg.display.set_mode((1024, 768), 0, 32)
pg.display.set_caption("timed funktion")

button1 = (409, 362)
button2 = (409, 449)
button3 = (408, 537)
timer_pos = button1
lenght = height = 70
##########  timer  #####################
wait1 = 3
wait2 = 4
wait3 = 5
pg.time.set_timer(USEREVENT + 1, 1000)

class Timer(object):   # macht timer zu einem objekt
    hour = 0
    min = 0
    sec = 0
    timerun = 0

    def print_timer(self):
        return print("(%.2d:%.2d:%.2d)" % (start_timer.hour, start_timer.min, start_timer.sec))

    def show_timer(self):       # ausgabe auf sceen
        fontmy = pg.font.Font("fonts/miso.ttf", 36)
        text01 = fontmy.render("Timer:" + str("%.2d:%.2d:%.2d" %
                                              (start_timer.hour, start_timer.min, start_timer.sec)), True, white)
        screen.blit(text01, timer_pos)
        pg.display.update()

    def inc_timer(self, x, dummy):
        dummy.sec = x
        if x >= 60:
            dummy.min, dummy.sec = divmod(x, 60)
            if dummy.min >= 60:
                dummy.hour, dummy.min = divmod(dummy.min, 60)
        return start_timer

    def counter(self):
        start_timer.sec -= 1
        if start_timer.hour > 0 > start_timer.sec and start_timer.min == 0:
            start_timer.hour -= 1
            start_timer.min += 60
        if start_timer.min > 0 > start_timer.sec:
            start_timer.min -= 1
            start_timer.sec += 60
        dummytimer.print_timer()
        if start_timer.hour == 0 and start_timer.min == 0 and start_timer.sec == 0:
            print("countdown has endet -> start function")
            play.click3()
            Timer.timerun = 0

start_timer = Timer()       # objekte der klasse Timer
load_timer = Timer()
about_timer = Timer()
dummytimer = start_timer

#########  mainloop  ####################
game = True
while game:
    keys = pg.key.get_pressed()
    for event in pg.event.get():
        if Timer.timerun == 1:
            if event.type == USEREVENT + 1:     # counter tick
               dummytimer.counter()
        if event.type == pg.QUIT or keys[pg.K_ESCAPE]:
            game = False
            sys.exit()
        if event.type == MOUSEBUTTONDOWN:
            mx, my = event.pos
            if (button1[0] < mx < button1[0]+lenght) and (button1[1] < my < button1[1]+height):
                if start_timer.sec == 0:
                    timer_pos = button1
                    dummytimer.inc_timer(wait1, start_timer)
                    dummytimer.print_timer()
                    Timer.timerun = 1
            if (button2[0] < mx < button2[0]+lenght) and (button2[1] < my < button2[1]+height):
                if start_timer.sec == 0:
                    timer_pos = button2
                    dummytimer.inc_timer(wait2, start_timer)
                    dummytimer.print_timer()
                    Timer.timerun = 1
            if (button3[0] < mx < button3[0]+lenght) and (button3[1] < my < button3[1]+height):
                if start_timer.sec == 0:
                    timer_pos = button3
                    dummytimer.inc_timer(wait3, start_timer)
                    dummytimer.print_timer()
                    Timer.timerun = 1
    screen.blit(show.start, (0, 0))
    if Timer.timerun == 1:          # blit nur wenn timer aktiv
        dummytimer.show_timer()
    pg.display.update()
    clock.tick(fps)
pg.quit()
BlackJack

@lemonbutterfly: Du möchtest Dir objektorientierte Programmierung vielleicht ausserhalb von so einem für Anfänger doch schon recht komplexem Programm anschauen und lernen wie man das richtig macht. Die Klasse `Timer` ist keine Klasse. Die Attribute gehören nicht auf die Klasse, und keine der ”Methoden” benutzt den Umstand das es Methoden auf einem Objekt sind, das sind alles Funktionen. Die zudem auch noch teilweise auf globalen Werten operieren.

Steck mal das Hauptprogramm in eine Funktion (wird konventionell `main()` genannt). Auf Modulebene sollten nur Konstanten, Funktionen, und Klassen definiert werden. Das hat dann auch zur Folge dass man das Hauptprogramm und definitionn von Funktionen oder Klassen die davon benutzt werden, nicht so unübersichtlich mischen kann. Der Quelltext Deines Hauptprogramms fängt ja an, wird von einer Klassendefinition unterbrochen und geht dann erst weiter.

``return print(…)`` macht keinen Sinn. `print()` gibt etwas aus und nichts sinnvolles zurück. An der Stelle würde ich auch eher die `__str__`-Methode implementieren und dann das `Timer`-Exemplar selbst mit `print()` ausgeben. Die `show_timer()`-Methode bräuchte dann nicht die gleiche Umwandlung eines `Timer`-Exemplars noch mal machen, sondern kann einfach `str()` mit sich selbst aufrufen.

Das `datetime`-Modul enthält Datentypen die einen mit Zeitwerten rechnen lassen ohne dass man sich das selber basteln muss.

Eine Methode auf einem `Timer`-Objekt würde ich kein ``pg.display.update()`` machen lassen. Stell Dir mal vor Du willst mehrere Timer gleichzeitig darstellen, dann würde jeder das Display aktualisieren, statt das man erst alle Blittet und dann *einmal* aktualisiert. Die Kommunikation zwischen Arbeitsspeicher und Grafikkarte kann potentiell verhältnismässig teuer sein.

`dummy` ist etwas was nicht gebraucht wird. Es sieht extrem komisch aus wenn jemand anfängt mit etwas was `dummy` heisst, unmengen an Operationen durchzuführen.

Die ”Methoden” von `Timer` dürften wie gesagt gar nicht auf `start_timer` und `dummytimer` zugreifen. Funktionen und Methoden sollten ausser auf Konstanten nur auf Werte zugreifen die als Argument übergeben wurden. Und nicht einfach so auf Namen die irgendwie aus der Umgebung kommen. Das schafft undurchsichtige Zusammenhänge.

Die `game` Variable erscheint mir unsinnig. Die Hauptschleife kannst Du durch ``while True:`` ersetzen, denn `game` kann an dieser Stelle niemals `False` werden. Desweiteren kann die letzte Zeile, die nach der ``while``-Schleife niemals erreicht werden, ist also entweder überflüssig, oder sollte nicht *dort* stehen, sondern bevor das Programm beendet wird ausgeführt werden.

Die `button*` würde ich als `pygame.Rect` anlegen, dann kann man leichter testen ob die Mausposition darin liegt.

Wenn man anfängt Namen durchzunummerieren will man in der Regel eigentlich eine Datenstruktur verwenden statt der Einzelwerte. Meistens eine Liste. Das trifft hier auf die `button*` und `Timer`-Exemplare zu. Dann müsste man auch nicht dreimal fast gleiche ``if``-Abfragen samt davon abhängigen Code schreiben, sondern kann das einmal schreiben in einer Schleife über die zusammengehörenden `button*` und `Timer`-Exemplare. Stichwort `zip()`-Funktion. Oder man führt einen Datentyp ein der jeweils Button und Timer zu einem Objekt zusammenfasst.
BlackJack

@lemonbutterfly: Nachtrag: Grundsätzlich ist es auch keine gute Idee so einen Countdown selber jede Sekunde zu aktualisieren. Das ist ungenau. Du lässt einmal die Sekunde ein Timer-Event erzeugen und wartest in jedem Schleifendurchlauf auch 1 Sekunde, das heisst es kann passieren, dass das Timer-Event etwas länger braucht und deshalb erst im nächsten Frame verarbeitet wird.

Man würde so einen Timer eher so modellieren dass man die Differenz von Start- und aktueller Zeit berechnet, beziehungsweise von aktueller Zeit und dem Zeitpunkt an dem der Countdown zuende sein soll. Dann ist man auch unabhängig von der Framerate, muss nicht ständig und regelmässig den Zustand des `Timer` aktualisieren sondern kann jederzeit den aktuellen Zustand berechnen.
lemonbutterfly
User
Beiträge: 9
Registriert: Dienstag 13. Januar 2015, 00:44

vielen dank BlackJack für die schnelle und ausführliche antwort,
auch wenn ich nich alles verstehe, was du geschrieben hast, hab ich die ersten änderungen schon vorgenommen.
...vieleicht hab ich mir für den anfang ein kleinwenig viel vorgenommen, aber ich lerne mit jedem tag dazu :-)

der meinloop ist jetzt eine funktion

Code: Alles auswählen

if __name__ == "__main__":
    mainloop()
die überflüssigen sachen hab ich alle rausgeschmissen und bin grad dabei die buttons in rect umzubauen.
das timer objekt hab ich auf:

Code: Alles auswählen

class Timer(object):    
    hour = 0
    min = 0
    sec = 0
reduziert.

du hattest das "datetime" modul erwähnt, das hab ich auf pygame.org nen paar infos zu gefunden werd aber bis jetzt noch nicht so richtig schlau draus, wie ich des am besten einbinden soll
...da mir durch die arbeit nen bisschen die zeit fehlt, dauerts manchmal nen bisschen länger bis ich was gelernt hab,
ich wäre sehr dankbar für hilfreiche ansätze
lemonbutterfly
User
Beiträge: 9
Registriert: Dienstag 13. Januar 2015, 00:44

ps. dadurch das der mainloop jetzt eine funktion ist, wird die variable timer_pos nicht mehr akzeptiert :cry:
...vom gefühl aus würde ich sagen, die werte als attribut in die funktion packen aber irgenwie erscheint mir das falsch...
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

lemonbutterfly hat geschrieben:du hattest das "datetime" modul erwähnt, das hab ich auf pygame.org nen paar infos zu gefunden werd aber bis jetzt noch nicht so richtig schlau draus, wie ich des am besten einbinden soll
Das datetime-Modul ist ein Standardmodul von Python. Du musst daher in der Dokumentation von Python nachschauen und nicht in der von PyGame.
lemonbutterfly hat geschrieben:ps. dadurch das der mainloop jetzt eine funktion ist, wird die variable timer_pos nicht mehr akzeptiert :cry:
Was heißt "nicht mehr akzeptiert?" Gibt es eine Fehlermeldung dazu? Am besten postest du diese und den Quellcode deines Programms. Dann lässt sich das schnell nachvollziehen.
lemonbutterfly hat geschrieben:

Code: Alles auswählen

class Timer(object):    
    hour = 0
    min = 0
    sec = 0
Das funktioniert so nicht. hour, min und sec sind nun Attribute von der Klasse Timer. Wenn du einem Objekt ein Attribut geben möchtest, dann musst du das in der __init__-Methode machen.

Code: Alles auswählen

class Timer(object):
    def __init__(self, hour, min, sec):
        self.hour, self.min, self.sec = hour, min, sec
Das Leben ist wie ein Tennisball.
BlackJack

@lemonbutterfly: Hier mal grob skizziert wie man so einen Timer schreiben könnte der nicht regelmässig aktualisiert werden muss und von dem man jederzeit die aktuelle Restzeit abfragen kann:

Code: Alles auswählen

#!/usr/bin/env python
from __future__ import absolute_import, division, print_function
from datetime import datetime as DateTime, timedelta as TimeDelta
from time import sleep

ZERO_TIME_DELTA = TimeDelta()


class Timer(object):

    def __init__(self, hours=0, minutes=0, seconds=0):
        self.end_time = (
            DateTime.today()
            + TimeDelta(hours=hours, minutes=minutes, seconds=seconds)
        )

    def __str__(self):
        minutes, seconds = divmod(self.time_left.seconds, 60)
        hours, minutes = divmod(minutes, 60)
        return '{0:02d}:{1:02d}:{2:02d}'.format(hours, minutes, seconds)

    @property
    def time_left(self):
        return max(self.end_time - DateTime.today(), ZERO_TIME_DELTA)

    @property
    def has_finished(self):
        return self.time_left == ZERO_TIME_DELTA


def main():
    timer = Timer(0, 1, 30)
    while not timer.has_finished:
        print(timer)
        sleep(0.5)
    print(timer, 'Kaboom!')


if __name__ == '__main__':
    main()
lemonbutterfly
User
Beiträge: 9
Registriert: Dienstag 13. Januar 2015, 00:44

@BlackJack vielen Dank für deinen "timer" der code funktioniert echt prima.
ich hoffe es hat dir nicht zu viel mühe bereitet.
es werden sich bestimmt noch viele nach mir darüber freuen.

hab das modul in mein hauptprogramm eingebunden und übergebe die countdownzeit als parameter,
hab auch noch nen "sys.exit" in die while schleife gepackt, sonst ließ sich das programm nicht beenden während der timer lief.


...nur steh ich momentan wieder vor der problematik, was eigentlich auch meine erste frage war.

Code: Alles auswählen

def main(hour, minute, sec):
    timer = Timer(hour, minute, sec)
    while not timer.has_finished:
        print(timer)
        sleep(1)    # sleep(0.5)
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
    print('Kaboom!')
    play.wolf()
die auszuführende funktion nach ablauf des timers steht in der main und mir gelint es einfach nicht anstelle der funktion eine variable zu platzieren
...ich hab auch schon überlegt eine variable als switch einzubauen, so dass der timer sie auf 1 und die ausgührte fkt wieder auf 0 setzt aber dann braucht es ja für jede eizelne abfrage nen eigenen switch
BlackJack

@lemonbutterfly: Ich verstehe das Problem nicht was Du da lösen möchtest. Sorry.
lemonbutterfly
User
Beiträge: 9
Registriert: Dienstag 13. Januar 2015, 00:44

also:
in meinem hauptprogramm (was mal irgendwann ein spiel werden will), hab ich zb. auf dem startbild 3 buttons

Code: Alles auswählen

        
import timer_fkt
...
countdown = timer_fkt
...
if event.type == MOUSEBUTTONDOWN:
            if show.main == show.start:
                if event.button == 1:
                    if start_rect.collidepoint(event.pos):  # test ob mouse innerhalb rect
                        show.main = show.home
                        countdown.main(0, 0, 3)
                        play.bg()
                    elif load_rect.collidepoint(event.pos):
                        countdown.main(0, 0, 5)
                    elif about_rect.collidepoint(event.pos):
                        countdown.main(0, 0, 1)
bei einem klick fängt der timer an zu zählen mit den gegebenen parametern
aber wenn der timer abgelaufen ist, wird immer die kunftion "play.wolf" gestartet(siehe letzten post)
und funktionen kann man schlecht als parameter übergeben(zumindest gibt das bei mir nen haufen fehler)
BlackJack

@lemonbutterfly: Ich glaube so ganz verstanden habe ich es immer noch nicht. Allerdings sind Funktionen in Python Objekte, können also auch als Argumente übergeben werden wie jedes andere Objekt auch.
lemonbutterfly
User
Beiträge: 9
Registriert: Dienstag 13. Januar 2015, 00:44

@BlackJack das klingt einfacher als getan...bei jedem versuch bekomm ich immer nur ne fehlermeldung im pycharm
wie zb. wenn ich die fkt so einbauen will:

Code: Alles auswählen

# hauptfunktion #        
        if event.type == MOUSEBUTTONDOWN:
            if show.main == show.start:
                if event.button == 1:
                    if start_rect.collidepoint(event.pos):  # test ob mouse innerhalb rect
                        show.main = show.home
190                     countdown.main(0, 0, 3, play.click())
                        play.bg()

Code: Alles auswählen

#timer funktion#
def main(hour, minute, sec, funktion):
    timer = Timer(hour, minute, sec)
    while not timer.has_finished:
        print(timer)
        sleep(1)    # sleep(0.5)
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
    print('Kaboom!')
45  funktion()     
und denn kommt halt der folgende fehler bei raus:
Traceback (most recent call last):
File "E:/python/simcraft 0.6.5/simcraft.py", line 190, in <module>
countdown.main(0, 0, 3, play.click())
File "E:\python\simcraft 0.6.5\timer_fkt.py", line 45, in main
funktion()
TypeError: 'NoneType' object is not callable
BlackJack

@lemonbutterfly: Du übergibst ja auch nicht die Funktion, sondern Du rufst die Funktion auf und übergibst deren Rückgabewert. Der ist `None` und das kann man nicht aufrufen — da bekommt man dann die Ausnahme die Du siehst.
lemonbutterfly
User
Beiträge: 9
Registriert: Dienstag 13. Januar 2015, 00:44

genau da liegt das problem, ich bekomm es einfach nicht hin die fkt zu übergeben...ist auch genaugenommen das ursprüngliche problem gewesen
das netz bietet mir auch keine konkrete antwort auf die frage(oder ich hab sie noch nicht gefunden)
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

BlackJack hat dir die Lösung schon verraten. Wenn du dich Funktion über geben möchtest, dann darfst du sie vorher nicht aufrufen. Das machen aber die () in deinem countdown.main-Aufruf, wo du play.click übergeben möchtest. Und dann wird als Funktion nicht play.click übergeben, sondern das Ergebnis des play.click-Aufrufs. Und das ist None.
Das Leben ist wie ein Tennisball.
lemonbutterfly
User
Beiträge: 9
Registriert: Dienstag 13. Januar 2015, 00:44

danke für den zaupfahl, hab den wald vor lauter klammern nicht gesehen...jetzt läuft es
...jetzt muß ich nur noch was basteln, das des ganze grafisch angezeigt wird.
lemonbutterfly
User
Beiträge: 9
Registriert: Dienstag 13. Januar 2015, 00:44

irgendwie komm ich mir grad vor wie beim domino
ein problem gelöst, schon entsteht ein neues
durch die while schleife in der timerfunktion updatet sich nur der timer auf dem screen und nicht der rest, der in dem hauptprogram steht.
...wenn ich das richtig interpretiere, steht das hauptprogramm solange still, bis der timer beendet ist und dann wird erst die whileschleife im hauptprogramm fortgesetzt.
gibts da eine elegante möglichkeit das zu ändern?
ich möchte ungern mein funktionierendes timer modul mit in dem maincode schmeißen
es wäre zwar eine möglichkeit ein farbiges rect drüber zu blitten aber das sieht dann doof aus

btw verhält der timer sich auch merkwürdig, indem er die erste secunde überspringt

Code: Alles auswählen

def main(hour, minute, sec, funktion, timer_pos):
    timer = Timer(hour, minute, sec)
    fontmy = pg.font.Font("fonts/miso.ttf", 36)
    while not timer.has_finished:
        print(timer)
        text01 = fontmy.render("Timer:" + str(timer), True, white)
        screen.blit(text01, timer_pos)
        pg.display.update()
        sleep(1)    # sleep(0.5)
        for event in pg.event.get():
            if event.type == QUIT:
                pg.quit()
                sys.exit()
    print('Kaboom!')
    funktion()

if __name__ == '__main__':
    main(0, 0, 3, play.saw, (0, 0))
BlackJack

@lemonbutterfly: Es ist extrem verwirrend das Du hier eine Funktion `main()` nennst, von einem „maincode” sprichst, der aber nicht diese `main()`-Funktion ist sondern der Code der die `main()` aufruft.

Du musst das halt irgendwie in die Hauptschleife einbauen statt eine weitere Schleife zu starten. Zum Beispiel eine Klasse schreiben die so einen Timer kapselt und eine `update()`-Methode besitzt die den Timer, die Position, und vielleicht auch das Surface auf dem der Timer geblittet werden soll kennt und die regelmässig in der Hauptschleife aufgerufen wird.

Die erste Sekunde wird nicht übersprungen sondern der Timer ”läuft” ja in dem Augenblick los in dem das Objekt erstellt wird. Und egal wie klein die Zeitspanne von dort bis zum ersten Auswerten als Zeichenkette auch ist, ist die erste Sekunde halt schon nicht mehr voll sondern angebrochen.
Antworten