Advertisement

Main game loop and fixed frame rate

Started by February 21, 2017 06:33 PM
1 comment, last by Kylotan 8 years ago

Hi, I am a newbie developer.

I am trying to understand the main loop of the game.

I am creating really simple game. The idea is a falling ball , the direction of which is controlled by a click.

Here some requirements for the game I am trying to achieve:

1. To have fixed frame rate on any device. For example, I need the ball to move to a new position with a fixed time interval (ex. 10ms).

2. To be able to increase the speed of the ball. It should be consistent with frame rate to provide the same experience of any device.

I've read a lot about this, but still have a lot of misunderstandings.

Let me explain the problem step by step.

Some parts of my Game

The Main Loop


 public void run() {
        while (mIsRunning) {
            processGameInput();
            updateGameState();
            drawGame();
        }
    }

This loop is being running in a separate thread.

The drawGame method


    @Override
    public void drawGame() {
        AbstractCanvas canvas = prepareAndGetCanvas();
        canvas.clear(canvasBgColor);
        ball.draw(canvas);
        // Draw other objects
        releaseCanvas();
    }

The updateGameState method


    public void updateGameState() {
        Point canvasSize = getCanvasSize();
        ball.updateState(canvasSize);
        // Update state of other objects
    }
 

The Ball class


public class Ball extends BaseBallShape {
    private VelocityVector velocityVector;

    public VectorBall(float centerX, float centerY, float radius, int color) {
        super(centerX, centerY, radius, color);
        velocityVector = new VelocityVector(0, 2);
    }

    public VectorBall(float centerX, float centerY, float radius, VectorGeometry.VelocityVector velocityVec) {
        super(centerX, centerY, radius);
        velocityVector = velocityVec;
    }

    @Override
    public void draw(AbstractCanvas canvas) {
        canvas.drawCircle(getCenterX(), getCenterY(), getRadius(), getColor());
    }

    @Override
    public void updateState(Point canvasSize) {
        updatePosition(canvasSize);
    }

    public void updatePosition(Point2d canvasSize) {
        centerX += velocityVector.x;
        centerY += velocityVector.y;
    }

    public void increaseVelocity(float deltaVelocity) {
        increaseXVelocity(deltaVelocity);
        increaseYVelocity(deltaVelocity);
    }

    public void decreaseVelocity(float deltaVelocity) {
        decreaseXVelocity(deltaVelocity);
        decreaseYVelocity(deltaVelocity);
    }

    public void decreaseYVelocity(float velocity) {
        velocityVector.y -= velocity;
    }

    public void decreaseXVelocity(float velocity) {
        velocityVector.x -= velocity;
    }

    public void increaseXVelocity(float velocity) {
        velocityVector.x += velocity;
    }

    public void increaseYVelocity(float velocity) {
        velocityVector.y += velocity;
    }

}

As you could see, I am using velocity vector in order to control ball direction and magnitude size (size of pixels to increase on each update).

Let me explain this in a greater details.

Each time the main loop does an iteration three method are called.

So update method of the ball object increases its position by the constant stored now in the velocityVector.

Consider the ball initial position is (10,10);

The velocity vector is (0,2);

1 Iteration - The Ball (10,12);

2 Iteration - The Ball (10,14);

I think this is clear.

The problem is that the game runs with different speed on different devices.

I have read a lot of articles but still cannot get an idea why such solution is chosen.

Here are main articles I've taken as base example.

1. http://gafferongames.com/game-physics/fix-your-timestep/

2. http://gameprogrammingpatterns.com/game-loop.html

3. And my question on stack overflow - http://gamedev.stackexchange.com/questions/137658/game-loop-delay-the-right-way-and-game-speed/137660#137660

Lets consider the solution from the second link at first. They both have the same thing I cannot get.


double previous = getCurrentTime();
double lag = 0.0;
while (true)
{
  double current = getCurrentTime();
  double elapsed = current - previous;
  previous = current;
  lag += elapsed;

  processInput();

  while (lag >= MS_PER_UPDATE)
  {
    update();
    lag -= MS_PER_UPDATE;
  }

  render();
}

The main questions I am interested in - Why the update method is called inside the loop, not the render ?

MS_PER_UPDATE = 100

As far as I can understand this example. It could be read like that.

"Measure current time, calculate elapsed time for the previous iteration, if it is greater than MS_PER_UPDATE that means that the render and processInput methods took more time that they should, so just update game while GPU is lagging"

So in this case the ball will be moving 2pixels down only if lag is greater than 0.1s.

What is the purpose of such solution ?

Another example I have suggested to use is following solution


double previous = getCurrentTime();
double totalElapsed = 0.0f;
while (true)
{
    double current = getCurrentTime();
    double elapsed = current - previous;
    previous = current;

    processInput();
    update( elapsed);
    totalElapsed += elapsed;

    if(totalElapsed > MS_PER_FRAME)
    {
        render();
        totalElapsed -= MS_PER_FRAME;  
        if(totalElapsed > MS_PER_FRAME)
            std::cout<<"Performance warning, rendering or update took too long"<< std::endl;      
    }
}

And the update method


public void updatePosition(double elapsed) {
    mCenterX += mVector.x * elapsed;  // framerate indipendent
    mCenterY += mVector.y * elapsed;
}

This is much clearer for me. But I have just tested this approach on different devices and the game runs differently.

So, I've completely lost here (

I would be grateful for any help to get better understanding of this topic. My goal is to have game running the same way on all devices and to be able to control the speed of the ball.


double previous = getCurrentTime();
double lag = 0.0;
while (true)
{
  double current = getCurrentTime();
  double elapsed = current - previous;
  previous = current;
  lag += elapsed;

  processInput();

  while (lag >= MS_PER_UPDATE)
  {
    update();
    lag -= MS_PER_UPDATE;
  }

  render();
}
This approach will give the same results on different devices. Rendering can only happen so many times per second. You usually target 30 or 60 times per second. However, if the framerate lags then you don't want the simulation to suffer. Using a fixed independent timestep will ensure that the game runs consistently no matter what framerate is.

Have one update for each render means if the game starts to lag then you will have larger timesteps in your simulation making the simulation depend on the framerate. This can introduce bugs that only show up when the framerate drops or on slower devices.
My current game project Platform RPG
Advertisement

The main questions I am interested in - Why the update method is called inside the loop, not the render ?

This is so that it's possible to perform updates where each one covers a fixed time period. This in turn is desirable because the simplified equations used by physics systems tend to perform differently if the time period varies.

Note that this isn't designed to keep the rendering rate stable. It's designed to keep the updates correct in the face of a potentially varying rendering rate.

There is no good reason to attempt to render multiple times per loop - if the graphics are being shown to the user, this means they'd see successive frames without their input being processed. Combined with you performing the updates at variable rates, this is even worse than a naive update/render/repeat loop as you get the worst of both worlds.

HappyCoder's example above will work correctly, and will produce virtually identical gameplay and behaviour, as long as you're happy with different rendering rates on different devices. If you need the same rate on all devices then that's a slightly more difficult problem, but you probably don't really need that, as long as your updates are working correctly.

This topic is closed to new replies.

Advertisement