pygame: how do I get my game to actually run at 60fps? - python

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))

Related

Trouble making a Simple video renderer with the python module Pygame

I need some help creating a video renderer in Pygame. It has a 48x36 display and has 3 colours. Black, white and grey. The data that goes into the project is in the form of a .txt file and each frame of the video in a line(as an example my video uses 6567 frames so there are 6567 lines in the text file). To render the frames I am using the rectangle function.
pygame.draw.rect(canvas, rect_color, pygame.Rect(30,30,60,60))
(By the way in the .txt file 0=black, 1=white and 2=grey)
After finally making a window pop up it stayed a rather boring black color...
After finally making a window pop up it stayed a rather boring black color...
If you could give any help it would be greatly needed!
(The code is messy i know)
import time
from datetime import datetime
import pygame
file= open(r"C:\Users\User\Downloads\video Data.txt","r")
lines = file.readlines()
current_l = 1
start_time = time.perf_counter()
pygame.init()
surface = pygame.display.set_mode((480,360))
color = (150,75,75)
def start_vid():
current_l = 1
for frame in range(1, 6572):
xpos = 0
ypos = 0
now = datetime.now()
count = 0
seconds = now.second
frame_data = lines[current_l]
current = frame_data[count]
for y in range(0, 36):
for x in range(0, 48):
if current == '0':
pygame.draw.rect(surface, (0, 0, 255),[xpos, xpos+10, ypos, ypos+10], 0)
elif current == '1':
pygame.draw.rect(surface, (255, 255, 255),[xpos, ypos, xpos, ypos], 0)
else:
pygame.draw.rect(surface, (130, 130, 130),[xpos, ypos, xpos, ypos], 0)
#print(current)
#pygame.display.update()
xpos = xpos + 10
current = frame_data[count]
count = count + 1
timer = round(abs((start_time - time.perf_counter())), 1)
current_l = seconds*30
current_l = int(timer*30)
ypos = ypos + -10
print(current_l)
pygame.display.update()
start_vid()
The reason why you can't see anything is because in Pygame, the origin (0, 0) of the screen is in the top left. The right side of the screen is where the x increases, and the bottom is where the y increases. So, the line:
ypos = ypos + -10
draws everything "above" the screen, hence invisible. You need to remove the - sign.
I also saw a couple of things in your code that could be improved, such as:
The fact that you never close the data file. To do that automatically, you could use the with statement:
with open('video.txt') as file:
lines = file.readlines()
# the file is closed
This will allow other applications to access the file.
You are drawing empty rects
You are using the pygame.draw.rect method incorrectly. You should use rect objects like such:
rect = pygame.Rect(x, y, width, height)
pygame.draw.rect(surface, color, rect)
You don't need the time and datetime modules, Pygame handles time like this. For example:
framerate = 30
start = pygame.time.get_ticks()
... # main loop
current_frame = int((pygame.time.get_ticks()-start) / 1000 * framerate)
And, most importantly
In Pygame, you need a main loop with events handling:
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
...
pygame.quit()
quit()
A correct implementation of your code could look like this:
import pygame
with open('video.txt') as file:
lines = file.readlines()
pygame.init()
surface = pygame.display.set_mode((480,360))
framerate = 30
start = pygame.time.get_ticks()
run = True
while run:
# get pygame events
for event in pygame.event.get():
if event.type == pygame.QUIT: # user closes the window
run = False
current_frame = int((pygame.time.get_ticks()-start) / 1000 * framerate)
if current_frame < len(lines): # is the video still running?
count = 0
for y in range(36):
for x in range(48):
current_pixel = lines[current_frame][count] # current pixel data
if current_pixel == '0':
color = (0, 0, 0)
elif current_pixel == '1':
color = (127, 127, 127)
else:
color = (255, 255, 255)
pygame.draw.rect(surface, color, pygame.Rect(x*10, y*10, 10, 10))
count += 1 # next pixel
pygame.display.flip()
# user closed the window
pygame.quit()
quit()
(In this example, you can reduce the amount of resources used since you know the framerate of the video using pygame.time.Clock)

Using kinect with python on a raspberry pi 3, is there a better/faster way for more fps?

I recently got a kinect camera to work on a raspberry pi 3 using libfreenect and pygame following this guy's work and I'm getting around 13fps but I was wondering if theres a better/faster way to get more fps or is it just a hardware limitation?
Here is the code right now:
from freenect import sync_get_depth as get_depth
import pygame
import numpy
import sys
def make_gamma():
num_pix = 2048 # there's 2048 different possible depth values
npf = float(num_pix)
_gamma = numpy.empty((num_pix, 3), dtype=numpy.uint16)
for i in xrange(num_pix):
if i < 950:
a = numpy.array([255, 0, 0], dtype=numpy.uint8)
else:
a = numpy.array([0, 0, 0], dtype=numpy.uint8)
_gamma[i] = a
return _gamma
fpsClock = pygame.time.Clock()
FPS = 30 # kinect only outputs 30 fps
disp_size = (640, 480)
pygame.init()
screen = pygame.display.set_mode(disp_size)
pygame.display.set_caption('Kinect depth display')
font = pygame.font.Font('/usr/share/fonts/truetype/freefont/FreeSerif.ttf', 32)
gamma = make_gamma()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
fps_text = "FPS: {0:.2f}".format(fpsClock.get_fps())
# draw the pixels
depth = numpy.rot90(get_depth()[0])
pixels = gamma[depth]
temp_surface = pygame.Surface(disp_size)
pygame.surfarray.blit_array(temp_surface, pixels)
pygame.transform.scale(temp_surface, disp_size, screen)
screen.blit(font.render(fps_text, 1, (255, 255, 255)), (30, 30))
pygame.display.flip()
fpsClock.tick(FPS)
You can use the C/C++ native code to read frames and stream them via system pipe. You will need to encode depth data into hexadecimal values and print them to console.

Python/Pygame - Create "End credits" like the ones at the end of a movie

I'm trying to create "end credits" like the ones at the end of a movie, using pygame. I've googled for other ways to achieve this using python, but I haven't found any yet.
I've almost achieved this with the following code: http://pastebin.com/nyjxeDYQ
#!/usr/bin/python
import time
import threading
import pygame
from pygame.locals import *
# Initialise pygame + other settings
pygame.init()
pygame.fastevent.init()
event_get = pygame.fastevent.get
pygame.display.set_caption('End credits')
screen = pygame.display.set_mode((1920, 1080))
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((255, 255, 255))
fontsize = 40
font = pygame.font.SysFont("Arial", fontsize)
x = 0
def main():
global x
credit_list = ["CREDITS - The Departed"," ","Leonardo DiCaprio - Billy","Matt Damon - Colin Sullivan", "Jack Nicholson - Frank Costello", "Mark Wahlberg - Dignam", "Martin Sheen - Queenan"]
going = True
while going:
events = event_get()
for e in events:
if e.type in [QUIT]:
going = False
if e.type in [KEYDOWN] and e.key == pygame.K_ESCAPE:
going = False
# Loop that creates the end credits
ypos = screen.get_height()
while ypos > (0 - len(credit_list)*50) and x == 0: # Loop through pixel by pixel, screenheight + height of all the textlines combined
drawText(credit_list,ypos)
ypos = ypos - 1
x = 1
pygame.quit()
def drawText(text,y):
for line in text:
text = font.render(line, 1, (10, 10, 10))
textpos = text.get_rect()
textpos.centerx = background.get_rect().centerx
background.blit(text, (textpos.x,y))
y = y + 45
# Blit all the text
screen.blit(background, (0, 0))
pygame.display.flip()
time.sleep(0.0001) # Sleep function to adjust speed of the end credits
# Blit white background (else all the text will stay visible)
background.fill((255, 255, 255))
screen.blit(background, (0, 0))
pygame.display.flip()
if __name__ == '__main__': main()
The problem is that the scrolling text is flickering. This is because I use a time.sleep()-function to control the speed of the scrolling. When I use a value like 0.04 sec, it works pretty well, but the text moves too slow and there is still a bit of flickering. When I use a much lower value, like: 0.001 sec, the text is moving at a speed that I like, but there is a lot more flickering going on.
There is another value I can use to adjust the speed of the scrolling: the number of pixels to move. But when I set this to anything higher than 1, the scrolling isn't smooth anymore.
Does anyone know a solution to this problem? I don't necessarily have to use pygame, I do have to use python though.
Many thanks in advance!
Albrecht
Here are some simpe rule you should follow that will help you with your problem:
Don't call pygame.display.flip() more than once per frame
Don't use time.sleep() to control the speed of something in your application
Use a Clock to control the framerate
Here's a cleaned up, minimal working example:
#!/usr/bin/python
import pygame
from pygame.locals import *
pygame.init()
pygame.display.set_caption('End credits')
screen = pygame.display.set_mode((800, 600))
screen_r = screen.get_rect()
font = pygame.font.SysFont("Arial", 40)
clock = pygame.time.Clock()
def main():
credit_list = ["CREDITS - The Departed"," ","Leonardo DiCaprio - Billy","Matt Damon - Colin Sullivan", "Jack Nicholson - Frank Costello", "Mark Wahlberg - Dignam", "Martin Sheen - Queenan"]
texts = []
# we render the text once, since it's easier to work with surfaces
# also, font rendering is a performance killer
for i, line in enumerate(credit_list):
s = font.render(line, 1, (10, 10, 10))
# we also create a Rect for each Surface.
# whenever you use rects with surfaces, it may be a good idea to use sprites instead
# we give each rect the correct starting position
r = s.get_rect(centerx=screen_r.centerx, y=screen_r.bottom + i * 45)
texts.append((r, s))
while True:
for e in pygame.event.get():
if e.type == QUIT or e.type == KEYDOWN and e.key == pygame.K_ESCAPE:
return
screen.fill((255, 255, 255))
for r, s in texts:
# now we just move each rect by one pixel each frame
r.move_ip(0, -1)
# and drawing is as simple as this
screen.blit(s, r)
# if all rects have left the screen, we exit
if not screen_r.collidelistall([r for (r, _) in texts]):
return
# only call this once so the screen does not flicker
pygame.display.flip()
# cap framerate at 60 FPS
clock.tick(60)
if __name__ == '__main__':
main()

How to cycle image sprite in pygame

I am looking to create characters in a game using pygame and python version 2.7.6. I have a simple python script which allows me to create a window and print an image to the screen. This works successfully, however it is simply a static image.
import pygame
class Game(object):
def main(self, screen):
#load first sprite to image for blit"
image = pygame.image.load('sprites/sprite1.png')
image2 = pygame.image.load('sprites/sprite2.png')
image3 = pygame.image.load('sprites/sprite3.png')
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
return
screen.fill((200, 200, 200))
screen.blit(image, (320, 240))
pygame.display.flip()
if __name__ == '__main__':
pygame.init()
screen = pygame.display.set_mode((640, 480))
Game().main(screen)
I am looking to create a more dynamic image by changing the first sprite with the second and so on for at least three sprites possibly more (as sprite1.png, sprite2.png, sprite3.png). How can I cycle images after a given time interval has expired, for example: 1 second 1/2 seconds...
Lets go there from the top, when writing a game it's very important to keep the rendering loop away from the main loop of your program (the one that handles window events). This way even when something will get stuck in your rendering process, your window will be much less likely to become unresponsive. Among other benefits, for example people will be able to resize, and otherwise manipulate it, without interrupting the graphics thread.
There are some nice tutorials for it around the web, and while I don't have one at hand for python, SFML tutorials can teach you the right concepts which you can then use in python.
With that public service out of the way, when you already have the application split into separate threads, we are left with the issue of animations. Very simple implementation, and certainly not the best, would be to spawn a separate thread for each image that will cosist only of something like that:
def shake_it():
while True:
time.sleep(3)
#do rotation here
While this will work quite well, it is clearly not the best solution because it's very non-flexible. Imagine that now you would also like to do something else periodically, for example make your NPCs patrol the fort. This will require another thread, and this would quickly escalate to quite a high number of threads (which isn't bad in itself, but not a desired effect).
So to the rescue would be a much more generic thread - event handler. A class that will control all your functions that need to be executed at specific intervals. And then, in that additional thread you would use for animating images, you would run an instance of event_handler in the loop instead, which would work as an abstract for actions like animation.
Slightly more detail on #Puciek's suggestion in the comments, one could use a loop such as
import time
for img in [image, image1, image2]:
screen.fill((200, 200, 200))
screen.blit(img, (320, 240))
time.sleep(1) # sleep 1 second before continuing
To cycle through sprite images you should just blit the corresponding images in the loop. A good approach is to place all the images of the sequence in one image and load it when the program starts. Then use subsurface() function to get the image for blitting, so you can bind the "window" of the subsurface within the whole image with some variable which is changing in the loop. A separate question is, how to control the frequency, there are few approaches, here is one simple example of sprite animation. It is the same sprite sequence with 8 states, which is cycling at three different FPS: initial - 12 fps (1/12 second pause), then 1/3 of initial fps = 4 fps (3/12 second pause) and 1/12 of initial fps = 1 fps (12/12 = 1 second pause). I post the whole script, so you can run and see how it works, just put it with the image in one place and run the script.
Use this image with the script:
Source code:
import pygame, os
from pygame.locals import *
def Cycle(n, N) : # n - current state, N - max number of states
value = n+1
case1 = (value >= N)
case2 = (value < N)
result = case1 * 0 + case2 * value
return result
w = 600 # window size
h = 400
cdir = os.path.curdir # current directory in terminal
states = 8 # number of sprite states
pygame.init()
DISP = pygame.display.set_mode((w, h))
BG = (36, 0, 36) # background color
DISP.fill(BG)
band_1 = pygame.image.load(os.path.join(cdir, "band1.png")).convert()
K = 4 # global scale factor
band_1 = pygame.transform.scale(band_1, (K * band_1.get_width(), K * band_1.get_height()))
band_1.set_colorkey((0,0,0))
sw = K*8 # sprite width
sh = K*8 # sprite height
r0 = (100, 100, sw, sh) # sprite 0 rectangle
r1 = (150, 100, sw, sh) # sprite 1 rectangle
r2 = (200, 100, sw, sh) # sprite 2 rectangle
s0 = 0 # initial state of sprite 0
s1 = 0 # initial state of sprite 1
s2 = 0 # initial state of sprite 2
t0 = 1 # main ticker
t1 = 1 # ticker 1
t2 = 1 # ticker 2
Loop_1 = True
FPS = 12
clock = pygame.time.Clock( )
while Loop_1 :
clock.tick(FPS)
DISP.fill(BG, r0)
sprite_0 = band_1.subsurface((s0*sw, 0, sw, sh))
DISP.blit(sprite_0, r0)
s0 = Cycle(s0, states)
if t1 == 3 :
DISP.fill(BG, r1)
sprite_1 = band_1.subsurface((s1*sw, 0, sw, sh))
DISP.blit(sprite_1, r1)
s1 = Cycle(s1, states)
t1 = 0
if t2 == 12 :
DISP.fill(BG, r2)
sprite_2 = band_1.subsurface((s2*sw, 0, sw, sh))
DISP.blit(sprite_2, r2)
s2 = Cycle(s2, states)
t2 = 0
for event in pygame.event.get( ):
if event.type == QUIT : Loop_1 = False
if event.type == KEYDOWN:
if event.key == K_ESCAPE : Loop_1 = False
# t0 = t0 + 1
t1 = t1 + 1
t2 = t2 + 1
pygame.display.update()
pygame.quit( )

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?
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

Categories

Resources