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, 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
Related
So I was making a game in python with pygame and I had some assets as characters. I coded everything correctly. But when I run the program none of the images show up and the window crashes immediately.
import pygame
import os
import random
WIDTH, HEIGHT = 900, 500
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Fly game")
FPS = 60
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
FPS = 60
VEL = 5
BORDER = pygame.Rect(WIDTH//2 - 5, 0, 10, HEIGHT)
PLAYER_WIDTH, PLAYER_HEIGHT = 55, 40
SKY = pygame.image.load(
os.path.join('C:\\Users\\Kostadin Klemov\\Desktop\\Programms\\Python\\projects\\Fly game\\Assets\\SKY.jpg')), (WIDTH, HEIGHT)
JETPACK_MAN_IMAGE = pygame.image.load(
os.path.join('C:\\Users\\Kostadin Klemov\\Desktop\\Programms\\Python\\projects\\Fly game\\Assets\\JETPACK_MAN.jpg'))
JETPACK_MAN = pygame.transform.scale(
JETPACK_MAN_IMAGE, (PLAYER_WIDTH, PLAYER_HEIGHT))
FLY_IMAGE = pygame.image.load(
os.path.join('C:\\Users\\Kostadin Klemov\\Desktop\\Programms\\Python\\projects\\Fly game\\Assets\\FLY.png'))
FLY = pygame.transform.scale(
FLY_IMAGE, (PLAYER_WIDTH, PLAYER_HEIGHT))
def draw_window(jetpack, fly):
WIN.blit(SKY, (0, 0))
pygame.draw.rect(WIN, BLACK, BORDER)
WIN.blit(JETPACK_MAN, (jetpack.x, jetpack.y))
WIN.blit(FLY, (fly.x, fly.y))
pygame.display.update()
def main():
jetpack = pygame.Rect(225, 250, PLAYER_WIDTH, PLAYER_HEIGHT)
fly = pygame.Rect(675, 250, PLAYER_WIDTH, PLAYER_HEIGHT)
clock = pygame.time.Clock()
run = True
while run:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
draw_window(jetpack, fly)
main()
if __name__ == "__main__":
main
No error showed up so I didn't know what was wrong.
If you can, please check out the code and try to fix it!
This is an indentation error.
while run:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
# <---- INDENTATION
# this should be in the while loop, the "game loop", not in the quit condition
draw_window(jetpack, fly)
Also, you probably don't want to call main() after you program terminates. (Assuming you want people to be able to exit your game easily.
if __name__ == "__main__":
main
This main does nothing, it needs to be called, like main(). With the parentheses. It's not "crashing instantly," it's just not running anything.
SKY = pygame.image.load(
os.path.join('C:\\Users\\Kostadin Klemov\\Desktop\\Programms\\Python\\projects\\Fly game\\Assets\\SKY.jpg')), (WIDTH, HEIGHT)
That (WIDTH, HEIGHT) at the end is very suspicious. Presumably you just want the image put in the SKY variable, not another random tuple.
On another note, os.path.join() does nothing if you give it the full path as an argument.
i have a code that pops up a speechbubble when the player hits a rectangle. But now its just spamming the speech bubble. Thats not good because i have random texts that appears so it's just going through it very fast
this is the code for the speech bubble
def draw_speech_bubble(screen, text, text_color, bg_color, pos, size):
font = pygame.font.SysFont(None, size)
text_surface = font.render(text, True, text_color)
text_rect = text_surface.get_rect(midbottom=pos)
#Background Object
bg_rect = text_rect.copy()
bg_rect.inflate_ip(10,10)
#Border
border_rect = bg_rect.copy()
border_rect.inflate_ip(4,4)
pygame.draw.rect(screen, text_color, border_rect)
pygame.draw.rect(screen, bg_color, bg_rect)
screen.blit(text_surface, text_rect)
This is my code for the collision
if(player.player_rect.colliderect(BarCounter)):
draw_speech_bubble(screen, str(RandomText()), (255, 255, 255), (0, 0,0),SpeechBubbleAnchor.midtop,25)
I want some sort of cooldown so it doesn't spam the speechbubbles. I tried making a calculation with ticks but i wasn't able to do that
Add a variable that stores the text (current_bubble_text) and change the text when the player hits the rectangle. Use pygame.time.get_ticks() to get the number of milliseconds since pygame.init() was called. Calculate the point in time at which the text has to disappear again. When the time comes, reset the variable:
current_bubble_text = None
bubble_text_end_time = 0
#application loop
while True:
current_time = pygame.time.get_ticks()
# [...]
if current_bubble_text == None and player.player_rect.colliderect(BarCounter):
current_bubble_text = RandomText()
bubble_text_end_time = current_time + 5000 # 1 second time interval
if current_bubble_text:
draw_speech_bubble(screen, current_bubble_text,
(255, 255, 255), (0, 0, 0), SpeechBubbleAnchor.midtop, 25)
if current_time > bubble_text_end_time:
current_bubble_text = None
# [...]
See also Adding a particle effect to my clicker game.
So I'm trying to make tetris in pygame, and it was running really slow. I isolated the function that draws everything and even that is running really slow, how can I fix this? (And by running I mean I get a black screen and the desired images just load slowly from the bottom over the course of several minutes.)
P.S. There are some unused variables because I was too lazy to comment them out, but I don't imagine a couple variables make a big difference
import random
import time
import pygame
pygame.init()
win=pygame.display.set_mode((420, 840))
clock= pygame.time.Clock()
score= 0
level= 0
lines= 0
field= [[1]*10 for _ in range(22)]
run= True
play= True
setState= False
t= 48
frameCounter= 0
font= pygame.font.SysFont('arial', 30)
def drawWin():
tempY= 40
for y in range(2 ,22):
tempX= 10
for x in field[y]:
if(x==1):
win.fill((255, 255, 255), (tempX, tempY, 40, 40))
pygame.draw.rect(win, (0, 0, 255), (tempX, tempY, 40, 40), 5)
tempX+=40
tempY+=40
pygame.draw.rect(win, (255, 255, 255), (10, 40, 400, 800), 2)
text= font.render('Score: '+ str(score)+ '\nLevel: '+ str(level), 1, (255, 255, 255))
while True:
drawWin()
for event in pygame.event.get():
if event.type== pygame.QUIT:
break
time.sleep(1)
clock.tick(t)
pygame.quit()
Try adding pygame.display.update() to the end of your while loop
I've had a similar issue running Pygame before :))
Simple fix, forgot the update display command for pygame.
In my main loop I have:
clock.tick_busy_loop(60)
pygame.display.set_caption("fps: " + str(clock.get_fps()))
However, the readout says the game is reading at 62.5fps.
I then tried to input clock.tick_busy_loop(57.5), which gave me a readout of 58.82...fps. When I set clock.tick_busy_loop(59) I get 62.5fps again. It looks like there is a threshold between 58.8fps and 62.5fps that I can't overcome here. How do I get my game to actually run at 60fps? I'm mainly looking for this kind of control because I executing events that are contingent on musical timing.
So I whipped up a simple demo based on my comment above that uses the system time module instead of the pygame.time module. You can ignore the OpenGL stuff as I just wanted to render something simple on screen. The most important part is the timing code at the end of each frame, which I have commented about in the code.
import pygame
import sys
import time
from OpenGL.GL import *
from OpenGL.GLU import *
title = "FPS Timer Demo"
target_fps = 60
(width, height) = (300, 200)
flags = pygame.DOUBLEBUF|pygame.OPENGL
screen = pygame.display.set_mode((width, height), flags)
rotation = 0
square_size = 50
prev_time = time.time()
while True:
#Handle the events
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
#Do computations and render stuff on screen
rotation += 1
glClear(GL_COLOR_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, width, 0, height, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glTranslate(width/2.0, height/2.0, 0)
glRotate(rotation, 0, 0, 1)
glTranslate(-square_size/2.0, -square_size/2.0, 0)
glBegin(GL_QUADS)
glVertex(0, 0, 0)
glVertex(50, 0, 0)
glVertex(50, 50, 0)
glVertex(0, 50, 0)
glEnd()
pygame.display.flip()
#Timing code at the END!
curr_time = time.time()#so now we have time after processing
diff = curr_time - prev_time#frame took this much time to process and render
delay = max(1.0/target_fps - diff, 0)#if we finished early, wait the remaining time to desired fps, else wait 0 ms!
time.sleep(delay)
fps = 1.0/(delay + diff)#fps is based on total time ("processing" diff time + "wasted" delay time)
prev_time = curr_time
pygame.display.set_caption("{0}: {1:.2f}".format(title, fps))
I'm using pygame for interface display and controller input on a project I'm working on. Currently everything works sufficiently well so this isn't a major concern, but if it accidentally happens during actual use there might be some problems so I'd like to fix it possible.
When I run my code, the pygame window appears, displays, and updates exactly as expected. However, if I click on it or it gains focus some other way the window freezes and becomes (Not Responding). The code itself continues running, including the thread that is responsible for updating the display, until I close the window so Python itself is still running fine, but the window stops working.
My code for the update loop is here, in all its really ugly glory:
while(1):
print "thread is fine"
pygame.event.pump()
if LOV_Flag == 1:
videoColor = (255, 0, 0)
else:
videoColor = (0, 255, 0)
# Refresh the screen and redraw components
screen.fill((255, 255, 255))
pulseLabel = pulseFont.render("Pulse: ", 1, (0, 0, 0))
videoLabel = videoFont.render("Video: ", 1, (0, 0, 0))
motorsLabel = labelFont.render("Motors", 1, (0, 0, 0))
motorLabel = captionFont.render("L R", 1, (0, 0, 0))
armLabel = captionFont.render("Arm", 1, (0, 0, 0))
gripperLabel = captionFont.render("Gripper", 1, (0, 0, 0))
screen.blit(motorsLabel, (55, 50))
screen.blit(motorLabel, (60, 75))
screen.blit(pulseLabel, (300, 19))
screen.blit(videoLabel, (300, 45))
screen.blit(armLabel, (250, 75))
screen.blit(gripperLabel, (235, 255))
leftBar = (50, 200 - drive.getSpeed()[0], 25, drive.getSpeed()[0])
rightBar = (100, 200 - drive.getSpeed()[1], 25, drive.getSpeed()[1])
armBar = (250, 200 - (100 * arm.report()), 25, (100 * arm.report()))
upperArmBar = (250, 200 - (100 * arm.reportUp()), 25, 2) # 100 is the value on the bar
lowerArmBar = (250, 200 - (100 * arm.reportDown()), 25, 2) # 135 (65) is the value on the bar
gripperBar = (212, 225, (100 * hand.report()), 25)
leftGripperBar = (212 + (100 * hand.reportClosed()), 225, 2, 25)
rightGripperBar = (212 + (100 * hand.reportOpen()), 225, 2, 25)
pygame.draw.rect(screen, (255, 0, 0), leftBar, 0)
pygame.draw.rect(screen, (255, 0, 0), rightBar, 0)
pygame.draw.rect(screen, (255, 0, 0), armBar, 0)
pygame.draw.rect(screen, (0, 0, 0), upperArmBar, 0)
pygame.draw.rect(screen, (0, 0, 0), lowerArmBar, 0)
pygame.draw.rect(screen, (255, 0, 0), gripperBar, 0)
pygame.draw.rect(screen, (0, 0, 0), leftGripperBar, 0)
pygame.draw.rect(screen, (0, 0, 0), rightGripperBar, 0)
pygame.draw.circle(screen, pulseColor, [370, 32], 10)
pygame.draw.circle(screen, videoColor, [370, 58], 10)
pygame.display.update()
time.sleep(0.1)
if killFlag:
return
An image for how the window looks, in case that helps you understand the code better.
Unfortunately I can't run the code you've posted to test myself, but there a few things I'd like to point out that might help. Firstly, that's not the right way to kill a program, at least not the way I know. What I use in pygame is:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
Is the time.sleep(1) at the end of your loop to try to control framerate? If so, you'd be much better served by utilizing a pygame clock. initialize a clock before your main loop as follows:
clock = pygame.time.Clock()
Then within the main loop use this:
clock.tick(60)
What this does is set the program to run at your desired fps. In this case it's set to 60.
Lastly, i honestly don't know why your window would stop responding once it receives mouse input, but if that's indeed the culprit you could simply block mouse events. Try this before your while loop:
pygame.mouse.set_visible(False)
pygame.event.set_blocked(pygame.MOUSEMOTION)
pygame.event.set_blocked(pygame.MOUSEBUTTONDOWN)
pygame.event.set_blocked(pygame.MOUSEBUTTONUP)
This should disregard all input from your mouse, you won't even be able to see it over your window.
I moved this code to main instead of a thread, it works now. I did end up needing the pygame.event.pump() line, and I now use clock.tick() for rate control, but it's otherwise identical but moved. Apparently pygame just doesn't play well with threading.
Thanks for all the help though, the actual solution may have been discovered accidentally but having things to try kept me trying long enough to try the thing I didn't expect to work.
The event queue is probably filled up and no more events can be handled, and thus the OS says the window is not responding.
You should clear the event queue by calling pygame.event.clear() if you're not interested in any events, but maybe you want to least handle pygame.QUIT, so calling pygame.event.get() (without an argument) will also do the trick.
The problem is threads, I had the same problem, if I run pygame updates in a thread, it locks up when I change focus, if I run it without threads the same code works fine. The work around is to make sure 'pygame' is only called from your main original thread, and then other code can be in other threads, but anything related to pygame should all come from the main thread. That worked for me anyway.