How to make pygame update the display only upon an event? - python

I'm trying to do something like this.
pygame.event.set_allowed([pygame.KEYDOWN])
for event in pygame.event.get():
. . .
pygame.display.flip()
Since the game I'm making is static when there's no input anyway, I'm trying to avoid re-rendering identical frames between inputs in every single while True iteration. This code seems to be almost working, except there's the slightest (disorienting) delay between input and result. If someone can help me mitigate this I want to go further and put much of the rest of the code within the for event in ... block to optimise a bunch more by skipping iterating over the game state 2D list every loop since it's static until there's input. Thank you.

The usual way is to update the game in every frame and to limit the frames per second using pygame.time.Clock.tick.
clock = pygame.time.Clock()
run = True
while run:
for event in pygame.event.get():
# handle events
# [...]
# clear background
# [...]
# draw scene
# [...]
# update the display
pygame.display.flip()
# limit frames per second
clock.tick(100)
If there is more than 1 event at once, your approach can update the game several times per frame. Sets a status variable when there was an event and updates the game once when the status is set:
run = True
while run:
redraw_scene = False
for event in pygame.event.get():
redraw_scene = True
# handle events
# [...]
if redraw_scene:
# clear background
# [...]
# draw scene
# [...]
# update the display
pygame.display.flip()

Related

PyGame - Can't hide mouse

I've read everything I could find about the mouse in pygame and yet I miss something.
The device has (and will not) have a mouse attached so I need to remove the cursor from the game surface, ideally even before it draws the first screen.
But no matter what I try I always have a big oppalin cursor in the middle of the screen.
My current function is this :
# initialize the pygame module
pygame.init()
# create a surface on screen that has the size of 240 x 180
screen = pygame.display.set_mode((160,80))
# define a variable to control the main loop
running = True
pygame.display.flip()
# main loop
while running:
screen.fill((0, 0, 0))
# hide cursor
pygame.mouse.set_pos((160,80)) # my screen is actually 160 x 80 px so this should hide it by pushing it over the edge
pygame.mouse.set_visible(False) # this should hide the mouse plain and simple
# Here I do other unrelated (but working) stuff, like displaying images and text
# event handling, gets all event from the event queue
for event in pygame.event.get():
# only do something if the event is of type QUIT
if event.type == pygame.QUIT:
# change the value to False, to exit the main loop
running = False
pygame.display.flip()
And the mouse is still on screen, sometimes after a while it disappears.
I tried other method like making the cursor transparent, but that crash the app.
Note that I'm running Pygame on an Alpine Linux with direct output to the framebuffer on a Raspberry Pi. There's no mouse attached to the device but if I connect one I can move the cursor around.
Pygame is Version 2.1.2 (compiled and installed by pip)
Python is 3.10.0
Any help or pointer would be greatly appreciated.
(sorry if it's a dumb question; I'm a total noob with python/pygame)
Just write the set_visible(False) outside the main loop, you don't need to call this every frame:
pygame.init()
screen = pygame.display.set_mode((160,80))
pygame.mouse.set_visible(False) # Hide cursor here
running = True
pygame.display.flip()
# main loop
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# unrelated with your question but I also suggest that you clear your background inside the loop, at the contrary you likely will need this at each frame
screen.fill((0, 0, 0))
# ... your code, blitting images etc
pygame.display.flip()

pygame: low fps causes difficulties to detect events

I am doing a pygame project and it is supposed to have only 4 fps which makes it a frame every 0.25 seconds. My problem is that when you click briefly on a key, the event might not get detected because the program does not check for events over the whole 0.25 seconds but rather once every 0.25 seconds, which makes it easy for it to miss out on an event. Is there a way to solve this problem in pygame?
(I am using clock.tick() to set the fps)
I cannot provide a solution for your particular situation as you do not show us your code. However
My problem is that when you click briefly on a key, the event might not get detected
No that is not true. All events are detected. pygame.event.get() returns all the event that have been occurred since the last call of this function. See How to get keyboard input in pygame?.
Your game logic may be wrong and you are setting a status when the KEYDOWN event occurs, but rest it when the KEYUP event occurs. This will cause the behavior.
It is even possible to run the event loop more often than the application loop.
Run the application loop and event loop at a higher frame rate, but run the game at only 4 FPS. For example, run the application loop at 40 FPS. Use a frame counter and run the game when the counter reaches 10:
clock = pygame.time.Clock()
frame_count = 0
run = True
while run:
clock.tick(40)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
frame_count = (frame_count +1) % 10
if frame_count == 0:
# run game and do drawing
# [...]
pygame.display.flip()

PyGame: Timer resets every time the mouse is moved

I am currently writing a game that requires a timer. I have written some code that appears to work, as in it counts upward in seconds, but when I move the mouse, the timer resets back to zero and starts counting up again. Obviously, this is not what I want to happen, and I've tried figuring out why this happens instead of counting up continuously, but I could not solve it.
My code is:
passed_time = 0
timer_started = False
(^outside the while loop)
(/ inside while loop)
for event in ev:
if event.type == pygame.QUIT:
is_alive = False
if start_clicked:
timer_started = True
if timer_started:
start_time = pygame.time.get_ticks()
if won:
timer_started = False
if timer_started:
passed_time = pygame.time.get_ticks() - start_time
text = font.render(str(passed_time / 1000), True, font_color)
screen.blit(text, (20, 450))
as well as mouse events that set [start_clicked] or [won] to True when their respective colors are clicked.
From my basic knowledge of timers in PyGame, this should keep counting up once start_clicked is set to true, however it just resets every time the mouse is moved, which completely defeats the point of a timer on a mouse-movement based avoider game. What can I do to fix this and make it run continuously, until the won variable is true?
And if for whatever reason I didn't include enough to figure out the issue, the full code is here:
By looking at your full code, your event loop is wrong, because your identation is off.
Currently you have if event.type == pygame.QUIT: in the event loop, and all the other if event.type == whatever outside it because they are not indented enough.
All the ifs checking the events must be inside the event loop, while ifs unrelated to the event loop (like if start_clicked:) must be outside, typically after, the event loop.
I cannot fix all your code, but fixing the event loop will surely help.

Pygame Display Update Glitch?

I have some code that is kinda funky. Basically, the variable gameStart is in a variable that uses the function, cursorOver, which finds and detects where and if the mouse button is and if it is pressed. I have 3 buttons and I want each button to become larger when the cursor is over the button. The first button implementation works. However, if I try to add another button, the button becomes enlarged, however, the button starts flickering.
window.blit(background,(0,0))
window.blit(title,(175,200))
pygame.draw.rect(window,PURPLE,(50,400,200,100),0)
pygame.draw.rect(window,PURPLE,(300,400,200,100),0)
pygame.draw.rect(window,PURPLE,(550,400,200,100),0)
close()
mouseX,mouseY = pygame.mouse.get_pos()
mouseClick = pygame.mouse.get_pressed()[0]
gameStartC = cursorOver(50,400,200,100,mouseX,mouseY)
instructionStartC = cursorOver(300,400,200,100,mouseX,mouseY)
objectiveStartC = cursorOver(550,400,200,100,mouseX,mouseY)
nextStartC = cursorOver(580,250,175,100,mouseX,mouseY)
if gameStartC == True:
while True:
pygame.draw.rect(window,PURPLE,(25,375,250,150),0)
pygame.display.update()
break
else:
pygame.draw.rect(window,PURPLE,(50,400,200,100),0)
pygame.display.update()
#this is the part where the code becomes glitchy
if instructionStartC == True:
while True:
pygame.draw.rect(window,PURPLE,(275,375,250,150),0)
pygame.display.update()
break
else:
pygame.draw.rect(window,PURPLE,(300,400,200,100),0)
pygame.display.update()
It's simply because you call pygame.display.update() multiple times.
You should create a standard game loop that typically does these three things:
handle input
update state
draw to screen
and then repeats.
In the 'draw to screen'-step, you draw all your sprites/rects/whatever to the screen surface, and then eventually call pygame.display.update() once at the end.
Calling pygame.display.update() multiple times, not clearing the screen between iterations of the loop and creating multiple unnecessary event loops are common beginner mistakes that lead to those kind of glitches IMHO.
So in your case, the code should probably look more like this:
if gameStartC:
pygame.draw.rect(window,PURPLE,(25,375,250,150),0)
else:
pygame.draw.rect(window,PURPLE,(50,400,200,100),0)
if instructionStartC:
pygame.draw.rect(window,PURPLE,(275,375,250,150),0)
else:
pygame.draw.rect(window,PURPLE,(300,400,200,100),0)
pygame.display.update()
I don't know what you expected the while-loops to do, and maybe you should use pygames Rect and Sprite classes. It will make your life easier.

Why is my pause system not working in Pygame?

Here's my check_for_pause() function:
#Check if the user is trying to pause the game
def check_for_pause():
keys=pygame.key.get_pressed() #Get status of all keys
if keys[K_SPACE]: #The space bar is held down
global paused #Make global so it can be edited
if paused==True: #It was paused, so unpause it
paused=False
elif paused==False: #It was playing, so pause it
paused=True
#Don't let the main loop continue until the space bar has been released again, otherwise the variable will flicker between True and False where the loop runs so fast!
space_bar_pressed=keys[K_SPACE]
while space_bar_pressed: #Repeat this loop until space_bar_pressed is False
keys=pygame.key.get_pressed()
if not keys[K_SPACE]: #Space bar has been released so set space_bar_pressed to False
space_bar_pressed=False
however this keeps making my program become unresponsive whenever I try to pause it! Basically, I want the variable "paused" to be either True or False. When the space bar is pressed, it should change to whichever one it isn't currently. Because I'm using check_for_pause() in another never-ending loop, I need to make it so that the function only stops executing when the space bar is released, otherwise if the user holds the space bar for more than a split second it will keep switching between True and False.
Any ideas why my program becomes unresponsive when I run this? I know it's to do with the bit that waits until the space bar's been released because when I remove that bit of the code then my program runs fine (but obviously the pause feature then doesn't work).
You have a main loop that probably looks something like this...
while True:
check_for_pause()
# Update the game
# Draw the game
When you check for pause, and the space key is pressed down, paused gets set to True. Then, you have this loop...
space_bar_pressed=keys[K_SPACE]
while space_bar_pressed: #Repeat this loop until space_bar_pressed is False
keys=pygame.key.get_pressed()
if not keys[K_SPACE]: #Space bar has been released so set space_bar_pressed to False
space_bar_pressed=False
The problem with this loop is that you assume that pygame.key.get_pressed() will continue to return up-to-date information. However, looking at the pygame source code, it appears that it uses SDL_GetKeyState, which says as part of its documentation..
Note: Use SDL_PumpEvents to update the state array.
In other words, repeatedly calling pygame.key.get_pressed() will NOT give you the updated key state if you are not additionally calling something like pygame.event.pump(), which actually updates pygame with the new key states. So, you could quickly fix this by introducing that pump function into the loop, as currently it is just running forever.
That being said: DON'T DO THIS. If you do it this way, your game will not be able to do ANYTHING when paused: this includes showing a "paused" screen, continuing to play the music in the background, etc. What you want to do instead is keep track of if the game is paused, and if so, change how the game is updating.
In the part of your game where you update things, some items should only happen if the game is paused. Something like...
paused = False
while True:
# This function should just return True or False, not have a loop inside of it.
paused = check_for_paused()
if not paused:
# This function moves your enemies, do physics, etc.
update_game()
draw_game()
In this case, the main loop will still happen, the game will continue to be drawn, and the input will continue to be handled. However, the enemies and players will not be moving, therefore the game can be said to be "paused".
Finally, there is also the fact that you're relying on get_key_pressed(), which you probably don't want to do. See this other similar answer I've given for reasons why you should be instead using the event queue.
You should never stop running your game loop in a game even if it paused. Also pauses are generally handled through events. For example look at this code:
import pygame, sys
from pygame.locals import *
pygame.init()
pygame.display.set_mode((400,400))
paused = False # global
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_SPACE:
paused = not paused
if paused:
continue # skip this iteration if paused
# add your game code here
print 'game code running'
In the above code, I toggle paused every time I press spacebar. If you want to pause only while holding spacebar do this:
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type in (KEYDOWN, KEYUP): # checks membership in tuple
if event.key == K_SPACE:
paused = not paused
if paused:
continue # skip this iteration if paused
# add your game code here
print 'game code running'
As a general note, you should always be handling events from your event queue or Pygame will just cry and whine and do nothing (be unresponsive). Thus, never stop spinning on your game loop even if you have paused the game.
EDIT: Alternatively, as abarnert pointed out in the comments you can do a trick with equality comparisons to assure that you never get conflicts between KEYDOWN and KEYUP events:
paused = event.type == KEYDOWN
This way you won't have "syncing problems" where the code accidentally sets paused to True whenever you actually release the spacebar. This can happen if 2 KEYDOWN events happen in a row or if 2 KEYUP events happen in a row (instead of a smooth alternating sequence like KEYDOWN, KEYUP, KEYDOWN, KEYUP, etc.). It's best to not assume that all event queues feed events that are 100 percent accurate.

Categories