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, anddraw()
to render the current state of the object. In every iteration of your main loop, all objects are updated, then all are drawn. Note thatdraw()
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 yourdraw()
function withyield
. 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 callingdraw()
and initialize the time when the next draw should occur:Then, in the main loop, after handling user input, check if it's time to draw:
If so, run the next iteration and schedule the next draw time: