I have played around with some code from http://legion.gibbering.net/golem/tutorial_loop.html and got a working demonstration for the concept in Java Web Start:
http://www.javaengines.dk/test/TestTimer.jnlp.php shows 2 balls, both being updated at 4 fps but rendering as fast as possible.
1 ball is interpolated between last and next positions, the other isn’t.
When you click on the screen the balls are set back a few pixels - notice that the first ball moves back instead of jumping there too, because of the interpolation.
I’ll probably port to GAGETimer if System.currentTimeMillis won’t cut the cake.
Please send comments or questions, I’m pretty new to this kind of timing myself and made this just to test the concept before using it in my own game.
(I figure I have to decouple now, because I want logic framerate as low as 8-10 fps to “eat” some of the network latency while still maintaining a smooth “feel” of the game)
TestTimer.java:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class TestTimer extends JPanel implements Runnable
{
private Point aLast = new Point(0, 20);
private Point aNext = new Point(aLast);
private Point b = new Point(0, 50);
private float percentWithinTick = 0f;
public TestTimer()
{
addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
aNext.x -= 30;
b.x -= 30;
}
});
}
public synchronized void paintComponent(Graphics g)
{
System.out.println("paint pwt=" + percentWithinTick);
g.clearRect(0, 0, getWidth(), getHeight());
int ax = aLast.x + (int) (percentWithinTick * (aNext.x - aLast.x));
int ay = aLast.y + (int) (percentWithinTick * (aNext.y - aLast.y));
g.fillOval(ax, ay, 10, 10);
g.fillOval(b.x, b.y, 10, 10);
notifyAll();//done painting
}
public void logicUpdate()
{
System.out.println("Logic");
aLast.x = aNext.x;
aLast.y = aNext.y;
aNext.x += 5;
b.x += 5;
}
public synchronized void run()
{
long time0, time1;
int numLoops;
int TICK_TIME = 250;
int MAX_LOOPS = 2;
time0 = System.currentTimeMillis();
while (true)
{
time1 = System.currentTimeMillis();
numLoops = 0;
while ((time1 - time0) > TICK_TIME && numLoops < MAX_LOOPS)
{
logicUpdate();
time0 += TICK_TIME;
numLoops++;
}
percentWithinTick = (time1 - time0) / (float) TICK_TIME;
if (percentWithinTick > 1)
percentWithinTick = 1;
repaint();
try
{
//System.out.println(System.currentTimeMillis());
//Deal with the asynchronous call to repaint()
//duration to wait for shouldn't matter, but if
//paintComponent for some reason isn't called
//TICK_TIME is probably the best answer
wait(TICK_TIME);
//System.out.println(" " + System.currentTimeMillis());
} catch (InterruptedException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
}
public static void main(String[] args)
{
final TestTimer timer = new TestTimer();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(timer, BorderLayout.CENTER);
f.setSize(800, 600);
JPanel p = new JPanel();
JButton b = new JButton("Start");
p.add(b);
f.getContentPane().add(p, BorderLayout.SOUTH);
b.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
Thread t = new Thread(timer);
t.start();
}
});
f.setVisible(true);
}
}