patternjavaMinor
General Game Loop 2.0
Viewed 0 times
loopgamegeneral
Problem
Follow up from General Game Loop
I have two interfaces. Entity for ticks. Drawable for rendering.
Drawable.java
Entity.java
I have a class to initialize the window that everything will be displayed to.
Window.java
Then there is the main component.
Game.java
```
import java.util.ArrayList;
import java.util.List;
import Logging.*;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
public class Game extends Canvas implements Runnable {
/**
* Run, Tick, Render, Entities and Drawables
*/
private boolean isRunning = true;
private Thread thread;
private static final long NANOSECOND = 1000000000;
private static final double OPTIMAL_TICKS = 50.0;
private static final double OPTIMAL_TIME = NANOSECOND / OPTIMAL_TICKS;
private long lastLoopTime = System.nanoTime ();
private long currentTime;
private double deltaTime;
private long secondTimer = System.currentTimeMillis ();
private final List entities = new ArrayList<> ();
private final List drawables = new ArrayList<> ();
/**
* Window Constants
*/
private static final int WIDTH
I have two interfaces. Entity for ticks. Drawable for rendering.
Drawable.java
import java.awt.Graphics;
public interface Drawable
{
public void draw (Graphics g);
}Entity.java
public interface Entity
{
public void tick ();
public void second ();
}I have a class to initialize the window that everything will be displayed to.
Window.java
import java.awt.Dimension;
import javax.swing.JFrame;
public class Window
{
public Window (int width, int height, String title, Game game) {
game.setPreferredSize(new Dimension (width, height));
game.setMaximumSize(new Dimension (width, height));
game.setMinimumSize(new Dimension (width, height));
JFrame frame = new JFrame (title);
frame.add (game);
frame.pack ();
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
frame.setResizable (false);
frame.setLocationRelativeTo (null);
frame.setVisible (true);
}
}Then there is the main component.
Game.java
```
import java.util.ArrayList;
import java.util.List;
import Logging.*;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
public class Game extends Canvas implements Runnable {
/**
* Run, Tick, Render, Entities and Drawables
*/
private boolean isRunning = true;
private Thread thread;
private static final long NANOSECOND = 1000000000;
private static final double OPTIMAL_TICKS = 50.0;
private static final double OPTIMAL_TIME = NANOSECOND / OPTIMAL_TICKS;
private long lastLoopTime = System.nanoTime ();
private long currentTime;
private double deltaTime;
private long secondTimer = System.currentTimeMillis ();
private final List entities = new ArrayList<> ();
private final List drawables = new ArrayList<> ();
/**
* Window Constants
*/
private static final int WIDTH
Solution
Your code is pleasant to read, and easy enough to follow. That's a great start. There are a number of issues, mostly on the mild side, but I am concerned about the timing in the game loop.
Simple things
It was pointed out to me recently, that you can't trust
Nitpick:
The
FPSViewer
Game Loop
You have three periodic events that are happening:
Your loop is complicated, it uses nano-seconds for one timer, milliseconds for another, and it seems the timer for the 'tick' loop is broken....
Let's break your code down:
Fine, you're a runnable, in your own thread (which should have a name, by the way).
OK, we loop until told not to.
OK, get the current time, and preserve it for the next loop in
deltaTime is a
Once the ticks are done, you render/draw the components
and then you check for the expiry of a second.
To summarize:
There is no need to keep most of that information in the class level. Keeping it in the method should be fine.
The loop I would use is as follows:
Update: Alternate game loop
```
long nextTick = System.nanoTime();
long nextSecond = nextTick;
long nextRender = nextTick;
while (isRunning) {
final long now = System.nanoTime();
if (now - nextRender >= 0) {
// update the game
render ();
do {
nextRender += NANOS_PER_RENDER;
} while (now - nextRender >= 0);
}
if (now - nextTick >= 0) {
update();
// catch up with as many ticks as needed (skip them if necessary).
do {
nextTick += NANOS_PER_TICK;
} while (now - nextTick >= 0);
}
if (now - nextSecond >= 0) {
updatePerSecond();
// catch up with as many ticks as needed (no skipping
Simple things
It was pointed out to me recently, that you can't trust
System.currentTimeMillis()... if the system clock changes, you could skip forward, or backward in time. The effects here would be trivial, so it's not a huge deal... but just saying.Nitpick:
private static final long NANOSECOND = 1000000000; should have a long designator on the constant: 1000000000L. If it helps, too, in Java you can now have an underscore separator: 1_000_000_000L as well. Also, NANOSECOND is an odd name... should it be NANOS_IN_SECOND?The
Drawable, Entity, and Window all look fine.FPSViewer
FPSViewer looks odd. Two things:- Why the
/ 60? What is the meaning of that? From what I can tell,framesis the count ofdraw()calls in a second. Why divide it by 60?
- worse, dividing by 60 is an integer operation. You are doing massive truncation in this process.
Game Loop
You have three periodic events that are happening:
- each second there is a special 'event'
- each frame-period there is a 'draw'
- each other time there is a 'tick'
Your loop is complicated, it uses nano-seconds for one timer, milliseconds for another, and it seems the timer for the 'tick' loop is broken....
Let's break your code down:
public void run () {Fine, you're a runnable, in your own thread (which should have a name, by the way).
while (isRunning) {OK, we loop until told not to.
// get delta time
currentTime = System.nanoTime ();
deltaTime += (currentTime - lastLoopTime) / OPTIMAL_TIME;
lastLoopTime = currentTime;OK, get the current time, and preserve it for the next loop in
lastLoopTime, but what about the line in the middle: deltaTime += (currentTime - lastLoopTime) / OPTIMAL_TIME;?deltaTime is a
double value that does not actually measure a change in time (bad name), it measures the number of ticks to put in before the next 'draw'. The apparent target is 50.0 ticks per second, so, you see whether a full 'tick' worth of time has happened and you tick() as many times as needed, before the draw.while (deltaTime >= 1) {
update ();
deltaTime--;
}Once the ticks are done, you render/draw the components
// update the game
render ();and then you check for the expiry of a second.
// reset if a second has passed
if (System.currentTimeMillis () - secondTimer > 1000) {
updatePerSecond ();
secondTimer += 1000;
}
}
}To summarize:
- you have a tight loop (CPU at 100%, no idle time)
- you render on every loop
- you tick about 50-times per second
- you have a 1-second routine about once each second.
There is no need to keep most of that information in the class level. Keeping it in the method should be fine.
The loop I would use is as follows:
public void run () {
long nextTick = System.nanoTime();
long nextSecond = nextTick;
final long rateLimit = NANOS_PER_SECOND / 200; // most 200 frames per second....
while (isRunning) {
long loopStart = System.nanoTime();
// update the game
render ();
final long now = System.nanoTime();
if (now - nextTick >= 0) {
update();
// catch up with as many ticks as needed (skip them if necessary).
do {
nextTick += NANOS_PER_TICK;
} while (now - nextTick >= 0);
}
while (now - nextSecond >= 0) {
updatePerSecond();
// catch up with as many ticks as needed (no skipping!).
nextSecond += NANOS_PER_SECOND;
}
final long delayms = ((loopStart + rateLimit) - System.nanoTime()) / NANOS_PER_MILLISECOND;
if (delayms > 0) {
// more than a millisecond wait, do it....
try {
Thread.sleep(delayms);
} catch (InterruptedException ie) {
// ignore.
}
}
}
}Update: Alternate game loop
```
long nextTick = System.nanoTime();
long nextSecond = nextTick;
long nextRender = nextTick;
while (isRunning) {
final long now = System.nanoTime();
if (now - nextRender >= 0) {
// update the game
render ();
do {
nextRender += NANOS_PER_RENDER;
} while (now - nextRender >= 0);
}
if (now - nextTick >= 0) {
update();
// catch up with as many ticks as needed (skip them if necessary).
do {
nextTick += NANOS_PER_TICK;
} while (now - nextTick >= 0);
}
if (now - nextSecond >= 0) {
updatePerSecond();
// catch up with as many ticks as needed (no skipping
Code Snippets
public void run () {while (isRunning) {// get delta time
currentTime = System.nanoTime ();
deltaTime += (currentTime - lastLoopTime) / OPTIMAL_TIME;
lastLoopTime = currentTime;while (deltaTime >= 1) {
update ();
deltaTime--;
}// update the game
render ();Context
StackExchange Code Review Q#72198, answer score: 7
Revisions (0)
No revisions yet.