Multithreading with Pygame

11,600

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
Share:
11,600
Javier
Author by

Javier

Updated on July 01, 2022

Comments

  • Javier
    Javier almost 2 years

    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?

  • Javier
    Javier over 12 years
    This worked like a charm. I was thinking of doing something along these lines, but I couldn't figure out how to make a function pause without reimplementing the entire threading module. I hadn't tought of using iterators. Thanks!
  • The-IT
    The-IT almost 11 years
    You should NOT use time.sleep. You need to create a clock in pygame: clock = pygame.time.Clock() and then use the clock.tick() method. Read more here: pygame.org/docs/ref/time.html
  • martineau
    martineau over 7 years
    @Javier: Adding the yield to draw() effectively makes it a Coroutine. See PEP 342.