Related
I'm using pygame in order to create a sort of animation. What I have in mind is to have a series of background images change in relation to the time that has passed once I initiate the game. I came up with this code to do so:
while True:
DISPLAYSURF.fill(black) #fills the displaysurf with black
for time in pygame.time.get_ticks():
if time == 2:
img_1 = img_2
DISPLAYSURF.blit(img_2, (0, 0)) #copy, pastes the surface object to fixed point
if time == 4:
img_2 = img_3
DISPLAYSURF.blit(img_3, (0, 0))
if time == 8:
img_3 = img_4
DISPLAYSURF.blit(img_4, (0, 0))
if time == 10:
img_4 = img_5
DISPLAYSURF.blit(img_5, (0, 0))
if time == 12:
img_5 = img_6
DISPLAYSURF.blit(img_6, (0, 0))
if time == 14:
img_6 = img_7
DISPLAYSURF.blit(img_7, (0, 0))
if time == 16:
img_7 = img_8
DISPLAYSURF.blit(img_8, (0, 0))
pygame.display.flip()
clock.tick(FPS)
What I received back when I ran the program was "'int' object is not iterable" which made me think that I may not be able to do what I had in mind, because the images I have are classified in Pygame as surface objects. I'm thinking of two things:
-->Is it possible to create a function that can re-upload the images in relation to time by somehow converting the surface objects' type?
-->Is my code even reflecting what I want it to do?
Please let me know and fire away with the critiques! I'm very new to coding, so any feedback is helpful!
#Aggragoth has covered the error message already, so I wont go into that.
One way to periodically change the background is to keep a timer, and adjust the background based on a predefined period.
import pygame
import time
# Window size
WINDOW_WIDTH = 200
WINDOW_HEIGHT = 200
# background colour
SKY_BLUE = (161, 255, 254)
WHITE = (255, 255, 255)
BLUE = ( 5, 55, 255)
### MAIN
pygame.init()
surface_type = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), surface_type )
pygame.display.set_caption("Background Change")
# Variables to manage the background change
background_delay = 1500 # milliseconds
background_time = 0 # when the background last changed
backgrounds = [ WHITE, SKY_BLUE, WHITE, SKY_BLUE, BLUE ]
background_index = 0 # index of the currently used background
# Main loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
# Re-draw the screen background from the list after a delay
time_now = pygame.time.get_ticks()
if ( time_now > background_time + background_delay ):
# switch to the next background
background_time = time_now
background_index += 1
# if we're out of backgrounds, start back at the head of the list
if ( background_index >= len( backgrounds ) ):
background_index = 0
# Draw the background
window.fill( backgrounds[ background_index ] )
pygame.display.flip()
# Update the window, but not more than 60fps
clock.tick_busy_loop( 60 )
pygame.quit()
The main part of the code is to keep the time we last changed the background. If the elapsed time since then (from pygame.time.get_ticks()) is greater than the last-change-time plus a delay, then change to the next background.
In this example I've just used colours, but the backgrounds[] list could also hold images, and be used with window.blit().
The reason that you are getting the error int object is not iterable is because pygame.time.get_ticks() is a function that returns an integer, which you cannot use to iterate unless you use it in the range() function in conjunction with the for loop. A better idea might be to simply replace for time in pygame.time.get_ticks() with just time = pygame.time.get_ticks()
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 #
Using PyGame, I get flickering things. Boxes, circles, text, it all flickers. I can reduce this by increasing the wait between my loop, but I though maybe I could eliminate it by drawing everything to screen at once, instead of doing everything individually. Here's a simple example of what happens to me:
import pygame, time
pygame.init()
screen = pygame.display.set_mode((400, 300))
loop = "yes"
while loop=="yes":
screen.fill((0, 0, 0), (0, 0, 400, 300))
font = pygame.font.SysFont("calibri",40)
text = font.render("TextA", True,(255,255,255))
screen.blit(text,(0,0))
pygame.display.update()
font = pygame.font.SysFont("calibri",20)
text = font.render("Begin", True,(255,255,255))
screen.blit(text,(50,50))
pygame.display.update()
time.sleep(0.1)
The "Begin" button flickers for me. It could just be my slower computer, but is there a way to reduce or eliminate the flickers? In more complex things I'm working on, it gets really bad. Thanks!
I think part of the problem is you're calling 'pygame.display.update()' more then once. Try calling it only once during the mainloop.
Some other optimizations:
You could take the font creation code out of the main loop to speed things up
Do loop = True rather then loop = "yes"
To have a more consistent fps, you could use Pygame's clock class
So...
import pygame
pygame.init()
screen = pygame.display.set_mode((400, 300))
loop = True
# No need to re-make these again each loop.
font1 = pygame.font.SysFont("calibri",40)
font2 = pygame.font.SysFont("calibri",20)
fps = 30
clock = pygame.time.Clock()
while loop:
screen.fill((0, 0, 0), (0, 0, 400, 300))
text = font1.render("TextA", True,(255,255,255))
screen.blit(text,(0,0))
text = font2.render("Begin", True,(255,255,255))
screen.blit(text,(50,50))
pygame.display.update() # Call this only once per loop
clock.tick(fps) # forces the program to run at 30 fps.
You're updating your screen 2 times in a loop, one for drawing first text(TextA) and one for second text(Begin).
After your first update, only first text appears, so you can't see begin text between first update and second update. This causes flickering.
Update your screen after drawing everything. In your case, remove first pygame.display.update().
You're redrawing the content of your entire screen every 0.1 seconds. It's much more common and faster to keep track of the changes you actual make and only update the rects that actually contain changed content. So draw everything outside of your loop and have your events modify the screen and keep track of the rectangles that actually are changed.
So something like this:
import pygame, time
pygame.init()
screen = pygame.display.set_mode((400, 300))
screen.fill((0, 0, 0), (0, 0, 400, 300))
font = pygame.font.SysFont("calibri",40)
text = font.render("TextA", True,(255,255,255))
screen.blit(text,(0,0))
font = pygame.font.SysFont("calibri",20)
text = font.render("Begin", True,(255,255,255))
screen.blit(text,(50,50))
loop = "yes"
counter = 0
dirty_rects = []
while loop=="yes":
pygame.display.update()
time.sleep(0.1)
# Handle events, checks for changes, etc. Call appropriate functions like:
counter += 1
if counter == 50:
font = pygame.font.SysFont("calibri",20)
text = font.render("We hit 50!", True,(255,255,255))
screen.blit(text,(50,100))
dirty_rects.append(Rect((50,100),text.get_size()))
Pygame has a Buffer system to avoid flickering, so you should draw them as you are doing, but update only once at the end:
...
while loop=="yes":
screen.fill((0, 0, 0), (0, 0, 400, 300))
font = pygame.font.SysFont("calibri",40)
text = font.render("TextA", True,(255,255,255))
screen.blit(text,(0,0))
font = pygame.font.SysFont("calibri",20)
text = font.render("Begin", True,(255,255,255))
screen.blit(text,(50,50))
pygame.display.update() # only one update
time.sleep(0.1)
And Pygame has a Clock Class that is better than time.sleep(0.1) if you wan't to keep a framerate.