Framerate Affect the Speed of the Game

pygame clock.tick() vs framerate in game main loop

FPS, Frames Per Second, is the number of frames shown per unit of time.

1 / FPS is the amount of time should pass between each frame.

Tick is just a measure of time in PyGame.

clock.tick(40) means that for every second at most 40 frames should pass.

Does Box2D Physics rely on the framerate?

Your question is, I think, really two questions.

1) Should you vary the rate of the physics timestep during simulation.

NO, you should not.. You can iterate the engine multiple times during the a time step of the game loop (call the Step(...) function multiple times with the same step values).

From section 2.4 of the 2.3.0 Box2D manual:

A variable time step produces variable results, which makes it
difficult to debug. So don't tie the time step to your frame rate
(unless you really, really have to).

2) How can I connect two real-time physics simulations and slave their physics update cycles to each other.

Once upon a time there was a genre-altering game called Age of Empires. It boasted thousands of AI units fighting each other in near-real-time on a 28.8 network. It worked so well, somebody wrote an article about how they did it:

  1. The article.
  2. The pdf version of the article.

I adapted the technique and the code below for my update loop so that I could control the frame rate of two games running against each other on two different iPads.

void GameManager::UpdateGame()
{
const uint32 MAXIMUM_FRAME_RATE = Constants::DEFAULT_OBJECT_CYCLES_PER_SECOND();
const uint32 MINIMUM_FRAME_RATE = 10;
const uint32 MAXIMUM_CYCLES_PER_FRAME = (MAXIMUM_FRAME_RATE/MINIMUM_FRAME_RATE);
const double UPDATE_INTERVAL = (1.0/MAXIMUM_FRAME_RATE);

static double lastFrameTime = 0.0;
static double cyclesLeftOver = 0.0;

double currentTime;
double updateIterations;

currentTime = CACurrentMediaTime();
updateIterations = ((currentTime - lastFrameTime) + cyclesLeftOver);

if(updateIterations > (MAXIMUM_CYCLES_PER_FRAME*UPDATE_INTERVAL))
{
updateIterations = MAXIMUM_CYCLES_PER_FRAME*UPDATE_INTERVAL;
}

while (updateIterations >= UPDATE_INTERVAL)
{
// DebugLogCPP("Frame Running");
updateIterations -= UPDATE_INTERVAL;
// Set the random seed for this cycle.
RanNumGen::SetSeed(_cycleManager->GetObjectCycle());
// Dispatch messages.
_messageManager->SendMessages();
// Update all entities.
_entityManager->Update();
// Update the physics
_gameWorldManager->Update(Constants::DEFAULT_OBJECT_CYCLE_SECONDS());
// Advance the cycle clock.
_cycleManager->Update();
}

cyclesLeftOver = updateIterations;
lastFrameTime = currentTime;
}

This piece of code keeps the number of iterations executed balanced between an upper and lower maximum. One key piece to note is that the actual call to this function doesn't happen if the message from the other player has not been received in a timely fashion. This effectively slaves the two physics systems together.

3) THE PART THAT YOU (MAYBE) REALLY SHOULD KNOW

If you plan on using Box2D on both devices to independently run the physics, you are going to almost certainly going to see them diverge after a short time. I ran my game on an iPad 2 and iPad 3 and noticed after a few seconds they diverged (collisions occur in one, but not the other). This is because rounding behavior in floating point numbers is different based on multiple factors. For a few quick calculations, no problems. But small discrepancies creep into the lower order bits and accumulate when the values are continually cycled through integrators (as you would see, for example, in a physics simulation). Double precision helps a little, but not in the end.

A few simple tests of looking at a specific bouncing ball in a field of bouncing balls on multiple CPUs (e.g. iPad 2 vs. iPad 3 for the exact same code) will show this. The errors creep in after a couple seconds of motion and suddenly your velocities/positions are off enough to make a difference.

Fixed point math is a solution to this, but this way also leads to its own kind of madness. At one point, Box2D had a fixed point version, but this time has passed.

I even toyed with cooking up a fixed point version of Box2D, but got distracted by another project (Space Spiders Must Die!). Some day...

This may not be a problem for your particular situation (i.e. you are not doing independent simulation or doing it in a way that it works as expected), and if it is not, no worries.

You can see lots of other Box2D stuff (but not this game...it is not up there yet) here on my blog.

Was this helpful?

Pygame snake velocity too high when the fps above 15

The return value of self.clock.tick() is the time which has passed since the last call.
Use the return value to control the speed. Define the distance, of movement of the snake per second (e.g. self.velocity = 400 means 400 pixel per second). Get the time between to frames (delta_t) and scale the movement of the snake by the elapsed time (delta_t / 1000):

class Game:
def __init__(self):
# [...]

# distance per second
self.velocity = 400

# [...]

def game(self):

# [...]

while self.running:
delta_t = self.clock.tick(30)

# [...]

if not self.paused:
step = delta_t / 1000 # / 1000 because unit of velocity is seconds
self.snake_x += self.snake_x_change * step
self.snake_y += self.snake_y_change * step
else:
self.game_over()

pygame.display.flip()

With this setup it is ease to control the speed of the snake. For instance, the speed can be increased (e.g. self.velocity += 50), when the snake grows.

Of course you have to round the position of the snake (self.snake_x, self.snake_y) to a multiple of the grid size (multiple of 25) when you draw the snake and when you do the collision test. Use round to do so:

x, y = round(self.snake_x / 25) * 25, round(self.snake_y / 25) * 25

Ensure that the positions which are stored in snake_list are a multiple of 25. Just append a new head to the list, if the head of the snake has reached a new field:

if len(self.snake_list) <= 0 or snake_head != self.snake_list[-1]:
self.snake_list.append(snake_head)

Apply that to the methods build_snake draw and check_apple_eaten:

class Game:
# [...]

def build_snake(self):
snake_head = list()
x, y = round(self.snake_x / 25) * 25, round(self.snake_y / 25) * 25
snake_head.append(x)
snake_head.append(y)
if len(self.snake_list) <= 0 or snake_head != self.snake_list[-1]:
self.snake_list.append(snake_head)

if len(self.snake_list) > self.snake_length:
del self.snake_list[0]

for snake in self.snake_list[:-1]:
if snake == snake_head:
self.snake_reset()

self.draw("snake")

def check_apple_eaten(self):
x, y = round(self.snake_x / 25) * 25, round(self.snake_y / 25) * 25
if x == self.apple_x and y == self.apple_y:
self.set_position("apple")
self.snake_length += 1
self.score += 1

def snake_borders_check(self):
x, y = round(self.snake_x / 25) * 25, round(self.snake_y / 25) * 25
if x < 0 or x > self.SCREEN_WIDTH - 25:
self.snake_reset()
if y < 0 or y > self.SCREEN_HEIGHT - 25:
self.snake_reset()

In Pygame, normalizing game-speed across different fps values

Games use a fixed-timestep for physics, while allowing the video timestep (fps) to vary. This means your update(delta) function gets called with a constant delta value. This maintains stability.

This means in practice, update may end up being called multiple times on average per single call of draw(), depending on how much time elapses.

For details see: Gaffer's "fix your timestep"

A larger (python) example is at cookbook: ConstantGametime

Constant game speed independent of variable FPS in OpenGL with GLUT?

glut is designed to be the game loop. When you call glutMainLoop(), it executes a 'for loop' with no termination condition except the exit() signal. You can implement your program kind of like you're doing now, but you need some minor changes. First, if you want to know what the FPS is, you should put that tracking into the renderScene() function, not in your update function. Naturally, your update function is being called as fast as specified by the timer and you're treating elapsedTime as a measure of time between frames. In general, that will be true because you're calling glutPostRedisplay rather slowly and glut won't try to update the screen if it doesn't need to (there's no need to redraw if the scene hasn't changed). However, there are other times that renderScene will be called. For example, if you drag something across the window. If you did that, you'd see a higher FPS (if you were properly tracking the FPS in the render function).

Velocity based on time rather than FPS

Calculate both your velocity and position based on time like this:

deltaTime = currentTime - timeLastFrame    // time sins last frame
velocityDelta = deltaTime * gravityRate // velocity change

// calculate new position with only half of the velocity change this frame
objectPosition.y -= (velocity.y + (velocityDelta / 2)) * deltaTime
velocity.y += velocityDelta

The scales needs to be adjusted to your game settings.


EDIT:

So in details - first we need to know how much time have happen since last:

deltaTime = currentTime - timeLastFrame    // time sins last frame

As the velocity keeps increasing with time, we need to calculate how much it have increased since last frame. The longer since last frame, the more the velocity is changed, those we multiply it with the delta time.

velocityDelta = deltaTime * gravityRate    // velocity change 

In order to calculate how much the object have moved during the frame, the average velocity in that frame is calculated. This is equal to the initial velocity: velocity.y plus the end velocity: velocity.y + velocityDelta, divided by 2. In short, this can be written as (velocity.y + (velocityDelta / 2)). With the average velocity we can then move the object by multiplying with the delta time of the frame:

objectPosition.y -= (velocity.y + (velocityDelta / 2)) * deltaTime   

To be ready for the next frame we also need to update the velocity:

velocity.y += velocityDelta 

Framerate() doesn't seem to affect Movie - Processing

I was able to reproduce your problem, but not to correct it. After trying very simplified code based on the example in the Processing documentation, I still couldn't make frameRate() work. I even tried different renderers with no discernable results.

You can still get a similar result with simple techniques. For once, you can count your frames to only update the movie once every couple frames:

Movie myMovie;
int frameCounter = 0;
int framerate = 30;
int movieFPS = 3; // the movie will be updated only 3 times per second
int updateFrame = 1;

void setup() {
size(200, 200);
frameRate(framerate);
updateFrame = framerate / movieFPS;

myMovie = new Movie(this, "SampleVideo_360x240_30mb.mp4");
myMovie.loop();
}

void draw() {
if (frameCounter++ % updateFrame == 0) {
image(myMovie, 0, 0);
}
}

// Called every time a new frame is available to read
void movieEvent(Movie m) {
m.read();
}

What's interesting here is that the sound won't be affected, as the movie is still read at the same speed, just not graphically displayed every frame.

If you're going for the "webcam" effect, you can randomize the frame count instead of having it update regularly:

Movie myMovie;
int frameCounter = 0;
int framerate = 30;
int minFramesBetweenUpdate = 10;
int maxFramesBetweenUpdate = 45;
int updateFrame = 1;

void setup() {
size(200, 200);
frameRate(framerate);

myMovie = new Movie(this, "SampleVideo_360x240_30mb.mp4");
myMovie.loop();
}

void draw() {
if (frameCounter++ > updateFrame) {
image(myMovie, 0, 0);
updateFrame = frameCounter + (int)random(minFramesBetweenUpdate, maxFramesBetweenUpdate);
}
}

// Called every time a new frame is available to read
void movieEvent(Movie m) {
m.read();
}

The framerate technically doesn't change the speed at which the movie will be displayed, only the number of frames that will be seen in a certain amount of time. A 10 seconds video with different framerates will always play in 10 seconds.

If you want to change the speed at which the movie is displayed, you need to use something like myMovie.speed(0.5);. The speed at which the movie is played will change how long it takes to run it. If your movie has sound, playing it at more than 1.0 speed will "chipmunk" the sound, for an example.

Let me know if I can help with something. And have fun!



Related Topics



Leave a reply



Submit