This subject has been touched on in other threads, particularly in the Articles & Tutorials sections, which I’ve looked through (and based my code on in fact). From what I understood, Timer (either util or Swing) is a bad idea for animation due to their lack of reliabile updating mechanisms. The seemingly “de facto” way is to use System.nanoTime() and compute your update and render time, accounting for the difference (whether fast or slow). It’s a sound method, and I’m understanding it the more I read.
I have two classes, GameLoop and GameWindow. I didn’t bother with a Canvas because java.awt.Window also implements a bufferstrategy and decided to forego adding an extra object/class to my code. Here is the relevant source:
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.swing.SwingUtilities;
public class GameLoop {
public static final long FRAME_INTERVAL = 41; // Equivalent to 24 frames per second
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
final GameWindow window = new GameWindow();
Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(
new Runnable(){
@Override
public void run() {
window.update(); // Perform update
window.repaint();
}
}, 0L, FRAME_INTERVAL, TimeUnit.MILLISECONDS);
}
});
}
}
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
@SuppressWarnings("serial")
public class GameWindow extends Window {
private int x = 0;
GameWindow() throws HeadlessException {
super(null, GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration());
if(!getGraphicsConfiguration().getDevice().isFullScreenSupported()) {
System.out.println("Your device does not support full screen exclusive mode");
System.exit(0);
}
addWindowListener(new WindowListener() {
@Override
public void windowOpened(WindowEvent e) {}
@Override
public void windowClosing(WindowEvent e) {
getGraphicsConfiguration().getDevice().setFullScreenWindow(null);
dispose();
}
@Override
public void windowClosed(WindowEvent e) {}
@Override
public void windowIconified(WindowEvent e) {}
@Override
public void windowDeiconified(WindowEvent e) {}
@Override
public void windowActivated(WindowEvent e) {
requestFocus();
}
@Override
public void windowDeactivated(WindowEvent e) {}
});
setBounds(getGraphicsConfiguration().getBounds());
setLayout(null);
setIgnoreRepaint(true);
getGraphicsConfiguration().getDevice().setFullScreenWindow(this); // Show full screen window
createBufferStrategy(2);
}
@Override
public void repaint() {
Graphics g = getBufferStrategy().getDrawGraphics();
g.clearRect(0, 0, getWidth(), getHeight());
paint(g);
g.dispose();
getBufferStrategy().show(); // Display the new graphics onto the canvas
getToolkit().sync();
}
@Override
public void paint(Graphics g) {
g.fillRect(x, 0, 20, 20);
}
public void update(){
x += x < 500 ? 5 : -500;
}
}
This works and all it does is paint a simple rectangle that moves along the upper portion of the screen. Nothing spectacular really, just something to help me get a grasp on thing.
My questions are the following:
Is it “wrong” to use an executor in this manner?
Is the executor a reliable timing mechanism?
As the next frame only executes once the prior frame has ended (due to the single thread scheduled execution) am I correct in assuming my frame rate is variable?
If the window.update() and window.render() finish prematurely, does the scheduler perform the equivalent waiting of Thread.sleep automatically?
The purpose of this is to develop some sort of reliable skeleton framework for animated games, that I understand and that isn’t just a copy/paste of someone else’s code. Use of an Executor in his fashion may obviously not be the best practice or an extremely obtuse or preposterous way of doing things as I haven’t seen it elsewhere (and there’s likely a reason for that). Any help or critiques about this code are welcome. Thanks for your input.