Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MultiThreading with pygame, program crashing

Hi everyone and thank you in advance for your help. I've just discovered pygame (a library for python) and I wanted to play a bit with it but I'm facing a problem. I tried to use threads in my code but the program keeps crashing each time I launch it.

I have isolated the problem and I know that it is thread_1 that causes the crash because when I comment it out everything works again. I have tried to change the code of the function of thread_1, but it still crashes. I'm pretty sure it's not the content of the function animateTitle that causes the crash but the way I'm using threads.

import pygame

from pygame.locals import *

from threading import Thread


def encadre(screen):  
    pygame.draw.line(screen, (250, 250, 250), (230, 140), (520, 140), 3)
    pygame.draw.line(screen, (250, 250, 250), (230, 190), (520, 190), 3)
    pygame.draw.line(screen, (250, 250, 250), (230, 140), (230, 190), 3)
    pygame.draw.line(screen, (250, 250, 250), (520, 140), (520, 190), 3)


def initRoad(screen):  
    pygame.draw.line(screen, (250, 250, 250), (30, 0), (30, 500))
    pygame.draw.line(screen, (250, 250, 250), (100, 0), (100, 500))
    pygame.draw.line(screen, (250, 250, 250), (650, 0), (650, 500))
    pygame.draw.line(screen, (250, 250, 250), (720, 0), (720, 500))
    drawLines(screen)


def drawLines(screen): 
    i = 0
    while i <= 49:
        pygame.draw.line(screen, (250, 250, 250), (65, i * 10), (65, (i + 1) * 10))
        pygame.draw.line(screen, (250, 250, 250), (685, i * 10), (685, (i + 1) * 10))
        i = i + 3


def initText(screen, text1):  
    text1pos = text1.get_rect()
    text1pos.x = 235
    text1pos.y = 150
    screen.blit(text1, text1pos)

    font1 = pygame.font.Font(None, 30)
    text1 = font1.render("PLAY", 1, (10, 10, 10))
    text1pos = text1.get_rect()
    text1pos.x = 210
    text1pos.y = 310
    screen.blit(text1, text1pos)

    font1 = pygame.font.Font(None, 30)
    text1 = font1.render("QUIT", 1, (10, 10, 10))
    text1pos = text1.get_rect()
    text1pos.x = 490
    text1pos.y = 310
    screen.blit(text1, text1pos)


def animateRoad(screen):  # not done
    pygame.draw.line(screen, (130, 130, 130), (65, 0), (65, 500))
    pygame.draw.line(screen, (130, 130, 130), (685, 0), (685, 500))


def animateTitle(screen, text1): 
    text1pos = text1.get_rect()
    while True:
        pygame.draw.rect(screen, (130, 130, 130), (235, 150, 283, 35))
        pygame.display.flip()
        pygame.time.wait(500)
        text1pos.x = 235
        text1pos.y = 150
        screen.blit(text1, text1pos)
        pygame.display.flip()
        pygame.time.wait(1000)


def loop(surface1, surface2):
    while True:
        for event in pygame.event.get():

            if event.type == QUIT:
                return

            if event.type == pygame.MOUSEBUTTONDOWN:
                if surface1.topleft[0] <= pygame.mouse.get_pos()[0] <= surface1.topright[0]:
                    if surface1.topleft[1] <= pygame.mouse.get_pos()[1] <= surface1.bottomleft[1]:
                    print('play')

                if surface2.topleft[0] <= pygame.mouse.get_pos()[0] <= surface2.topright[0]:
                    if surface2.topleft[1] <= pygame.mouse.get_pos()[1] <= surface2.bottomleft[1]:
                    return

    pygame.display.flip()
    pygame.time.wait(10)


def main():
    pygame.init()
    screen = pygame.display.set_mode((750, 500))
    pygame.display.set_caption('Infinite circle run')

    background = pygame.Surface(screen.get_size())  
    background = background.convert()
    background.fill((130, 130, 130))
    screen.blit(background, (0, 0))

    encadre(screen)
    initRoad(screen)

    surface1 = pygame.Rect(193, 290, 85, 50) 
    button1 = pygame.draw.rect(screen, (0, 0, 240), surface1)
    surface2 = pygame.Rect(472, 290, 85, 50)  
    button2 = pygame.draw.rect(screen, (240, 0, 0), surface2)

    font1 = pygame.font.Font(None, 50)
    text1 = font1.render("Infinite circle run", 1, (0, 240, 0))
    initText(screen, text1)  

    pygame.display.flip()

    thread_1 = Thread(target=animateTitle(screen, text1), daemon=True)
    thread_2 = Thread(target=loop(surface1, surface2))
    # thread_3 = Thread(target=animateRoad(screen))

    thread_1.start()
    thread_2.start()
    # thread_3.start()


if __name__ == '__main__':
    main()
like image 874
Riushda Avatar asked Apr 28 '26 13:04

Riushda


1 Answers

Threading causes many problems and a general rule of thumb is to avoid them if they aren't necessary. They make your program non-deterministic, harder to debug, harder to test, harder to maintain and slower (most of the time). In your program, there are no reasons to use threads. Instead, you should do things sequentially. Pygame will implicitly create threads whenever necessary (for example when dealing with pygame.mixer)

Why it doesn't work, is because pygame expects all event handling to happen in the thread that set the video mode (pygame is using SDL2, hence that link). You cannot handle them in another thread, and as you don't handle them (correctly), the operating system will think your program has crashed.


I made an example to show one way you could do the animation. The concept is that you tell pygame that after a certain amount of time, you want an event to be posted. When that event appears in your event loop, you do something.

In your case, you tell pygame to post DRAW_TEXT_EVENT after 500 ms. When this event appears in your event loop, you first tell pygame to don't post DRAW_TEXT_EVENT anymore, but to post CLEAR_TEXT_EVENT after 1000ms. Then you draw the text.

After 1000ms, the CLEAR_TEXT_EVENT will appear in your event loop. Now you do basically the same thing, but disable CLEAR_TEXT_EVENT and tell pygame to post DRAW_TEXT_EVENT after 500ms.

I haven't changed much in your code. I added 2 definitions for the events at the top. I've removed your functions loop and animateTitle, and put them into the game loop. Lastly, I've added the game loop in the main function.

import pygame
from pygame.locals import *

# Events that we're going to post.
DRAW_TEXT_EVENT  = pygame.USEREVENT + 1
CLEAR_TEXT_EVENT = pygame.USEREVENT + 2


def encadre(screen):
    pygame.draw.line(screen, (250, 250, 250), (230, 140), (520, 140), 3)
    pygame.draw.line(screen, (250, 250, 250), (230, 190), (520, 190), 3)
    pygame.draw.line(screen, (250, 250, 250), (230, 140), (230, 190), 3)
    pygame.draw.line(screen, (250, 250, 250), (520, 140), (520, 190), 3)


def initRoad(screen):
    pygame.draw.line(screen, (250, 250, 250), (30, 0), (30, 500))
    pygame.draw.line(screen, (250, 250, 250), (100, 0), (100, 500))
    pygame.draw.line(screen, (250, 250, 250), (650, 0), (650, 500))
    pygame.draw.line(screen, (250, 250, 250), (720, 0), (720, 500))
    drawLines(screen)


def drawLines(screen):
    i = 0
    while i <= 49:
        pygame.draw.line(screen, (250, 250, 250), (65, i * 10), (65, (i + 1) * 10))
        pygame.draw.line(screen, (250, 250, 250), (685, i * 10), (685, (i + 1) * 10))
        i = i + 3


def initText(screen, text1):
    text1pos = text1.get_rect()
    text1pos.x = 235
    text1pos.y = 150
    screen.blit(text1, text1pos)

    font1 = pygame.font.Font(None, 30)
    text1 = font1.render("PLAY", 1, (10, 10, 10))
    text1pos = text1.get_rect()
    text1pos.x = 210
    text1pos.y = 310
    screen.blit(text1, text1pos)

    font1 = pygame.font.Font(None, 30)
    text1 = font1.render("QUIT", 1, (10, 10, 10))
    text1pos = text1.get_rect()
    text1pos.x = 490
    text1pos.y = 310
    screen.blit(text1, text1pos)


def animateRoad(screen):  # not done
    pygame.draw.line(screen, (130, 130, 130), (65, 0), (65, 500))
    pygame.draw.line(screen, (130, 130, 130), (685, 0), (685, 500))



def main():
    pygame.init()
    screen = pygame.display.set_mode((750, 500))
    pygame.display.set_caption('Infinite circle run')

    background = pygame.Surface(screen.get_size())
    background = background.convert()
    background.fill((130, 130, 130))
    screen.blit(background, (0, 0))

    encadre(screen)
    initRoad(screen)

    surface1 = pygame.Rect(193, 290, 85, 50)
    button1 = pygame.draw.rect(screen, (0, 0, 240), surface1)
    surface2 = pygame.Rect(472, 290, 85, 50)
    button2 = pygame.draw.rect(screen, (240, 0, 0), surface2)

    font1 = pygame.font.Font(None, 50)
    text1 = font1.render("Infinite circle run", 1, (0, 240, 0))
    initText(screen, text1)

    pygame.display.flip()

    pygame.time.set_timer(DRAW_TEXT_EVENT, 500)

    text1pos = text1.get_rect()

    # GAME LOOP
    while True:

        for event in pygame.event.get():
            if event.type == QUIT:
                return
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if surface1.topleft[0] <= pygame.mouse.get_pos()[0] <= surface1.topright[0]:
                    if surface1.topleft[1] <= pygame.mouse.get_pos()[1] <= surface1.bottomleft[1]:
                        print('play')
                elif surface2.topleft[0] <= pygame.mouse.get_pos()[0] <= surface2.topright[0]:
                    if surface2.topleft[1] <= pygame.mouse.get_pos()[1] <= surface2.bottomleft[1]:
                        return

            elif event.type == DRAW_TEXT_EVENT:
                pygame.draw.rect(screen, (130, 130, 130), (235, 150, 283, 35))
                pygame.time.set_timer(DRAW_TEXT_EVENT,  0)     # Disable the event.
                pygame.time.set_timer(CLEAR_TEXT_EVENT, 1000)  # Post event after 1000ms.
            elif event.type == CLEAR_TEXT_EVENT:
                text1pos.x = 235
                text1pos.y = 150
                screen.blit(text1, text1pos)
                pygame.time.set_timer(CLEAR_TEXT_EVENT, 0)    # Disable the event.
                pygame.time.set_timer(DRAW_TEXT_EVENT,  500)  # Post event after 500ms.

        # Only call once each frame!
        pygame.display.flip()


if __name__ == '__main__':
    main()
like image 186
Ted Klein Bergman Avatar answered Apr 30 '26 01:04

Ted Klein Bergman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!