Pygame set_timer() not working? - python

I'm creating a game in pygame that requires waves of enemies being sent out consistently. My wave-sending code works perfectly when used just once by itself, but when I try to repeat it with set_timer(), nothing happens.
Necessary code:
def game():
WAVE_EVENT = USEREVENT + 1
pygame.time.set_timer(WAVE_EVENT, 1000)
and
for event in pygame.event.get():
if pygame.event.get(WAVE_EVENT):
wave1()
print 'Wave1 sent'
Result? Nothing at all. My player just sits there in the middle of the screen looking bored.
How would I make the set_timer event actuall work?

Your event loop code is incorrect. When you call pygame.event.get() (with no argument), you'll get all events currently in the event queue, including the timer driven WAVE_EVENTs. Your second call tries to get only the WAVE_EVENTs, but since they've already been collected by the first call, there won't be any left in the queue.
Generally the way to deal with this is to check the type of each event as you iterate over them:
for event in pygame.event.get(): # gets all events, loops over them
if event.type == WAVE_EVENT:
wave1()
print 'Wave1 sent'
Often you'll also want to check for other event types (such as QUIT events that are generated when the user tries to close your window). For simple code with a few different event types, you'll probably just use elif statements, but for complicated stuff you might want to use a dictionary to map event types to handler functions.
An alternative solution would be to separately request each type of event you need to handle, and never request all event types:
if pygame.event.get(WAVE_EVENT):
wave1()
print 'Wave1 sent'
if pygame.event.get(pygame.event.QUIT):
# ...
for key_event in pygame.event.get(pygame.event.KEYDOWN):
# ...
This later form is good for separating out different parts of your code that consume different events, but it may cause problems if you're not requesting all of the event types that are being queued. You'll probably need to be using some of the filtering functions (e.g. pygame.event.set_allowed) to make sure your event queue doesn't fill up with events you're not checking for.

Related

How to use nested events in PySimpleGUI

I'm trying to used nested events.
When I browse a file, the filename alone being stripped from the full path triggers an event that makes the filename to be transferred to a textbox which has the enable_events set to true, which will trigger another event to call a function and get the pdf details.
If I enable the two commented lines, you can see that the function works and transfers the return value, but I'm trying to separate these two events as the function to get the details of the PDF takes a while.
So the order is:
__pdfpath__ gets the full path of a certain browsed file which triggers an event that transfers the filename to __bookfilename__ which should trigger another event which will call a function that will send its response to __pdfdetails__
import PySimpleGUI as sg
import os
def get_pdf_details(pdfname):
return pdfname + ' was processed'
layout = [
[sg.InputText('',key='_pdfpath_',enable_events=True),sg.FileBrowse(key='_filepath_')],
[sg.Text('',key='_bookfilename_',enable_events=True,size=(40, 1))],
[sg.Text('',key='_pdfdetails_', size=(40, 1) )],
]
window = sg.Window('', layout)
while True:
event, value = window.Read()
if event == '_pdfpath_':
filename = os.path.basename(value['_pdfpath_'])
window.Element('_bookfilename_').Update(filename)
#response = get_pdf_details(filename)
#window.Element('_pdfdetails_').Update(response)
if event == '_bookfilename_':
response = get_pdfdetails(value['_bookfilename_'])
window.Element('_pdfdetails_').Update(response)
So the question is, how can I trigger the second event?
I tried creating a second window.Read() to create a second loop like this:
event2, value2 = window.Read()
but didn't work.
Any ideas?
Thanks
The way through this isn't events traveling around PySimpleGUI. What you need to do is break out your long-running function into a Thread.
EDIT - Since the original answer in early 2019 a lot of continued to be added to PySimpleGUI.
Because having a function take too long be one of the most common problems first encountered writing a GUI, a method was added so that beginners that are not quite ready to write their own threaded code aren't held up.
The Window.perform_long_operation takes a function or a lambda expression as a parameter and a key that will be returned when your function returns.
window.perform_long_operation(my_long_operation, '-OPERATION DONE-')
You'll get the benefits of multi-threading without needing to do all the work yourself. It's a "stepping stone" approach. Some users have only been using Python for 2 or 3 weeks when they write their first GUI and simply aren't ready for the threading module.
The Cookbook has a section on this topic and the eCookbook has a number of examples that can be immediately run. http://Cookbook.PySimpleGUI.org and http://Cookbook.PySimpleGUI.org
The Demo Programs are always a great place to look too - http://Demos.PySimpleGUI.org. There are at least 13 examples shown there as of 2021.
Try this:
while True:
event, value = window.Read()
process_event(event, value)
def process_event(event, value):
if event == '_pdfpath_':
filename = os.path.basename(value['_pdfpath_'])
window.Element('_bookfilename_').Update(filename)
value.update(_bookfilename_=filename)
process_event('_bookfilename_', value)
if event == '_bookfilename_':
response = get_pdfdetails(value['_bookfilename_'])
window.Element('_pdfdetails_').Update(response)
PSG has one magic element that can Fire event whenever you want, It's basically is Button, it can be hidden by setting visible=False. Just call window['ButtonKey'].click() where you want to fire 'ButtonKey' event.

Pygame event handlers interfering with one another?

I'm making a game launcher in Python 3.7, and I'm using the Pygame ("pg") library. I made a class for clickable text ("links"), and I have an event handler which checks for the pg.QUIT function.
I have a while loop set up that begins with the event handler function that checks for pg.QUIT, and later on in the loop, I have a class function that listens to see if the link has been clicked.
Unfortunately, when they are both able to run within the loop, they interfere with each other and some input gets ignored, such as pg.MOUSEBUTTONDOWN (which is crucial here).
I've tried disabling the event handler (I just commented the line that calls it), and that made it work; every input was registered. However, I don't want to check for pg.QUIT inside of an object class, and I don't want to look for specific objects in my event handler: I want to keep these things separate.
The code in my class is as follows:
(Inside TextObject class)
def link():
for e in pg.event.get():
if e.type == pg.MOUSEBUTTONDOWN and {mouse is over link}:
print('click!')
The code in my event handler is as follows:
def handle():
for e in pg.event.get():
if e.type == pg.QUIT:
running = False
The code in my while loop is as follows:
while running:
handle()
{update screen and draw text}
textObject.link()
clock.tick(fps)
I want the program to listen for pg.QUIT, and then if that hasn't happened, move on and listen to see if the link has been clicked.
When I run it, the program only prints 'click!' after I've clicked the link about twenty times. It seems almost random.
I'm predicting that the two functions are interfering with each other, and I could fix it with some kind of joint function, but I'd really prefer to keep these functions vague without hard-coding in some text coordinates, y'know?
Thanks.
P.S. I used pseudo-code to give some context, but I've already confirmed that the issue lies in the code I've written explicitly.
Move all the event handling into your main loop. You're losing events because some are being handled in one loop (and discarding the un-handled events), and similarly in the other event loop, but for other event types.
Spreading the event processing throughout various parts of the code makes the logic and debugging harder. Ideally it works well to have a single logical item handled in a single place. So when you have an issue with user-input, you only need to debug the user-input section - rather than this-function, that-function, and this-thing-in-a-file-over-there.
Sure, if you need to see which button was pressed or whatever, write a function to do that, then call the function (passing it the mouse position) during the click event handling.
while running:
# Handle user input
for e in pg.event.get():
if e.type == pg.QUIT:
running = False
elif e.type == pg.MOUSEBUTTONDOWN:
if {mouse is over link}:
print('click!')
# Handle screen painting
...
Although I consider Kingsley answer as really good and valuable, I find splitting event execution onto separate files/functions useful in some cases (as sort of categorisation making code cleaner - especially if this goes with clean visualisation, like in this workspace of mine where use of events is seen easily by additional variable and restricted to few functions).
My way of doing that is by putting events into variable, so those are called once, and referring them further:
while running:
events = pg.event.get() # "variablised" event, so we call it once per frame
for e in events: # stuff can be used explicitly
if e.type == pg.QUIT:
running = False
textObject.link(events) #or passed to another function
This is how another file/function will look then:
(Inside TextObject class)
def link(events):
for e in events:
if e.type == pg.MOUSEBUTTONDOWN and {mouse is over link}:
print('click!')
I wanted to revive this topic because it gave me idea how to solve the problem the way I portrayed, yet I disagreed (only to some extent) with Kingsley's answer which can be really messy in some cases.

Why can pygame.event.get() only be called once per frame

I was writing a function to make it easy to detect a key press without having to use the for loop that is normally used:
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == key:
And I found that the function that I had written didn't work, and this is because I have this test_for_quit function in all my projects that runs every frame. I've found this to be useful as I can just copy this into any program that I'm writing:
def test_for_quit():
'''Shuts down the game if it's closed'''
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
Surprisingly this is the first game I've made since implementing that function to all my programs, that doesn't use key presses. But I found that the reason that the new function that I've written didn't work because pygame.event.get() had already been called in the test_for_quit function.
After some more testing I found that this method cannot be called twice, for instance:
while True:
print(pygame.event.get())
print() #Leaves an empty line
print(pygame.event.get())
returns:
[<Event(17-VideoExpose {})>, <Event(16-VideoResize {'h': 600, 'size': (800, 600), 'w': 800})>, <Event(1-ActiveEvent {'gain': 0, 'state': 1})>, <Event(4-MouseMotion {'rel': (538, 315), 'pos': (537, 314), 'buttons': (0, 0, 0)})>]
[]
So why is it that pygame.event.get() can only be called once, this really intrigues me and I couldn't find anything about it on the internet?
Also I've decided to not continue using the test_for_quit function and the key_down function in favour of just using the traditional for loop.
event.get can be called as many times as you want. The matter is it does take the events out of the event queue - and is up to your program to consume them. When it is called a second time with no interval (pygame itself knows nothing about "frames" - you give the delay between frames) - all events are gone, and no others have been generated.
The documentation for event.get reads:
This will get all the messages and remove them from the queue. If a type or sequence of types is given only those messages will be removed from the queue.
If you are only taking specific events from the queue, be aware that the queue could eventually fill up with the events you are not interested.
You have two choices: keep your "copy and pasted" call to event.get and read the keyboard state by using other calls - pygame.key.get_pressed for one - or to use a smarter way to consume the events in the queue.
There are even ways to check for evetns on the event queue without consuming them - so you could place those calls before a call to events.get.
One is that the function itself allows you to specify event types you are interested in. So let's suppose you have a function to deal with mouse events and a function to deal with keybard events:
def do_keys():
for event in pygame.event.get(KEYDOWN):
...
def do_mouse():
for event in pygame.event.get((MOUSEMOTION, MOUSEBUTTONDOWN)):
..
def main():
while True:
do_keys()
do_mouse()
# discard other events:
pygame.event.get()
# draw secreen
...
pygame.time.delay(...)
Or, which is even easier, you can simply assign the return value of the call to pygame.event.get to a variable - that will be a single list over which you can iterate as many times as you wish.
events = pygame.event.get()
for event in events():
# treat some evetns here
# some other logic here
...
do_keyboard_things(events)
def do_keyboard_things(events):
for event in events:
...

Is there an instant-updating function for texts or patterns as button.config() in tkinter?

I wrote a modified program of the 'mines' game, and I hope it shows every step/click graphically. I use time.sleep(0.5) to make a pause. So, in general the main program is like:
check_block():
if mine == 0:
buttons[current].config(image = tile_clicked)
elif mine == 1:
buttons[current].config(image = tile[1])
...
while(1):
time.sleep(0.5)
check_block()
get_next()
if check_fail():
break
However, the buttons don't update every 0.5 second: they are all updated together when the game(loop) finishes.
I guess it's just like 'cout' in C++: if you don't flush they will get stacked. So, is there a method to get them updated step by step, or say, instantly?
Thanks!
In all GUI systems you have to allow the message loop to run so that Windowing events occur promptly. So do not use a while loop like this. Instead, create a method that calls check_block() and get_next() and use after to call that function after a delay. At the end of that function, you use after again to call the same function again so that this function is called every 0.5 second forever. The after function queues a timer event and then lets the message queue be processed. Once your timer event fires, the callback function is run which allows you to do things and keep the UI responsive.
You should never call sleep in a GUI program. This is because the GUI must be "awake" at all times so that it can service events (including internal events that cause the screen to update). Instead, leverage the already-running eventloop by using the after method to put events on the queue at regular intervals.
In your case, you would replace the while loop with something like:
def do_check():
check_block()
if not check_fail():
root.after(500, do_check)
# in your initialization code, start the loop by calling it directly:
do_check()
I don't know what your get_next function does, so I don't know if you need to call it periodically too. Probably not. I'm guessing it waits for the next button press, which you don't need to do with tkinter or most other GUI toolkits. Instead, you configure the button to call a function when clicked.
Regardless, the way to do the type of looping you want is to place events on the event queue at a regular interval.

Pygame event queue

I would like to know if there is a way of using poll() or get() without removing the events from the queue.
In my game, I check input at different places (not only in the main loop) and sometimes I need to check the same event at different places but when i check it once it removes it from the queue. I tried using peek() but the problem is that I can't get the key corresponding to the event done.
while 1:
event = pygame.event.poll()
if event.type == KEYDOWN:
return event.key
else:
pass
#works but removes event from the queue
This can get the key corresponding to the event but with peek() it can't:
pygame.event.peek(pygame.KEYDOWN).key
#dosent work
However I can't use the first method because removes the event from the queue so I can't check key events elsewhere in the program.
I don't understand well how the queue works so maybe I'm just mistaking but I tried the first one at different location and only the first time i checked the event it worked.
My goal is to check events in different classes in my game.
Thanks for your help
I think a better design would be to check events in a single place - even if in a factored out function or method outside the mainloop code, and keep all the relevnt event data in other objetcts (as attributes) or variables.
For example, you can keep a reference to a Python set with all current pressed keys, current mouse position and button state, and pass these variables around to functions and methods.
Otherwise, if your need is to check only for keys pressed and mouse state (and pointer posistion) you may bypass events entirely (only keeping the calls to pygame.event.pump() on the mainloop). The pygame.key.get_pressed function is my favorite way of reading the keyboard - it returns a sequence with as many positions as there are key codes, and each pressed key has its correspondent position set to True in this vector. (The keycodes are available as constants in pygame.locals, like K_ESC, K_a, K_LEFT, and so on).
Ex:
if pygame.key.get_pressed()[pygame.K_ESCAPE]:
pygame.quit()
The mouse module (documented in http://www.pygame.org/docs/ref/mouse.html) allows you to get the mouse state without consuming events as well.
And finally, if you really want to get events, the possibility I see is to repost events to the Queue if they are not consumed, with a call to pygame.event.post - this call could be placed, for example at the else clause in an if/elif sequence where you check for some state in the event queue.
I don't know if it is good style, but what I did was just saving all the events in a variable and passing it to the objects that used their own event queues to detect "their" events.
while running:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
running = False
self.allS.update(events)
and in the update method:
for event in events:
print("Player ", event)
As far as I can tell there is no one 'right' way to do this, but one option is to save all the events into a variable. Then you can access them as many times as you want.

Categories

Resources