I recently tried to connect to game 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 suggested 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 cycle looked promising and easy, but now that I'm actually trying to do something about it, I'm not sure anymore. If I'm not completely mistaken, updateGame() takes care of things like calculating positions, moving enemies, calculating collisions, ...? Since interpolation is not passed to updateGame() , does this mean that we assume that updateGame() is called accurately and stably UPDATES_PER_SECOND once per second? Does this mean that all our calculations are based on this assumption? And this will not cause us many problems, if for any reason - the call to updateGame () is delayed?
For example, if my characteristic sprite should go correctly, and we move it according to its speed on each updateGame() - if the method should be delayed, does this mean that our calculations are simply disabled and the symbol will lag?
The following example for interpolation is indicated on the website:
If in 10Th gametick the position is 500 and speed is 100, then in 11Th gametick the position will be 600. So, where will you place your car when you display it? You could just take the position of the last gametric (in this case 500). But the best way is to predict where the car will be in the exact 10.3, and this happens as follows:
view_position = position + (speed * interpolation)
The vehicle will then be displayed at position 530.
I know this is just an example, but will car speed not affect UPDATES_PER_SECOND ? So would a larger UPS mean a faster car? It may not be right ...?
Any help, tutorial, anything appreciated.
UPDATE / DECISION
After all the help (thanks!), This is what I'm using right now - it still works pretty well (for moving the sprite symbol), but let the really complex game material wait to solve this. However, 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 still experimental, but I think it will work. The problem is that, as soon as you actually CALCULATE the delta from two timestamps, you enter float calculation errors. These are small, but given that your update is called millions of times, they add up. And since the second while loop ensures that we catch up with missed updates (in case the rendering takes a lot of time, for example), we can be sure that we get 25 updates per second. Worst case: we miss more than MAX_FRAMESKIP updates - in this case, updates are lost and the game will lag. However, as I said, experimental. I can change this to the actual delta again.- predicts the next state of the game? Yes. A GameState is an object that contains all the relevant information about the game; the visualization tool passes this object to display the game on the screen. In my case, I decided to provide the renderer with two states: one that we usually pass to it, with the current state of the game and the predicted future state,
UPDATE_INTERVAL in the future. Thus, rendering can use the interpolation value to easily interpolate between them. Calculating the future game state is actually quite simple - since your update method ( createGameState() ) takes a delta value anyway, just increase the delta by UPDATE_INTERVAL - this will predict the future state. The future state, of course, involves user input, etc. It remains unchanged. If this does not happen, the next game state update will take care of the changes. - The rest remains almost the same and is taken from the deWiTTERS game cycle.
MAX_FRAMESKIP is largely fault tolerant if the equipment is REALLY slow so that we display something from time to time. But if it hits, we will have extreme lags, I suppose. The interpolation is the same as before - but now the rendering can simply interpolate between two games, it should not have any logic other than interpolation of numbers. It's nice!
Maybe, for clarification, an example. This is how I calculate the position of the character (a bit simplified):
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 window draw method ...
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); }