I'm using pygame for interface display and controller input on a project I'm working on. Currently everything works sufficiently well so this isn't a major concern, but if it accidentally happens during actual use there might be some problems so I'd like to fix it possible.
When I run my code, the pygame window appears, displays, and updates exactly as expected. However, if I click on it or it gains focus some other way the window freezes and becomes (Not Responding). The code itself continues running, including the thread that is responsible for updating the display, until I close the window so Python itself is still running fine, but the window stops working.
My code for the update loop is here, in all its really ugly glory:
while(1):
print "thread is fine"
pygame.event.pump()
if LOV_Flag == 1:
videoColor = (255, 0, 0)
else:
videoColor = (0, 255, 0)
# Refresh the screen and redraw components
screen.fill((255, 255, 255))
pulseLabel = pulseFont.render("Pulse: ", 1, (0, 0, 0))
videoLabel = videoFont.render("Video: ", 1, (0, 0, 0))
motorsLabel = labelFont.render("Motors", 1, (0, 0, 0))
motorLabel = captionFont.render("L R", 1, (0, 0, 0))
armLabel = captionFont.render("Arm", 1, (0, 0, 0))
gripperLabel = captionFont.render("Gripper", 1, (0, 0, 0))
screen.blit(motorsLabel, (55, 50))
screen.blit(motorLabel, (60, 75))
screen.blit(pulseLabel, (300, 19))
screen.blit(videoLabel, (300, 45))
screen.blit(armLabel, (250, 75))
screen.blit(gripperLabel, (235, 255))
leftBar = (50, 200 - drive.getSpeed()[0], 25, drive.getSpeed()[0])
rightBar = (100, 200 - drive.getSpeed()[1], 25, drive.getSpeed()[1])
armBar = (250, 200 - (100 * arm.report()), 25, (100 * arm.report()))
upperArmBar = (250, 200 - (100 * arm.reportUp()), 25, 2) # 100 is the value on the bar
lowerArmBar = (250, 200 - (100 * arm.reportDown()), 25, 2) # 135 (65) is the value on the bar
gripperBar = (212, 225, (100 * hand.report()), 25)
leftGripperBar = (212 + (100 * hand.reportClosed()), 225, 2, 25)
rightGripperBar = (212 + (100 * hand.reportOpen()), 225, 2, 25)
pygame.draw.rect(screen, (255, 0, 0), leftBar, 0)
pygame.draw.rect(screen, (255, 0, 0), rightBar, 0)
pygame.draw.rect(screen, (255, 0, 0), armBar, 0)
pygame.draw.rect(screen, (0, 0, 0), upperArmBar, 0)
pygame.draw.rect(screen, (0, 0, 0), lowerArmBar, 0)
pygame.draw.rect(screen, (255, 0, 0), gripperBar, 0)
pygame.draw.rect(screen, (0, 0, 0), leftGripperBar, 0)
pygame.draw.rect(screen, (0, 0, 0), rightGripperBar, 0)
pygame.draw.circle(screen, pulseColor, [370, 32], 10)
pygame.draw.circle(screen, videoColor, [370, 58], 10)
pygame.display.update()
time.sleep(0.1)
if killFlag:
return
An image for how the window looks, in case that helps you understand the code better.
Unfortunately I can't run the code you've posted to test myself, but there a few things I'd like to point out that might help. Firstly, that's not the right way to kill a program, at least not the way I know. What I use in pygame is:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
Is the time.sleep(1) at the end of your loop to try to control framerate? If so, you'd be much better served by utilizing a pygame clock. initialize a clock before your main loop as follows:
clock = pygame.time.Clock()
Then within the main loop use this:
clock.tick(60)
What this does is set the program to run at your desired fps. In this case it's set to 60.
Lastly, i honestly don't know why your window would stop responding once it receives mouse input, but if that's indeed the culprit you could simply block mouse events. Try this before your while loop:
pygame.mouse.set_visible(False)
pygame.event.set_blocked(pygame.MOUSEMOTION)
pygame.event.set_blocked(pygame.MOUSEBUTTONDOWN)
pygame.event.set_blocked(pygame.MOUSEBUTTONUP)
This should disregard all input from your mouse, you won't even be able to see it over your window.
I moved this code to main instead of a thread, it works now. I did end up needing the pygame.event.pump() line, and I now use clock.tick() for rate control, but it's otherwise identical but moved. Apparently pygame just doesn't play well with threading.
Thanks for all the help though, the actual solution may have been discovered accidentally but having things to try kept me trying long enough to try the thing I didn't expect to work.
The event queue is probably filled up and no more events can be handled, and thus the OS says the window is not responding.
You should clear the event queue by calling pygame.event.clear() if you're not interested in any events, but maybe you want to least handle pygame.QUIT, so calling pygame.event.get() (without an argument) will also do the trick.
The problem is threads, I had the same problem, if I run pygame updates in a thread, it locks up when I change focus, if I run it without threads the same code works fine. The work around is to make sure 'pygame' is only called from your main original thread, and then other code can be in other threads, but anything related to pygame should all come from the main thread. That worked for me anyway.
Related
So I'm trying to make tetris in pygame, and it was running really slow. I isolated the function that draws everything and even that is running really slow, how can I fix this? (And by running I mean I get a black screen and the desired images just load slowly from the bottom over the course of several minutes.)
P.S. There are some unused variables because I was too lazy to comment them out, but I don't imagine a couple variables make a big difference
import random
import time
import pygame
pygame.init()
win=pygame.display.set_mode((420, 840))
clock= pygame.time.Clock()
score= 0
level= 0
lines= 0
field= [[1]*10 for _ in range(22)]
run= True
play= True
setState= False
t= 48
frameCounter= 0
font= pygame.font.SysFont('arial', 30)
def drawWin():
tempY= 40
for y in range(2 ,22):
tempX= 10
for x in field[y]:
if(x==1):
win.fill((255, 255, 255), (tempX, tempY, 40, 40))
pygame.draw.rect(win, (0, 0, 255), (tempX, tempY, 40, 40), 5)
tempX+=40
tempY+=40
pygame.draw.rect(win, (255, 255, 255), (10, 40, 400, 800), 2)
text= font.render('Score: '+ str(score)+ '\nLevel: '+ str(level), 1, (255, 255, 255))
while True:
drawWin()
for event in pygame.event.get():
if event.type== pygame.QUIT:
break
time.sleep(1)
clock.tick(t)
pygame.quit()
Try adding pygame.display.update() to the end of your while loop
I've had a similar issue running Pygame before :))
Simple fix, forgot the update display command for pygame.
Is it possible to add a purely python code to python, more specifically in the pygame module, so that it can take direct input from a Controller (XBox One, PS4, etc.)? I have attempted to use the following code with no luck;
win.onkey()
win.use(sys())
You can use the pygame.joystick module to get button presses from any controller connected to the computer. The id you pass into pygame.joystick.Joystick(id) is the order the controller was connected to the computer, starting at 0, for most cases you can use 0.
import pygame
pygame.init()
window = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Controller")
controller = pygame.joystick.Joystick(0)
done = False
while not done:
if controller.get_button(0): #A
pygame.draw.rect(window, (255, 0, 0), [0, 0, 800, 600])
if controller.get_button(1): #B
pygame.draw.rect(window, (0, 255, 0), [0, 0, 800, 600])
if controller.get_button(2): #X
pygame.draw.rect(window, (0, 0, 255), [0, 0, 800, 600])
if controller.get_button(3): #Y
pygame.draw.rect(window, (0, 0, 0), [0, 0, 800, 600])
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.display.update()
pygame.quit()
I don't know all of the ids for the buttons, so you'll have to experiment.
In addition to the previous answer, you don't need to experiment to find the buttons because the pygame.joystick documentation ( https://www.pygame.org/docs/ref/joystick.html ) includes mapping for various controllers.
So, I'm following a tutorial which currently has me building the following for a quick look at drawing using pygame functions.
import pygame, sys
from pygame.locals import *
pygame.init() #has to be called before any other pygame functions
#setup the window
DISPLAYSURF = pygame.display.set_mode((500,400), 0, 32)
pygame.display.set_caption('Drawing')
#colors
black = (0, 0, 0, 0)
white = (255, 255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
BLUE = (0, 0, 255)
#draw on the surface object
DISPLAYSURF.fill(white)
pygame.draw.polygon(DISPLAYSURF, green, ((146,0), (291,106), (236,277),
(56,277), (0,106))
pygame.draw.line(DISPLAYSURF, BLUE, (60, 60), (120, 60), 4)
pygame.draw.line(DISPLAYSURF, BLUE,(120,60), (60,120))
pygame.draw.line(DISPLAYSURF, BLUE,(60,120), (120,120), 4)
pygame.draw.circle(DISPLAYSURF, BLUE, (300,500), 20, 0)
pygame.draw.ellipse(DISPLAYSURF, red, (300, 250, 40, 80), 1)
pygame.draw.rect(DISPLAYSURF, red, (200,150,100,50))
pixObj = pygame.pixelarray(DISPLAYSURF)
pixObj[480][380] = black
pixObj[482][382] = black
pixObj[484][384] = black
pixObj[486][386] = black
pixObj[488][388] = black
del pixObj
#run game loop
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
pygame.display.update()
My problem is that in attempting to run this code, I get an invalid syntax error pointing to the first pygame.draw.line. If this is somehow a typo, I'll be kinda surprised, I retyped those lines several times to make sure they were correct, even double checked the docs to make sure that was still the valid syntax. Any ideas?
Focusing on that line:
pygame.draw.polygon(DISPLAYSURF, green, ((146,0), (291,106), (236,277),
(56,277), (0,106))
Let's make some changes to it, that take valid syntax to valid syntax.
pygame.draw.polygon(DISPLAYSURF, green, ((), (), (), (), ())
pygame.draw.polygon(DISPLAYSURF, green, ()
pygame.draw.polygon(
There are more open parentheses than closing.
If the following lines are correct, you should remove an open paren after "green".
I am using Python 3.4.0 and I have Mac OSX 10.9.2. I have the following code saved as sublimePygame in Sublime Text.
import pygame, sys
from pygame.locals import *
pygame.init()
#set up the window
DISPLAYSURF = pygame.display.set_mode((400, 300))
pygame.display.set_caption('Drawing')
# set up the colors
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = ( 0, 255, 0)
BLUE = ( 0, 0, 255)
# Draw on surface object
DISPLAYSURF.fill(WHITE)
pygame.draw.polygon(DISPLAYSURF, GREEN, ((146, 0), (291, 106), (236, 277), (56, 277), (0, 106)))
pygame.draw.line(DISPLAYSURF, BLUE, (60, 60), (120, 60), 4)
pygame.draw.line(DISPLAYSURF, BLUE, (120, 60), (60, 120))
pygame.draw.line(DISPLAYSURF, BLUE, (60, 120), (120, 120), 4)
pygame.draw.circle(DISPLAYSURF, BLUE, (300, 50), 20, 0)
pygame.draw.ellipse(DISPLAYSURF, RED, (300, 250, 40, 80), 1)
pygame.draw.rect(DISPLAYSURF, RED, (200, 150, 100, 50))
pixObj = pygame.PixelArray(DISPLAYSURF)
pixObj[480, 380] = BLACK
pixObj[482, 382] = BLACK
pixObj[48, 384] = BLACK
pixObj[486, 386] = BLACK
pixObj[488, 388] = BLACK
del pixObj
while True: # main game loop
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
pygame.display.update()
I ran the code in my terminal and the python window opened for a second and then closed.
I got this error in the terminal.
Traceback (most recent call last):
File "sublimePygame", line 29, in <module>
pixObj[480, 380] = BLACK
IndexError: invalid index
Segmentation fault: 11
I checked the pygame documentation and my code seemed ok. I googled the error and Segmentation Error 11 seems to be a bug in python but I read that it was fixed in Python 3.4.0.
Does Anyone know what went wrong?
Thanks in advance!
Edit: Marius found the bug in my program, however when I run it it opens a blank Python window and not what it was supposed top open. Does anyone know why this happened?
I can see a definite bug in your code, but I'm not sure if that's causing a segmentation fault, which is a more serious error.
The bug in your code is that you create a 400x300 window:
DISPLAYSURF = pygame.display.set_mode((400, 300))
And then try to set colour values for pixels that are outside the bounds of that window
pixObj[480, 380] = BLACK
Again, this might not be causing the segmentation fault.
Your blank python window problem is here:
while True: # main game loop
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
pygame.display.update()
pygame.display.update() will only be executed if QUIT is activated. You need to remove some of the indents so that it is outside the for loop, but within the while loop.
I'm having some trouble writing a simple Pygame application that uses threads. Please keep in mind that this is the first multithreaded code I've ever written.
Here's the situation. I'm writing a simple app that will draw some funky lines to the screen. My problem is that while I'm drawing the lines, the app can't handle input, so I can't (for example) close the window until the lines are finished. This is what my original code looked like:
import time
import pygame
from pygame.locals import *
SIZE = 800
def main():
screen = pygame.display.set_mode((SIZE, SIZE))
for interval in xrange(50, 1, -5):
screen.fill((0, 0, 0))
for i in xrange(0, SIZE, interval):
pygame.draw.aaline(screen, (255, 255, 255), (i+interval, 0), (0, SIZE-i))
pygame.draw.aaline(screen, (255, 255, 255), (i, 0), (SIZE, i+interval))
pygame.draw.aaline(screen, (255, 255, 255), (SIZE, i), (SIZE-i-interval, SIZE))
pygame.draw.aaline(screen, (255, 255, 255), (SIZE-i, SIZE), (0, SIZE-i-interval))
pygame.display.update()
time.sleep(0.03)
time.sleep(3)
while True:
for evt in pygame.event.get():
if evt.type == QUIT:
return
if __name__ == '__main__':
pygame.init()
main()
pygame.quit()
As you can see, the event loop is only run when the drawing is done, so until then the window close button is unresponsive. I thought that putting the drawing code into its own thread might help, so I changed the code to this:
import threading, time
import pygame
from pygame.locals import *
SIZE = 800
def draw():
screen = pygame.display.set_mode((SIZE, SIZE))
for interval in xrange(50, 1, -5):
screen.fill((0, 0, 0))
for i in xrange(0, SIZE, interval):
pygame.draw.aaline(screen, (255, 255, 255), (i+interval, 0), (0, SIZE-i))
pygame.draw.aaline(screen, (255, 255, 255), (i, 0), (SIZE, i+interval))
pygame.draw.aaline(screen, (255, 255, 255), (SIZE, i), (SIZE-i-interval, SIZE))
pygame.draw.aaline(screen, (255, 255, 255), (SIZE-i, SIZE), (0, SIZE-i-interval))
pygame.display.update()
time.sleep(0.03)
time.sleep(3)
def main():
threading.Thread(target=draw).start()
while True:
for evt in pygame.event.get():
if evt.type == QUIT:
return
if __name__ == '__main__':
pygame.init()
main()
pygame.quit()
But all I get is a black screen which doesn't respond to input either. What am I doing wrong here?
Although I have never used pygame, I doubt that you can (or should) call its API from different threads. All your drawing should be done in the main event loop.
I guess you have to change the way you are thinking for game development. Instead of using time.sleep() to pause the drawing, create an object that can be updated in regular intervals. Typically, this is done in two passes -- update() to advance the object state in time, and draw() to render the current state of the object. In every iteration of your main loop, all objects are updated, then all are drawn. Note that draw() should assume that the screen is blank and draw every line that is needed up to the current time.
In your simple case, you could get away with something simpler. Replace the time.sleep() in your draw() function with yield. This way you get an iterator that will yield the recommended amount of time to wait until the next iteration. Before the main loop, create an iterator by calling draw() and initialize the time when the next draw should occur:
draw_iterator = draw()
next_draw_time = 0 # Draw immediately
Then, in the main loop, after handling user input, check if it's time to draw:
current_time = time.time()
if current_time >= next_draw_time:
If so, run the next iteration and schedule the next draw time:
try:
timeout = next(draw_iterator)
except StopIteration:
# The drawing is finished, exit the main loop?
break
next_draw_time = current_time + timeout