Hi all,
I’m having problems where my game loop falls behind on slower machines and is not able to keep up with the frame rate I have set. I’m working on a metronome application which I will be moving over to mobile devices, so having this program run on slower devices is very important as I want it to work well for anyone who uses it.
This code runs flawlessly as long as the machine is fast enough to keep up. I ran it for close to 20 minutes or so on a fast laptop and the visual stayed in perfect sync with the audio, no problems at all.
I’m using JLabels and sprite sheets for my animations, which as I understand means that I can’t use some of the fixes I’ve found for handling this problem on slower machines. Methods like variable frame rates or changing how far objects move during each update based on how fast the computer moves through the loop seem like they require motion to be based on physics calculations instead of sprite sheets.
I’ve tried a few different methods using a constant frame rate. Initially I tried using the Util Timer, but this was really bad on the slow machine and I had no way of allowing it to skip ahead when it fell behind since it does all the timing stuff itself. Then I tried my own version of checking the system time and checking to see if it has time to spare to sleep or if it needs to rush ahead by skipping over the paint method…this worked a little better, but would cause glitches in the graphic where it would freeze for a bit and then start moving again. Also, even when it was moving smoothly the timing was off from the audio.
The current method I’m using now is one I found in this article:
This also used the technique of skipping the paint method, and so far this method seems to produce the smoothest animations (no long freezing glitches), but this still doesn’t keep time correctly with the audio on slower machines.
The only change I made to the method used in the article is I removed the option to reset the nextTime variable if it falls too far behind. I have two frame counters going, one on the audio side and one on the visual side and it seems like if I were to reset nextTime on the visual side that would cause it to simply drop those frames instead of trying to make them up. Thus the visual frame counter would never catch back up to the audio’s frame counter and the two would be permanently out of sync.
Here’s the entire working example which I posted to pastebin in case anyone wants to run my code and/or tweak it.
http://pastebin.java-gaming.org/7bb7d715d0b13
And here is the animation I made for it:
animation.jpg
Here is just the visual loop part of my code, where I believe the problem is. Thanks!
// LOOP
public Runnable loop = new Runnable(){
public void run(){
nextTime = System.currentTimeMillis();
while (isPlaying){
currentTime = System.currentTimeMillis();
if (currentTime >= nextTime){
update();
nextTime += targetLoopDelay;
if ((currentTime < nextTime) || (skippedFrames > maxSkippedFrames)){
edtPaint();
skippedFrames = 1;
}
else{
skippedFrames++;
}
frameCount++;
}
else{
sleepTime = (nextTime - currentTime);
try {
Thread.sleep(sleepTime);
} catch(InterruptedException ie){}
}
}
}
};
// UPDATE
public void update(){
for (int i=0; i<2; i++){
updateAnimation(i);
updateBeatDisplay(i);
}
}
public void updateAnimation(int i){
if (frameCount >= animationFrame[i]){
animationPanel[i].setSubImage(imageIndex[i]);
imageIndex[i] += rightDirection[i] ? 1 : -1;
if (imageIndex[i] == 0) {
rightDirection[i] = true;
}
if (imageIndex[i] == 8) {
rightDirection[i] = false;
}
animationFrame[i] += (long)(frameRate / 8);
// division rounding fix
if (frameCount % 25 == 0){
animationFrame[i]++;
}
}
}
public void updateBeatDisplay(int i){
if (frameCount >= beatDisplayFrame[i]){
currentColor[i] = (int)(labelIndex[i] % 2);
currentCount[i] = (int)((labelIndex[i] % 4) + 1);
labelIndex[i]++;
beatDisplayFrame[i] += frameRate;
}
}
// PAINT
public void edtPaint(){
final int[] finalCount = currentCount;
final int[] finalColor = currentColor;
SwingUtilities.invokeLater(new Runnable(){
public void run(){
for (int i=0; i<2; i++){
paintAnimation(i);
paintBeatDisplay(i, finalCount, finalColor);
}
}
});
}
public void paintAnimation(int i){
animationPanel[i].repaint();
}
public void paintBeatDisplay(int i, int[] finalCount, int[] finalColor){
beatDisplay[i].setText(""+finalCount[i]);
beatDisplay[i].setBackground(colors[finalColor[i]]);
}