I just built an example of this for my own project!
I'm not sure what caused yours to freeze, though I know for mine the OS kept trying to force it to close until I started actually processing the events from the event queue - apparently when the queue filled up the OS thought my thread was broken.
Here's the basic idea below, though you can find my slightly more detailed example, with more comments and with example instances to use, Here on GitHub.
import threading
import pygame
class ThreadedRenderer(threading.Thread):
def __init__(self, instances=[], size=(128, 128), fps=30, event_functions={}):
threading.Thread.__init__(self) # Run the Thread parent init
# Set up program structure
self.instances = instances
self.fps = fps
self.events = event_functions
self.running = True
# Set up Pygame
pygame.init()
self.screen = pygame.display.set_mode(size)
self.clock = pygame.time.Clock()
# Start the thread, which starts the loop
self.start()
def _step(self):
""" Update the program objects, called each frame."""
for inst in self.instances:
inst.update()
def _draw(self):
""" Redraw the screen, called each frame."""
self.screen.fill((0, 0, 0))
for inst in self.instances:
inst.draw(self.screen)
pygame.display.flip()
def run(self):
""" The function that is Threaded. DO NOT call this function."""
while self.running is True:
self.clock.tick(self.fps)
for event in pygame.event.get():
if event.type in self.events:
self.events[event.type](event)
elif event.type == pygame.QUIT: # May be overridden
self.running = False
self._step()
self._draw()
# Below will be executed whenever the thread quits gracefully or kill is called
pygame.quit()
ThreadedRenderer.instantiated = False
def kill(self):
self.running = False