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.
Related
So i am making a pygame program and for that i have written a GUI.py module. One of the classes is Button and one of the methods of that button is clicked, which basically checks if the button is being pressed. Initially i used pygame.mouse.get_pressed() to check for mouse press, but the problem i had with that is that it registered multiple presses within a single frame, which is not what i want.
def clicked(self):
mouse_pos = pygame.mouse.get_pos()# Gets the position of the mouse
mouse_pressed = pygame.mouse.get_pressed()# Checks if the mouse is being pressed
# checking if the mouse is already inside the button
if self.mouseover():
# mouse_pressed[0] returns true if the left mouse button is being pressed
if mouse_pressed[0]:
return True
return False
So i need to use events to check for mouse press. However, i am importing GUI.py to other modules, which are then imported to main.py. Becasue of this, i cannot import main.py to GUI.py. But main.py is where pygame.event.get() is being called in the main loop. I could call the method in other modules and pass in events as an argument, but i want to do that every single time i make a button.
Sorry if what i tried to explain is unclear, but here is what the question boils down to. Is there a way to make pygame.event.get() available to all the modules in my program independent of everything else?
The way i solved this in my pygame UI module was to store the previous frame click state, so if the mouse was clicked this frame and not last frame, its a click otherwise its being held down and nothing happens. (my module is a bit more complicated as it makes sure you clicked on it, then let go on it to count as a click)
if click and not previous_frame_click:
#clicked
if you are just using a function and not a class to do this, then maybe create a global variable
last_frame_click = False
def clicked(self):
global last_frame_click
mouse_pos = pygame.mouse.get_pos()# Gets the position of the mouse
mouse_pressed = pygame.mouse.get_pressed()# Checks if the mouse is being pressed
# checking if the mouse is already inside the button
if self.mouseover():
# mouse_pressed[0] returns true if the left mouse button is being pressed
if mouse_pressed[0] and not last_frame_click[0]:
last_frame_click = mouse_pressed
return True
last_frame_click = mouse_pressed
return False
EDIT: just noticed you said one of the classes, dont worry about above code, just use self.last_frame_click
When I had a similar issue I created an event manager module for it. Anything that wanted to be notified about events would register an event handler with the event manager, providing the events the handler was interested in and a event handler callback.
The event manager was handed the events from the event loop and it would check the events against the registered events. If there was a match then the event manager would call the associated event handler callback passing along the event as an argument to the callback.
It sounds more complicated than it really is.
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.
How do you make the code stop for a moment before checking for something else? (I'm new to code)
if BonusTime==True and Drop==True:
if event.type == pygame.MOUSEBUTTONDOWN:
window.blit(Fired,(mouseX-12,mouseY-12))
Cankill=True
#I want it to delay here
Cankill=False
There is a cross hair that follows the mouse and when I click it, it fires. The problem is, you can just hold the mouse down and leave the cross hair in one place. Whenever an enemy walks into the cross hair, they die. I need it so even when you hold it will only fire once. I plan to make it delay the if statement, to set "Cankill" to true and then wait a second, after waiting, it should set "Cankill" to false. I've already looked through a lot of other people's questions similar to this and haven't found something I can understand. So if you could please help me find out how to delay it in the way I need.
"Pausing" is not what you want - if you do that, your game will just freeze, and nothing will move (since you are usign the OS mouse pointer, maybe it could move).
Anyway, the function to "pause" inside a game is pygame.time.wait(1000) - the number is in miliseconds.
Waht you actually need is to mark down the time the click was made, continue with the game, and when 1 second has passed, reset the variable back to the other state.
Something along:
last_trigger = 0
while True:
# game updating code goes here (getting events, and such)
...
if Cankill and pygame.time.get_ticks() - last_trigger > 1000:
Cankill = False
if event.type == pygame.MOUSEBUTTONDOWN:
window.blit(Fired,(mouseX-12,mouseY-12))
Cankill=True
last_trigger = pygame.time.get_ticks()
The "get_ticks" call returns the number of miliseconds that passed since pygame was initialized - and is usefull for this time of timers.
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:
...
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.