Does "deWiTTERS Game Loop" assume a constant UPS?
Asked Answered
S

1

8

I recently tried to get into games programming. I am pretty experienced with Java, but not with game programming. I read http://www.koonsolo.com/news/dewitters-gameloop/ and implemented the game loop proposed there with the following code:

private static int UPDATES_PER_SECOND = 25;
private static int UPDATE_INTERVAL = 1000 / UPDATES_PER_SECOND * 1000000;
private static int MAX_FRAMESKIP = 5;

public void run() {
    while (true) {
        int skippedFrames = 0;
        while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) {
            this.updateGame();
            this.nextUpdate += UPDATE_INTERVAL;
            skippedFrames++;
        }

        long currentNanoTime = System.nanoTime();
        double interpolation = (currentNanoTime + UPDATE_INTERVAL - this.nextUpdate) / UPDATE_INTERVAL;
        this.repaintGame(interpolation);
    }
}

The loop looked promising and easy, but now that I am actually trying to do something with it I am not so sure anymore. If I am not completly mistaken updateGame() takes care of things like calculating positions, moving enemies, calculating collisions, ...? Since the interpolation is not passed to updateGame(), does that mean that we assume that updateGame() is called exactly and steadily UPDATES_PER_SECOND times per second? Does that mean all our calculations are based on that assumption? And wouldn't that cause us a lot of trouble if - for whatever reason - the call to updateGame() is delayed?
For example, if my character sprite is supposed to walk right and we move it according to its speed on every updateGame() - if the method were to be delayed, that would mean our calculations are simply off and the character would lag?

On the website, the following example for the interpolation is stated:

If in the 10Th gametick the position is 500, and the speed is 100, then in the 11Th gametick the position will be 600. So where will you place your car when you render it? You could just take the position of the last gametick (in this case 500). But a better way is to predict where the car would be at exact 10.3, and this happens like this:
view_position = position + (speed * interpolation)
The car will then be rendered at position 530.

I know it's just an example, but wouldn't that make the car speed dependent on the UPDATES_PER_SECOND? So more UPS would mean a faster car? This can't be right...?

Any help, tutorial, whatever is appreciated.

UPDATE / SOLUTION

After all the help (thanks!), here is what I am currently using - works pretty good so far (for moving a character sprite), but let's wait for the really complicated game stuff to decide this. Still, I wanted to share this.
My game loop now looks like this:

private static int UPDATES_PER_SECOND = 25;
private static int UPDATE_INTERVAL = 1000 / UPDATES_PER_SECOND * 1000000;
private static int MAX_FRAMESKIP = 5;

private long nextUpdate = System.nanoTime();

public void run() {
    while (true) {
        int skippedFrames = 0;
        while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) {
            long delta = UPDATE_INTERVAL;
            this.currentState = this.createGameState(delta);
            this.newPredictedNextState = this.createGameState(delta + UPDATE_INTERVAL, true);

            this.nextUpdate += UPDATE_INTERVAL;
            skippedFrames++;
        }

        double interpolation = (System.nanoTime() + UPDATE_INTERVAL - this.nextUpdate) / (double) UPDATE_INTERVAL;
        this.repaintGame(interpolation);
    }
}

As you can see, a few changes:

  • delta = UPDATE_INTERVAL ? Yes. This is experimental so far, but I think it will work. The problem is, as soon as you actually CALCULATE a delta from two timestamps, you are introducing float calculation errors. Those are small, but considering your update is called millions of times, they add up. And since the second while-loop makes sure we catch up on missed updates (in case the rendering takes long for example) we can be pretty sure we get our 25 updates per second. Worst case: We miss more than MAX_FRAMESKIP updates - in this case, updates will get lost and the game will lag. Still, like I said, experimental. I might change this to an actual delta again.
  • predictesNext game state? Yes. The GameState is the object holding all relevant game information, the renderer gets passed this object to render the game to the screen. In my case, I decided to give the renderer two states: The one we would normally pass him, with the current game state, and a predicted future state, UPDATE_INTERVAL in the future. This way the renderer can use the interpolation value to easily interpolate between both. Calculating the future game state is actually quite easy - since your update method (createGameState()) takes a delta value anyway, just increase the delta by UPDATE_INTERVAL - this way a future state will be predicted. The future state, of course, assumes user input etc. stays the same. If it doesn't, the next game state update will take care of the changes.
  • The rest stays pretty much the same and is taken from deWiTTERS game loop. MAX_FRAMESKIP is pretty much a failsafe in case the hardware is REALLY slow, to make sure we render something from time to time. But if this kicks in, we will have extreme lags anyway I guess. The interpolation is the same as before - but now the renderer can simply interpolate between two gamestates, it doesn't have to have any logic except interpolating numbers. That's nice!

Maybe for clarification, an example. Here is how I calculate the character position (simplified a little):

public GameState createGameState(long delta, boolean ignoreNewInput) {
    //Handle User Input and stuff if ignoreNewInput=false

    GameState newState = this.currentState.copy();
    Sprite charSprite = newState.getCharacterSprite();
    charSprite.moveByX(charSprite.getMaxSpeed() * delta * charSprite.getMoveDirection().getX()); 
    //getMoveDirection().getX() is 1.0 when the right arrow key is pressed, otherwise 0.0
}

... then, in the paint method of the window ...

public void paint(Graphics g) {
    super.paint(g);

    Graphics2D g2d = (Graphics2D) g;

    Sprite currentCharSprite = currentGameState.getCharacterSprite();
    Sprite nextCharSprite = predictedNextState.getCharacterSprite(); 
    Position currentPos = currentCharSprite.getPosition();
    Position nextPos = nextCharSprite.getPosition();
    //Interpolate position
    double x = currentPos.getX() + (nextPos.getX() - currentPos.getX()) * this.currentInterpolation;
    double y = currentPos.getY() + (nextPos.getY() - currentPos.getY()) * this.currentInterpolation;
    Position interpolatedCharPos = new Position(x, y);
    g2d.drawImage(currentCharSprite.getImage(), (int) interpolatedCharPos.getX(), (int) interpolatedCharPos.getY(), null);
}
Streak answered 26/2, 2013 at 22:9 Comment(0)
M
4

Don't base your game logic on the assumption that the update intervals are constant. Include a game clock that exactly measures the time that passed between two updates. You can then base all your calculations on the delay and don't have to worry about the actual update rate.

In this case the cars velocity would be given in units/second and the delta would be the total seconds since the last update:

car.position += car.velocity * delta;

It's a common practice to separate updating the game logic from drawing the frame. Like you said, this makes it possible to keep a steady update rate by skipping rendering a frame every now and then.

But it's not so much about keeping the update intervals constant. It is just really important to have a minimum number of updates per time unit. Imagine a vehicle moving really fast towards an obstactle. If the update frequency is too low, the travelled distance between two updates could be greater than the obstacles total size. The vehicle would move right through it.

Martini answered 26/2, 2013 at 22:30 Comment(8)
Thanks for the response. The thing that confuses me is that deWITTER proposes in his game loop to pass the delta (or the interpolation, which basically is calculated from the delta) only to the rendering function, but not the update function that takes care of the game logic. So are you saying that both functions should receive the delta value one way or another?Streak
I would say that the delta is only needed in the update method, since this is where you modify the game world. It should be sufficient that the render method only reads the game world model. It creates a representation of the world, but should not change its state; so the delta in the render method seems unnecessary.Martini
well, yes, I think I will just implement it that way then, as this makes more sense to me as well. As a sidenote, I still think passing the delta to the rendering can still be useful - it makes it possible to interpolate movements between two game updates. Otherwise if there were 25 game updates per second but 200 rendering updates per second, 175 renderings would basically go wasted. As you said, this could mean that a car drives "through" an obstacle, but it is likely that the update method is called often enough so this doesn't happen/is not noticed.Streak
You are right, I based my argument on the assumption that the number of rendering calls is always less or equal to the number of updates.Martini
I implemented this along with the interpolation model and it really helped smooth out my sprite movement, (however it isn't perfect). I am passing the interpolation value to my rendering method, as suggested. You're right, this is done because it needs to be calculated at render time rather than when the logic is updating. How did you get on with this? (I'm running at 30 updates per second, I am making an assumption that most, if not all devices out there will handle 30 game updates). I've also limited my rendering so it doesn't run away . I only render when I know there as been an update.Ruscio
since this is a free-time project I didn't have too much time to work on it, but my game loop now is as follows: updateGame() gets passed a time delta and calculates a new game state with it. additionally, it predicts the game state for the next update (e.g. where will the textures be in UPDATE_INTERVAL?). the render function can then simply interpolate between those two states - this way the important logic stays with the update, not the rendering.Streak
@Streak cool, so instead of compensating at time of rendering you're predicting ahead of time? How are you achieving this?Ruscio
I've added my original post with an explanation + little example :-) Remember I'm just a noob in game programming, so maybe this will cause major problems later on, but I hope not.Streak

© 2022 - 2024 — McMap. All rights reserved.