Related
This question already has answers here:
Why is my pygame display not responding while waiting for input?
(1 answer)
How can I create a text input box with Pygame?
(5 answers)
Closed 5 months ago.
So basically what happened is, I am making a reminder app where it rings an alarm at a certain time, the problem is I'm having trouble displaying a time. I've used show text in this code to display the current time, but when I try to display an activity I'm not getting an error but it's not showing.
Code:
from pygame import mixer
from datetime import datetime
import pygame
from pygame.locals import *
pygame.init()
mixer.init()
screen = pygame.display.set_mode((500,500))
pygame.display.set_caption("App")
mixer.music.load("song.mp3")
mixer.music.set_volume(0.7)
global o
o=0
#FUNCTIONS
def show_text(msg, x, y, color, size):
fontobj= pygame.font.SysFont("freesans", size)
msgobj = fontobj.render(msg,False,color)
screen.blit(msgobj,(x, y))
def Session():
session_name = input("What do you want to call your session?")
starttime = input("What time do you want to start? (Example 600 = 6:00)")
endtime = input("What time do you want to end? (Example 700 = 7:00)")
o=1
pygame.display.update()
#VARIABLES
green = (0,255,0)
black = (0,0,0)
white = (255,255,255)
activites = ""
start_time = ""
end_time = ""
counter = 0
hours = 60
#END OF VARIABLES
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
exit()
if event.type == MOUSEBUTTONDOWN:
x, y = pygame.mouse.get_pos()
if x>239 and x<286 and y> 399 and y<446:
counter = 1
if counter == 1:
Session()
counter = 0
screen.fill(black)
#GETTING TIME
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
#END OF GETTING TIME
#GUI
show_text(str(current_time),220,10,green,20)
if o==1:
show_text(str(session_name), 180,200, white, 75)
show_text("Add Activity",220,375,green,20)
pygame.draw.rect(screen,green,(240,400,45,45))
pygame.draw.rect(screen,white,(260,403,5,40))
pygame.draw.rect(screen,white,(245,420,37,5))
#END OF GUI
pygame.display.update()
I Would appreciate a response on why it was wrong as well as a fixed code, thanks in advance!
I am looking to write a better game loop in Python using pygame.time.Clock(), and I understand the concept of keeping time and de-coupling rendering from the main game loop in order to better utilise the ticks. I also understand about passing time lag into the rendering so that it renders the correct amount of movement, but the only examples I've found are written in C# and although I first thought it fairly simple to convert to Python, it's not behaving.
I've figured out that Pygame's Clock() already works out the milliseconds between the last 2 calls to .tick, and I've tried to adapt the sample code I found, but I really need to see a working example written in Python. Here's what I've come up with so far:
FPS = 60
MS_PER_UPDATE = 1000 / FPS
lag = 0.0
clock = pygame.time.Clock()
running = True
# Do an initial tick so the loop has 2 previous ticks.
clock.tick(FPS)
while running:
clock.tick(FPS)
lag += clock.get_time()
user_input()
while lag >= MS_PER_UPDATE:
update()
lag -= MS_PER_UPDATE
render(lag / MS_PER_UPDATE)
I'm not sure if this is all worth it in Pygame, or if it's already taken care of in some of it's time functions already? My game runs slower on the laptop (expected) but I thought doing this might even out the FPS a bit between my main PC and laptop by de-coupling the rendering. Does anyone have experience doing these advanced game loops in Pygame? I just want it to be as good as it can be...
Just take the time it took to render the last frame (called delta time) and pass it to your game objects so they can decide what to do (e.g. move more or less).
Here's a super simple example:
import pygame
class Actor(pygame.sprite.Sprite):
def __init__(self, *args):
super().__init__(*args)
self.image = pygame.Surface((32, 32))
self.rect = pygame.display.get_surface().get_rect()
self.image.fill(pygame.Color('dodgerblue'))
def update(self, events, dt):
self.rect.move_ip((1 * dt / 5, 2 * dt / 5))
if self.rect.x > 500: self.rect.x = 0
if self.rect.y > 500: self.rect.y = 0
def main():
pygame.init()
screen = pygame.display.set_mode((500, 500))
sprites = pygame.sprite.Group()
Actor(sprites)
clock = pygame.time.Clock()
dt = 0
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
sprites.update(events, dt)
screen.fill((30, 30, 30))
sprites.draw(screen)
pygame.display.update()
dt = clock.tick(60)
if __name__ == '__main__':
main()
If your game slows down below 60 (or whatever) FPS, dt gets bigger, and Actor moves more to make up for the lost time.
I want to calculate the time of user's mouse events in Pygame, if user doesn't move his mouse about 15 seconds, then I want to display a text to the screen. I tried time module for that, but it's not working.
import pygame,time
pygame.init()
#codes
...
...
font = pygame.font.SysFont(None,25)
text = font.render("Move your mouse!", True, red)
FPS = 30
while True:
#codes
...
...
start = time.time()
cur = pygame.mouse.get_pos() #catching mouse event
end = time.time()
diff = end-start
if 15 < diff:
gameDisplay.blit(text,(10,500))
pygame.display.update()
clock.tick(FPS)
pygame.quit()
quit()
Well output is not what I want, I don't know how to calculate it if user doesn't move his mouse.
If I want to write a text when user's mouse in a special area, it's working like;
if 100 < cur[0] < 200 and 100 < cur[1] < 200:
gameDisplay.blit(text,(10,500))
But how can I calculate? I even couldn't find how to tell Python, user's mouse is on the same coordinates or not.Then I can say, if mouse coordinates changes, start the timer, and if it's bigger than 15, print the text.
Edit: You can assume it in normal Python without Pygame module, assume you have a function that catching the mouse events, then how to tell Python if coordinates of mouse doesn't change, start the timer, if the time is bigger than 15 seconds,print a text, then refresh the timer.
To display a text on the screen if there is no mouse movement within the pygame window for 3 seconds:
#!/usr/bin/python
import sys
import pygame
WHITE, RED = (255,255,255), (255,0,0)
pygame.init()
screen = pygame.display.set_mode((300,200))
pygame.display.set_caption('Warn on no movement')
font = pygame.font.SysFont(None, 25)
text = font.render("Move your mouse!", True, RED, WHITE)
clock = pygame.time.Clock()
timer = pygame.time.get_ticks
timeout = 3000 # milliseconds
deadline = timer() + timeout
while True:
now = timer()
if pygame.mouse.get_rel() != (0, 0): # mouse moved within the pygame screen
deadline = now + timeout # reset the deadline
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill(WHITE)
if now > deadline: # no movement for too long
screen.blit(text, (10, 50))
pygame.display.flip()
clock.tick(60) # set fps
You should add:
start = time.time()
cur = None
before while loop.
You should also change start = time.time() in while loop to:
if cur != pygame.mouse.get_pos():
start = time.time()
Also you could use pygame.time (it's similar to time but measure time in milliseconds)
In your code, the while True: code block is continuously running. The cur = pygame.mouse.get_pos() function is non blocking. This means it does not wait for mouse input - it will return straight away. So you need to initialize the start and cur variables before your while True: code block and then check the mouse position constantly in your loop.
If cur has changed since the last time the loop ran, then reset the start variable to the current time, and if the difference between the current time and start becomes larger than your 15 seconds, you can display the text.
You can also do that even without getting time, since you can calculate the pause as an integer counter through your FPS. Consider following example. Note that if the cursor is out of the window, the values of its positon will not change even if you move the cursor.
import pygame
pygame.init()
clock = pygame.time.Clock( )
DISP = pygame.display.set_mode((600, 400))
FPS = 25
Timeout = 15
Ticks = FPS*Timeout # your pause but as an integer value
count = 0 # counter
MC = pygame.mouse.get_pos()
MC_old = MC
MainLoop = True
while MainLoop :
clock.tick(FPS)
pygame.event.pump()
Keys = pygame.key.get_pressed()
if Keys[pygame.K_ESCAPE]:
MainLoop = False
MC = pygame.mouse.get_pos() # get mouse position
if (MC[0]-MC_old[0] == 0) and (MC[1]-MC_old[1] == 0) :
count = count + 1
else : count = 0
if count > Ticks :
print "What are you waiting for"
count = 0
MC_old = MC # save mouse position
pygame.display.flip( )
pygame.quit( )
I made a simple program with Pygame that was basically a scrolling background and noticed periodic lag spikes. After messing with the code for a long time, I found out that calls to pygame.display.update() would sometimes take a lot longer to execute.
To really strip down and replicate the problem, I wrote the following piece of code:
import pygame
import sys
import time
FRAME_RATE = 30
# don't mind the screen and time_passed variables; they aren't used in this script
def run_game():
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((500, 500))
prev_spike = 0
time_passed = 0
while 1:
start = time.clock()
pygame.display.update()
timenow = time.clock()
time_spent = timenow - start
if time_spent > 0.01:
print time_spent
if prev_spike:
print "Last spike was: {} seconds ago".format(timenow - prev_spike)
prev_spike = timenow
time_passed = clock.tick(FRAME_RATE)
if __name__ == "__main__":
run_game()
A snippet of output at that framerate:
0.0258948412828
Last spike was: 1.01579813191 seconds ago
0.0186809297657
Last spike was: 0.982841934526 seconds ago
0.0225958783907
Last spike was: 2.01697784257 seconds ago
0.0145269648427
Last spike was: 1.01603407404 seconds ago
0.0186094554386
Last spike was: 2.01713885195 seconds ago
0.0283046020628
Last spike was: 1.03270104172 seconds ago
0.0223322687757
Last spike was: 1.01709735072 seconds ago
0.0152536205013
Last spike was: 1.01601639759 seconds ago
I've really no clue what's going on, and would really love some insight.
Some more details:
A snippet of the output when printing the time_spent in every loop iteration (instead of only when it was > 0.01):
0.000204431946257
0.000242090462673
0.000207890381438
0.000272447838151
0.000230178074828
0.0357667523718 <-- update taking two orders of magnitude longer than normal
0.000293582719813
0.000343153624075
0.000287818661178
0.000249391603611
When run at 60 FPS, the interval between each spike almost always be 1 second, very rarely 2 seconds (and the spikes would last about twice as long). At lower frame rates, the interval between spikes would start to vary more, but would always be close to a whole number in value.
I tried running the script on another computer, but the problem wasn't replicated; the execution time on pygame.display.update() was reasonably quick and consistent. However, when I ran my original program on that machine, the one-second-interval lag spikes remained (I'll probably look for other machines to test on...)
Both machines that I tested on ran Windows 7.
EDIT:
I grabbed a few random games hosted on the Pygame website and I'm getting similar behaviour - calls to pygame.display.update (or flip) periodically take between 10 - 40 ms, whereas they normally take less than 2 ms.
Nobody else seems to be having this problem (or complaining about, at it least. That could be because most games run on less than 30 FPS where this issue isn't too noticeable), so there's likely something off with my environment. I did (kinda) reproduce the issue on a second machine though (as described above), so I'd rather not ignore the problem and hope end users don't experience it...
Try asking this in Game Development and you might get a better answer.
EDIT: The following code doesn't seem to fix the issues raised, but does provide testing for animation and uses timed callbacks for main game loop
Try working with a timed callback to your render function.
import pygame
import time
import math
from pygame.locals import *
desiredfps = 60
updaterate = int(1000 / desiredfps)
print "Aiming for {0}fps, update every {1} millisecond".format(desiredfps, updaterate)
lasttime = 0
rectx = 0
recty = 0
def run_game():
pygame.init()
screen = pygame.display.set_mode((500, 500))
pygame.time.set_timer(USEREVENT+1, updaterate)
def mainloop():
global lasttime
global rectx
global recty
screen.fill(pygame.Color("white"))
screen.fill(pygame.Color("red"), pygame.Rect(rectx,recty,20,20))
screen.fill(pygame.Color("blue"), pygame.Rect(480-rectx,480-recty,20,20))
screen.fill(pygame.Color("green"), pygame.Rect(rectx,480-recty,20,20))
screen.fill(pygame.Color("yellow"), pygame.Rect(480-rectx,recty,20,20))
rectx += 5
if rectx > 500:
rectx = 0
recty += 20
beforerender = time.clock()
pygame.display.update()
afterrender = time.clock()
renderdelta = afterrender - beforerender
framedelta = beforerender - lasttime
lasttime = beforerender
if renderdelta > 0.01:
print "render time: {0}".format(renderdelta)
print "frame delta: {0}".format(framedelta)
print "-------------------------------------"
while(1):
for event in pygame.event.get():
if event.type == USEREVENT+1:
mainloop()
if event.type == QUIT:
pygame.quit()
return
# Run test
run_game()
I don't seem to have any trouble when doing this, but please let me know if you still experience issues.
After some testing, here are some of the results. First, to answer the question:
The cause of lag spikes is not pygame.display.update(). The cause of lag spikes is clock.tick(FRAME_RATE). Note, that clock.tick() without the FRAME_RATE parameter doesn't cause spikes. The problem did not go away when I tried to substitute pygame's clock.tick() with manual tracking of frame rate using python's time.sleep() method. I think it is because internally, both python's time.sleep() and pygame's clock.tick() use the same function, which is known to be imprecise. It seems that if you feed that function 1ms to sleep (so as to not hog all of the CPU if the game is simple enough), the function will sometimes sleep much longer than that, about 10-15ms longer. It depends on the OS implementation of the sleep mechanism and the scheduling involved.
The solution is to not use any sleep-related functions.
There is also a second part. Even if you don't use any sleep(), there is an issue of an inconsistent delta time between individual frames, which when not taken into account may cause visual jittering/stuttering. I believe that this issue has been explored in great detail in this tutorial.
So I went ahead and implemented the solution presented in this tutorial in python and pygame, and it works perfectly. It looks very smooth even though I'm updating "physics" at only 30fps. It eats a lot of cpu, but it looks nice. Here is the code:
from __future__ import division
import pygame
from random import randint
from math import fabs
PHYS_FPS = 30
DT = 1 / PHYS_FPS
MAX_FRAMETIME = 0.25
def interpolate(star1, star2, alpha):
x1 = star1[0]
x2 = star2[0]
# since I "teleport" stars at the end of the screen, I need to ignore
# interpolation in such cases. try 1000 instead of 100 and see what happens
if fabs(x2 - x1) < 100:
return (x2 * alpha + x1 * (1 - alpha), star1[1], star1[2])
return star2
def run_game():
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((500, 500))
# generate stars
stars = [(randint(0, 500), randint(0, 500), randint(2, 6)) for i in range(50)]
stars_prev = stars
accumulator = 0
frametime = clock.tick()
play = True
while play:
frametime = clock.tick() / 1000
if frametime > MAX_FRAMETIME:
frametime = MAX_FRAMETIME
accumulator += frametime
# handle events to quit on 'X' and escape key
for e in pygame.event.get():
if e.type == pygame.QUIT:
play = False
elif e.type == pygame.KEYDOWN:
if e.key == pygame.K_ESCAPE:
play = False
while accumulator >= DT:
stars_prev = stars[:]
# move stars
for i, (x, y, r) in enumerate(stars):
stars[i] = (x - r * 50 * DT, y, r) if x > -20 else (520, randint(0, 500), r)
accumulator -= DT
alpha = accumulator / DT
stars_inter = [interpolate(s1, s2, alpha) for s1, s2 in zip(stars_prev, stars)]
# clear screen
screen.fill(pygame.Color('black'))
# draw stars
for x, y, r in stars_inter:
pygame.draw.circle(screen, pygame.Color('white'), (int(x), y), r)
pygame.display.update()
if __name__ == "__main__":
run_game()
import pygame
import time
import math
from pygame.locals import *
desiredfps = 60
updaterate = int(1000 / desiredfps)
lasttime = 0
rectx = 0
recty = 0
def run_game():
pygame.init()
screen = pygame.display.set_mode((500, 500))
pygame.time.set_timer(USEREVENT+1, updaterate)
def mainloop():
global lasttime
global rectx
global recty
screen.fill(pygame.Color("white"))
screen.fill(pygame.Color("red"), pygame.Rect(rectx,recty,20,20))
screen.fill(pygame.Color("blue"), pygame.Rect(480-rectx,480-recty,20,20))
screen.fill(pygame.Color("green"), pygame.Rect(rectx,480-recty,20,20))
screen.fill(pygame.Color("yellow"), pygame.Rect(480-rectx,recty,20,20))
rectx += 5
if rectx > 500:
rectx = 0
recty += 20
beforerender = time.clock()
pygame.display.update()
afterrender = time.clock()
renderdelta = afterrender - beforerender
framedelta = beforerender - lasttime
lasttime = beforerender
if renderdelta > 0.01:
print ("render time: {0}").format(renderdelta)
print ("frame delta: {0}").format(framedelta)
print ("-------------------------------------")
while(1):
for event in pygame.event.get():
if event.type == USEREVENT+1:
mainloop()
if event.type == QUIT:
pygame.quit()
return #
How can I capture and save a sequence of images or a video of a pygame screen?
Basically I want to share my game video on youtube. Also, want to make a tutorial.
The game is rendered mainly in a loop:
def main():
while True:
GetInput()
Move()
Shift()
Draw()
With the Draw() function doing all the blit() and stuff before doing the pygame.display.flip()
Use pygame.image.save on your screen surface:
window = pygame.display.set_mode(...)
...
pygame.image.save(window, "screenshot.jpeg")
Note that this will slow down your program tremendously. If it is time-based, you may wish to fake the framerate when doing a capture.
The accepted answer said you could save the current screen with the pygame.image.save method
This is a good idea, however you might not want to save the images on the hard drive while the game is running, also pointed out by the question. Instead, you should save the screens in the program and then process them after the game has stopped running.
Here is my code with comments, that only shows the general idea of how a screen recording might work. It uses opencv (with numpy) and pygame but you have to install ffmpeg to convert the images to a video (try ffmpg in the terminal to test). Don't let the program run too long, because the saving still takes quite a while and is about proportional to the recorded frames. For more efficiency, you could only record every second frame or so.
from contextlib import contextmanager
import os
import time
import cv2
import numpy as np
import pygame as pg
from pygame import surfarray
def pg_to_cv2(cvarray:np.ndarray)->np.ndarray:
cvarray = cvarray.swapaxes(0,1) #rotate
cvarray = cv2.cvtColor(cvarray, cv2.COLOR_RGB2BGR) #RGB to BGR
return cvarray
def timer_wrapper(func):
def inner(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
#print("Finished:" ,func.__name__ ,end-start)
return end - start
return inner
#contextmanager
def video_writer(*args,**kwargs):
video = cv2.VideoWriter(*args,**kwargs)
try:
yield video
finally:
video.release()
#timer_wrapper
def save_frames(frames: list, average_dt: float|list, file_type: str = "mp4", name: str = "screen_recording"):
if type(average_dt) is list: average_dt = sum(average_dt)/len(average_dt) # force average_dt to be a float
size = frames[0].get_size()
codec_dict={
"avi":'DIVX',
"mp4":'MP4V'
}
codec = cv2.VideoWriter_fourcc(*codec_dict[file_type])
with video_writer(name+"."+file_type, codec, 1000/average_dt, size) as video: # file_name, codec, average_fps, dimensions
for frame in frames:
try:
pg_frame = surfarray.pixels3d(frame) # convert the surface to a np array. Only works with depth 24 or 32, not less
except:
pg_frame = surfarray.array3d(frame) # convert the surface to a np array. Works with any depth
cv_frame = pg_to_cv2(pg_frame) # then convert the np array so it is compatible with opencv
video.write(cv_frame) #write the frame to the video using opencv
def draw_fps(s:pg.Surface,clock:time.Clock):
fps = clock.get_fps()
sysfont.render_to(s,(100,100),str(fps),fgcolor=nice_green)
# initializing globals (colors, fonts, window, etc.)
pg.init()
sysfont = pg.freetype.SysFont(None,40)
BLACK = (0,)*3
nice_green = pg.Color("chartreuse2")
size=(1000, 600)
pg.display.set_caption("Screen Recording")
window = pg.display.set_mode(size)
# this is to save the frames
frames = []
dts = []
clock = pg.time.Clock()
running=True
try:
while running:
dt = clock.tick(60) # aim for ... fps
for event in pg.event.get():
if event.type == pg.QUIT:
running=False
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
running=False
window.fill(BLACK)
draw_fps(window,clock)
window_copy = window.copy() # if we don't copy the window then we will have the same image in all frames at the end
frames.append(window_copy) # We save the current frame together with the time passed between the frames
dts.append(dt)
pg.display.flip()
#if len(frames) >= 100: running = False # uncomment this to stop the game after ... frames for similar results in every run"
finally:
pg.quit()
# At this stage, the game ended and the cleanup process can start. For this we convert the frames to opencv images
# Only then we will write the video to the hard drive (That is what makes the save so slow).
# General information about the recording
frame_num = len(frames)
dt_sum = sum(dts)
average_dt = dt_sum/frame_num
# This is only an approximation:
# for each frame we have width * height many pixels -> frame_num * width * height
# A Surface needs get_bytesize() many bytes per pixel (In this case 4 bytes, because we set the depth of the display to 32 bits)
memory_usage_approx = frame_num * size[0] * size[1] * frames[0].get_bytesize() #https://www.pygame.org/docs/ref/surface.html#pygame.Surface.get_bytesize
print("Total time:" , dt_sum/1000,"s")
print("Average time per frame:" , average_dt,"ms")
print("Number of frames:", frame_num)
print("Memory usage approximation" , memory_usage_approx/1000, "KB")
args = (frames,dts,"avi","screen_recording")
time_for_save = save_frames(*args)
file_name = args[3]+"."+args[2]
video_memory_usage = os.path.getsize(file_name)
print("Video memory usage:" , video_memory_usage/1000, "KB")
with open("test.txt", "a") as f:
print("Total time:" , dt_sum/1000,"s\nNumber of frames:", frame_num,"\nSize:",size,"\nTime for save:",time_for_save,"s\nSaved in file:",file_name,file=f)
print("_"*100,file=f)
Or you just use a lib like Pygame Recorder.
x3 = pygame.surfarray.pixels3d(screen)
x3 = x3[:,:,::-1]
I found a cool way;
you can use
x3 = pygame.surfarray.pixels3d(window)
to get all the pixels on any surface or the screen (the window variable)!
You can use this in things like NumPy where you can use the code
array = numpy.uint8(x3)
to get the image of the surface as a NumPy array and then
im = PIL.Image.fromarray(array)
to make it a Pillow image, if you want. Then you can show it with a simple im.show() or just do whatever with it.