Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multithreading with Pygame

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?

like image 344
Javier Avatar asked Dec 27 '22 15:12

Javier


1 Answers

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
like image 159
Ferdinand Beyer Avatar answered Jan 03 '23 06:01

Ferdinand Beyer