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 thanMAX_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 byUPDATE_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);
}