Normally the game speed with pygame is locked with the fps, so I attempted making a simple two classes that'd be super easy to use, to allow for the same game speed no matter what the fps is.
For each frame, it calculates the difference between the amount of ticks that have passed, and how many should have passed (I get that this way could be better, but people don't change this mid game right?). For example, the difference at 60fps at 60 ticks per second is 1, at 30fps, the difference becomes 2, at 120fps, one frame will have a difference of 0, and the next 1. I did my own version of ticks since I locked the actual ticks to the fps.
With the frames per second, I set it to calculate it every x seconds, so it's a bit more accurate and avoids times so small it causes zero division errors (0.1 seconds for 1753 frames is more accurate that 0.00005704506 seconds for 1 frame). If you don't limit the fps, the inbuilt fps function just returns 0.
There does seem to be a bit of a problem though, and I'm not sure what's causing it. I'm sure I've done the framerate calculation correctly (with a minimum time so it doesn't end up working with tiny floats), but setting the fps to 500 reads as 333, and 1000 reads as 500, which is really strange. Setting it to something really high or quite low returns something that looks a lot more correct.
I've not really used a with
with a class containing another class before, so I'm not sure if I've done it right (I originally only had the one class, but realised __enter__
and __exit__
needed the class called each time), so any feedback would be good as to what I didn't do well.
from __future__ import division
import pygame
import time
import random
class GameTime(object):
def __init__(self, desired_fps, desired_ticks, clock):
self.start_time = time.time()
self.desired_fps = desired_fps
self.desired_ticks = desired_ticks
self.clock = clock
self.ticks = 0
self.framerate_counter = 1
self.framerate_time = 0
def get_ticks(self, current_time):
"""Ensure the correct number of ticks have passed since the
start of the game.
This doesn't use the inbuilt pygame ticks.
"""
time_elapsed = current_time - self.start_time
total_ticks_needed = int(time_elapsed * self.desired_ticks)
ticks_this_frame = total_ticks_needed - self.ticks
self.ticks += ticks_this_frame
return ticks_this_frame
def get_fps(self, current_time, update_time=0.1):
"""Calculate the FPS from actual time, not ticks.
It will return a number every update_time seconds, and will
return None any other time.
"""
frame_time = current_time - self.framerate_time
if frame_time < update_time:
self.framerate_counter += 1
else:
self.framerate_time = current_time
fps = self.framerate_counter / frame_time
self.framerate_counter = 1
return int(fps)
def limit_fps(self, alternate_fps=None):
wanted_fps = alternate_fps or self.desired_fps
if wanted_fps:
self.clock.tick(wanted_fps)
class GameTimeLoop(object):
"""This gets called every loop but uses GameTime."""
def __init__(self, GTObject):
self.GTObject = GTObject
GTObject.loop_start = time.time()
#Run the code once now so the result can be called multiple times
self.ticks = GTObject.get_ticks(GTObject.loop_start)
self.fps = GTObject.get_fps(GTObject.loop_start)
self.temp_fps = None
def __enter__(self):
return self
def __exit__(self, *args):
self.GTObject.limit_fps(self.temp_fps)
self.temp_fps = None
def set_fps(self, fps):
self.GTObject.desired_fps = fps
def update_fps(self, fps):
self.temp_fps = fps
An example pygame function using it:
def game():
pygame.init()
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
max_fps = None
ticks_per_second = 60
FrameRate = GameTime(max_fps, ticks_per_second, clock)
move_speed = 0.175
move_total = 0
while True:
with GameTimeLoop(FrameRate) as frame_time:
#Display the current fps
if frame_time.fps is not None:
pygame.display.set_caption(str(frame_time.fps))
#Calculations to be done once per tick
#Can multiply by the number of ticks or do a loop
ticks_this_frame = frame_time.ticks
for tick in xrange(frame_time.ticks):
pass
#Do normal stuff here
move_total += move_speed * ticks_this_frame
print move_total #Just print this to show it's correctly staying at the same speed
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
#Set a new fps
if event.type == pygame.MOUSEBUTTONDOWN:
new_fps = random.choice((None, 1, 5, 10, 30, 60, 120, 1000, 10000))
frame_time.set_fps(new_fps)
print 'set fps to: {}'.format(new_fps)
#Temporarily set a new fps while the mouse moves
if event.type == pygame.MOUSEMOTION:
frame_time.update_fps(30)