You are on page 1of 14

Java-Gaming.

org

Featured games (81)
games approved by the League of Dukes
Games in Showcase (499)
Games in Android Showcase (118)
games submitted by our members
Games in WIP (568)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials Search

HOME

HELP SEARCH LOGIN REGISTER

Java-Gaming.org > Game Development > Articles & tutorials > Game loops!
Pages: [1] 2 3
IGNORE | PRINT

Game loops!
There are a lot of different sorts of game loops you can use. In general, you can think of a game loop as
having 3 different pieces:
1) Some sort of, well... loop. From timer calls to while loops to recursive function calls. (the loop)
2) A way to delay the length of each loop iteration so you get a frame rate you like. (the timing
mechanism)
3) A way to make the game speed remain independent of the speed of the loop. (the interpolation)
Bad Loops
Let's start with some pretty ugly ones I've seen that you definitely shouldn't do:
1
2
3
4
5
6
7
8
9
public void gameLoop()
{
while(true) //the loop
{
doGameUpdates();
render();
for (int i = 0; i < 1000000; i++) ; //the timing mechanism
}
}
OMG so bad. Don't ever do that. I've literally seen it in people's games though. Aside from maxing out your
processor, that's going to be a completely varying length of time for each machine and even might have
variation on your machine. Bad bad bad. You can also see that this is missing a method of interpolation, but
there's no way to provide that when it doesn't measure time in any way!
1
2
3
4
5
6
7
8
9
public void gameLoop()
{
while(true) //the loop
{
doGameUpdates();
render();
Thread.sleep(1); //the timing mechanism
}
}
This is better, but still not great. Thread.sleep is not always going to be accurate, plus if you've got any
other threads hogging processor it won't necessarily allow your thread to resume in time. From the java
docs:
Quote
Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and
accuracy of system timers and schedulers.
Also, this is missing interpolation. That's because this sort of loop assumes that Thread.sleep() is actually
going to be 100% accurate, but we know it isn't.
Another issue that we see with both of these loops is the self-defined infinite loop that is "while(true)." We
know that this loop can never ever end. It's impossible. I will absolutely advise against doing this, ever. It will
only cause problems. Even though, conceptually, we don't want this loop ever to end, it's a good idea to pop
a boolean in there so you at least have the option if killing the loop.
1 private boolean isRunning;
Eli Delventhal
2
3
4
5
6
7
8
9
10
11
public void gameLoop()
{
while(isRunning) //the loop
{
doGameUpdates();
render();
Thread.sleep(1); //the timing mechanism
}
}
Great, now we can set isRunning to false whenever we want to stop the game loop, or pause the game, or
anything like that.
Here's a crazy recursive way of doing the same loop:
1
2
3
4
5
6
7
8
9
10
11
12
private boolean isRunning;
public void gameLoop()
{
doGameUpdates();
render();
Thread.sleep(1); //the timing mechanism
if (isRunning)
{
gameLoop(); //the loop
}
}
I don't know why'd you ever do that, but it's another thing I've seen so I've included it for completion's sake.
Aside from being not obvious, I think this will eventually cause a stack overflow (someone correct me if I'm
wrong).
Decent loops, but I still wouldn't use them
I think I've seen the approach of using either java.util.Timer or javax.swing.Timer more often than any other
approach (at least in amateur projects). This is nice because it saves you from having to deal with any part
of the loop yourself, and it's more or less accurate. The reason for this is that neither of the two Timer
classes are intended to be used as heavy-lifting tasks. java.util.Timer specifically says "Timer tasks should
complete quickly. If a timer task takes excessive time to complete, it "hogs" the timer's task execution
thread" in the Java docs. Even better, javax.swing.Timer is meant to be used with Swing (go figure), so it is
not at all a reliable timing solution. It fires all events on the EDT (event-dispatching-thread), which is used
for all Swing events. So, your action might be fired after a lot of other things, thereby resulting in a very
unpredictable timing solution.
Still, avoiding the overhead of dealing with your own timing system can be nice.
java.util.Timer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private java.util.Timer timer;
private boolean isRunning;
public void gameLoop()
{
timer = new Timer();
timer.schedule(new LoopyStuff(), 0, 1000 / 60); //new timer at 60 fps, the timing mechanism
}
private class LoopyStuff extends java.util.TimerTask
{
public void run() //this becomes the loop
{
doGameUpdates();
render();
if (!isRunning)
{
timer.cancel();
}
}
}
I won't bother laying out javax.swing.Timer, because it is totally suck. Don't use it!
Good game loops
So, in all those examples, we've got issues with the reliability of timers. If your game logic is only updating
at 30 times per second, how do you draw anything at 60 fps? Similarly, if your fps is down to 10, how do you
keep the updates at 30? Or, if your game is updating at 70 times per second one frame and 10 times per
second another frame, how do you ensure that the game speed is consistent for players?
Remember that third component of a game loop that we haven't used yet? That's right, we need to use
interpolation.
In terms of your loop, you have two options:
- Variable timestep
- Fixed timestep
Which one you use depends on personal preference and also what sort of things you are doing in your
loops.
Variable timestep loops are great because the game will seem consistent regardless of how fast the
player's computer is. allowing you to potentially cater to lots of different types of machines. They also often
allow you to update the game logic with very high granularity, that can make a much better experience in
certain games. Things are drawn as they change position, so there is no graphical latency whatsoever.
Fixed timestep loops are great because you know that every single timestep will take up the exact same
length of time. This means you will always have consistent gameplay, regardless of how fast a machine is.
This works wonderfully for math-heavy games, like physics operations, and is also my loop of choice for
networked games so that you know packets are going and coming at a generally constant speed. You also
can keep the game logic running at a very low rate while the frame rate can still get extremely high, albeit
with one frame of latency.
You can't really use variable timestep well in physics simulations, and fixed timestep fails in situations
where your game can be interrupted or on machines that are too slow to hit your fixed rate.
Here's where the interpolation comes in:
- If you are using a variable stepped loop, then you need to update the game different amounts depending
on how long recent updates took. You will use a delta value to do this, which you multiply times every single
value that updates based on time (think of things like velocity, position, attack rate, and the like). A delta of
1.0 means that your loop took as long as you normally expect (an "optimal" amount of time), where as a
delta of < 1 means that the loop is going faster than optimal, and a delta of > 1 means that it's slower.
- If you are using a fixed timestep, then you have three options: you can either have a very high fixed
update (which lowers the number of machines that can reliably run your game), you can have a very low
frame rate (if the positions of your characters are only updating 20 times per second, then no matter how
fast the rendering is going it's only going to render those 20 frames), or you can interpolate the most recent
update to the current one over each render. That sounds confusing, but it's not hard to implement, and it
gives you yummy butter smoothness!
Both obviously have some caveats you're going to need to worry about. With the former, you need to
multiply every single time-based updated values by the delta. This is a pain in the butt and it's pretty easy to
forget to multiply by the delta sometimes, but it's reliable and it works. In the latter, you've got to multiply all
time-based rendered values by the interpolation amount. Also a pain in the butt!
Personally, I usually use a fixed timestep loop, because it's generally less to think about. My logic is almost
always much more complicated than my rendering, and you can usually abstract out the interpolation so
that you don't have to worry about it more than once. However, I'd use whatever makes sense to you!
Without further ado, here are implementations:
Variable timestep (credit goes to Kevin Glass on his site, with heavy changes)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void gameLoop()
{
long lastLoopTime = System.nanoTime();
final int TARGET_FPS = 60;
final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
// keep looping round til the game ends
while (gameRunning)
{
// work out how long its been since the last update, this
// will be used to calculate how far the entities should
// move this loop
long now = System.nanoTime();
long updateLength = now - lastLoopTime;
lastLoopTime = now;
double delta = updateLength / ((double)OPTIMAL_TIME);
// update the frame counter
lastFpsTime += updateLength;
fps++;

// update our FPS counter if a second has passed since
// we last recorded
if (lastFpsTime >= 1000000000)
{
System.out.println("(FPS: "+fps+")");
lastFpsTime = 0;
fps = 0;
}

// update the game logic
doGameUpdates(delta);

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// draw everyting
render();

// we want each frame to take 10 milliseconds, to do this
// we've recorded when we started the frame. We add 10 milliseconds
// to this and then factor in the current time to give
// us our final value to wait for
// remember this is in ms, whereas our lastLoopTime etc. vars are in ns.
try{Thread.sleep( (lastLoopTime-System.nanoTime() + OPTIMAL_TIME)/1000000 )};
}
}
private void doGameUpdates(double delta)
{
for (int i = 0; i < stuff.size(); i++)
{
// all time-related values must be multiplied by delta!
Stuff s = stuff.get(i);
s.velocity += Gravity.VELOCITY * delta;
s.position += s.velocity * delta;

// stuff that isn't time-related doesn't care about delta...
if (s.velocity >= 1000)
{
s.color = Color.RED;
}
else
{
s.color = Color.BLUE;
}
}
}
Fixed timestep (credit goes to me, this includes an example with a ball bouncing around so that you can
clearly see how interpolation works)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GameLoopTest extends JFrame implements ActionListener
{
private GamePanel gamePanel = new GamePanel();
private JButton startButton = new JButton("Start");
private JButton quitButton = new JButton("Quit");
private JButton pauseButton = new JButton("Pause");
private boolean running = false;
private boolean paused = false;
private int fps = 60;
private int frameCount = 0;

public GameLoopTest()
{
super("Fixed Timestep Game Loop Test");
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
JPanel p = new JPanel();
p.setLayout(new GridLayout(1,2));
p.add(startButton);
p.add(pauseButton);
p.add(quitButton);
cp.add(gamePanel, BorderLayout.CENTER);
cp.add(p, BorderLayout.SOUTH);
setSize(500, 500);

startButton.addActionListener(this);
quitButton.addActionListener(this);
pauseButton.addActionListener(this);
}

public static void main(String[] args)
{
GameLoopTest glt = new GameLoopTest();
glt.setVisible(true);
}

public void actionPerformed(ActionEvent e)
{
Object s = e.getSource();
if (s == startButton)
{
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
running = !running;
if (running)
{
startButton.setText("Stop");
runGameLoop();
}
else
{
startButton.setText("Start");
}
}
else if (s == pauseButton)
{
paused = !paused;
if (paused)
{
pauseButton.setText("Unpause");
}
else
{
pauseButton.setText("Pause");
}
}
else if (s == quitButton)
{
System.exit(0);
}
}

//Starts a new thread and runs the game loop in it.
public void runGameLoop()
{
Thread loop = new Thread()
{
public void run()
{
gameLoop();
}
};
loop.start();
}

//Only run this in another Thread!
private void gameLoop()
{
//This value would probably be stored elsewhere.
final double GAME_HERTZ = 30.0;
//Calculate how many ns each frame should take for our target game hertz.
final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
//At the very most we will update the game this many times before a new render.
//If you're worried about visual hitches more than perfect timing, set this to 1.
final int MAX_UPDATES_BEFORE_RENDER = 5;
//We will need the last update time.
double lastUpdateTime = System.nanoTime();
//Store the last time we rendered.
double lastRenderTime = System.nanoTime();

//If we are able to get as high as this FPS, don't render again.
final double TARGET_FPS = 60;
final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;

//Simple way of finding FPS.
int lastSecondTime = (int) (lastUpdateTime / 1000000000);

while (running)
{
double now = System.nanoTime();
int updateCount = 0;

if (!paused)
{
//Do as many game updates as we need to, potentially playing catchup.
while( now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER
{
updateGame();
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}

//If for some reason an update takes forever, we don't want to do an insane number of catchups.
//If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if ( now - lastUpdateTime > TIME_BETWEEN_UPDATES)
{
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}

//Render. To do so, we need to calculate interpolation for a smooth render.
float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES
drawGame(interpolation);
lastRenderTime = now;

//Update the frames we got.
int thisSecond = (int) (lastUpdateTime / 1000000000);
if (thisSecond > lastSecondTime)
{
System.out.println("NEW SECOND " + thisSecond + " " + frameCount);
fps = frameCount;
frameCount = 0;
lastSecondTime = thisSecond;
}

//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
while ( now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES
{
Thread.yield();

//This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
//You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
//FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
try {Thread.sleep(1);} catch(Exception e) {}

now = System.nanoTime();
}
}
}
}

private void updateGame()
{
gamePanel.update();
}

private void drawGame(float interpolation)
{
gamePanel.setInterpolation(interpolation);
gamePanel.repaint();
}

private class GamePanel extends JPanel
{
float interpolation;
float ballX, ballY, lastBallX, lastBallY;
int ballWidth, ballHeight;
float ballXVel, ballYVel;
float ballSpeed;

int lastDrawX, lastDrawY;

public GamePanel()
{
ballX = lastBallX = 100;
ballY = lastBallY = 100;
ballWidth = 25;
ballHeight = 25;
ballSpeed = 25;
ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
}

public void setInterpolation(float interp)
{
interpolation = interp;
}

public void update()
{
lastBallX = ballX;
lastBallY = ballY;

ballX += ballXVel;
ballY += ballYVel;

if (ballX + ballWidth/2 >= getWidth())
{
ballXVel *= -1;
ballX = getWidth() - ballWidth/2;
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
}
else if (ballX - ballWidth/2 <= 0)
{
ballXVel *= -1;
ballX = ballWidth/2;
}

if (ballY + ballHeight/2 >= getHeight())
{
ballYVel *= -1;
ballY = getHeight() - ballHeight/2;
ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
}
else if (ballY - ballHeight/2 <= 0)
{
ballYVel *= -1;
ballY = ballHeight/2;
}
}

public void paintComponent(Graphics g)
{
//BS way of clearing out the old rectangle to save CPU.
g.setColor(getBackground());
g.fillRect(lastDrawX-1, lastDrawY-1, ballWidth+2, ballHeight+2);
g.fillRect(5, 0, 75, 30);

g.setColor(Color.RED);
int drawX = (int) ((ballX - lastBallX) * interpolation + lastBallX - ballWidth/2);
int drawY = (int) ((ballY - lastBallY) * interpolation + lastBallY - ballHeight/2);
g.fillOval(drawX, drawY, ballWidth, ballHeight);

lastDrawX = drawX;
lastDrawY = drawY;

g.setColor(Color.BLACK);
g.drawString("FPS: " + fps, 5, 10);

frameCount++;
}
}

private class Ball
{
float x, y, lastX, lastY;
int width, height;
float xVelocity, yVelocity;
float speed;

public Ball()
{
width = (int) (Math.random() * 50 + 10);
height = (int) (Math.random() * 50 + 10);
x = (float) (Math.random() * (gamePanel.getWidth() - width) + width/2);
y = (float) (Math.random() * (gamePanel.getHeight() - height) + height/2);
lastX = x;
lastY = y;
xVelocity = (float) Math.random() * speed*2 - speed;
yVelocity = (float) Math.random() * speed*2 - speed;
}

public void update()
{
lastX = x;
lastY = y;

x += xVelocity;
y += yVelocity;

if (x + width/2 >= gamePanel.getWidth())
{
xVelocity *= -1;
x = gamePanel.getWidth() - width/2;
yVelocity = (float) Math.random() * speed*2 - speed;
}
else if (x - width/2 <= 0)
{
xVelocity *= -1;
x = width/2;
}

if (y + height/2 >= gamePanel.getHeight())
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
{
yVelocity *= -1;
y = gamePanel.getHeight() - height/2;
xVelocity = (float) Math.random() * speed*2 - speed;
}
else if (y - height/2 <= 0)
{
yVelocity *= -1;
y = height/2;
}
}

public void draw(Graphics g)
{

}
}
}
Use whatever type of loop makes sense to you, but make it once and then reuse it everywhere! I've even
seen people use both fixed timestep and variable timesteps together in the same game - do whatever
makes sense!
And I think that's it. Please everyone, include questions, comments, and corrections. Much of this code has
not been run, and I'm sure there will be a lot of opinions about adjustments.
Eli Delventhal
One weakness of that fixed timestep that could be implemented:
If your game lags or dips for a period of time, it will play catch up until it matches the amount of lost time. I have a fix for that
somewhere, it's pretty easy. If I get around to it I'll edit the post.
ra4king
Oh wow! I love your last example of the loop playing catch up until it matches the amount of lost time! I will implement that in my
game engine. Thanks!
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
dishmoth
Hi, Eli. Nice work on the tutorial. How about making it the first post in the shiny new Articles & tutorials section?
Here are some random comments and bits of feedback for you to disagree with and ignore.
- It's down to personal preference of course, but for variable time steps I prefer to pass the elapsed time in seconds (a double
obviously) to the update function, rather than a 'delta' relative to a nominal time step (1/60th of second in your code). My
reasons: If you've got a game object that works on a timer (e.g., a bomb that detonates after five seconds) then it's nice if the
code doesn't have to keep converting back and forth between seconds and nominal time steps. Also, you don't have to rewrite lots
of code if you want to change the value of the nominal time step. As I say, personal preference, but I thought I'd make the case
for the alternative approach.
- It took me a while to realise that you're talking about fixed time steps with respect to the update function only. I would argue
that a game loop that uses fixed time steps for both updating and rendering is the most newbie-friendly approach. Any chance of
including an example of that in the tutorial? I know that getting perfectly smooth results that way can be difficult, but I think it's
better for beginners than trying to struggle with either variable time steps in the update function or interpolation in the render
function.
- How about making all examples implement the same bouncing ball demo? That would let people compare the different
approaches more easily. It would be even better if there were links to the examples running as applets/webstart so people have
evidence that the code works as advertised on their machine.
- (I'm not entirely sure this is a good idea, but if in the example update functions you added long delays (sleeps) at random
intervals, that would demonstrate how good the game loop is at remaining smooth in adverse conditions.)
- Is there a reason for not using BufferStrategy in the examples?
- Gravity.ACCELERATION not Gravity.VELOCITY in the variable time step example.
- I think it's important in a variable time step loop to impose a maximum on the time step value passed to the update function
(1/10th of a second, say). If the game hits a delay longer than the maximum then it's not going to feel smooth whatever you do,
and I bet that a lot of update functions will behave strangely when the time step is unexpectedly long (bullets will jump straight
over aliens, Mario will fall through the platform below him, etc.).
- In the variable time step example, the argument to Thread.sleep() can go negative. I don't know how the function reacts to that
(although presumably it does nothing).
- The Ball class in the fixed time step example is never used.
- In the fixed time step example, I don't think it's possible for the following clause to be executed (now is always greater than
lastUpdateTime).
1
2
3
4
if (lastUpdateTime - now > TIME_BETWEEN_UPDATES)
{
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
- I've never tried using interpolation in the render function, but I can't help feeling that it might introduce some strange glitches
unless you're very careful. For instance, if you're interpolating between the player being alive in one position, and dead in another
position, there's a danger that the player's corpse might appear to jump around the screen briefly. Is this a problem in your
experience?
Cheers,
Simon
ra4king
Adding on Simon's (dishmoth's) post, you are depending on two threads for game loop and drawing which could cause some
trouble since it might be in the middle of redrawing when it sets a new interpolation value and thus your redraw is completely
thrown off. Maybe synchronized the methods or just use 1 thread for logic and render?
Eli Delventhal
Thanks for all those comments, Simon! I'll definitely put some of them in as I get the time, especially putting the ball example in
for other examples. I agree that's a great idea, especially having applets bundled with them too.
@ra4king: Which example are you referring to? I don't think any of them have a separate thread for rendering and updating.
ra4king
You call repaint(), which means you use passive rendering, depending on the EDT (Event Dispatching Thread) to call
paintComponent(). Your game loop, meanwhile is in another thread that you start inside runGameLoop(). It is recommended, for
more professional games that use Java2D, to use BufferStrategy and have game logic and rendering in the same thread.
EDIT: Oh and I am talking about your Bouncing Ball example
Eli Delventhal
Quote from: ra4king on May 15, 2011
You call repaint(), which means you use passive rendering, depending on the EDT (Event Dispatching Thread) to call
paintComponent(). Your game loop, meanwhile is in another thread that you start inside runGameLoop(). It is recommended, for
more professional games that use Java2D, to use BufferStrategy and have game logic and rendering in the same thread.
EDIT: Oh and I am talking about your Bouncing Ball example
Ah yes, this is true. I merely did that because it was quick to implement and I figured the most newbies would understand the
paintComponent/repaint model. I had to include something actually happening to illustrate how to do interpolation with your
drawing. But who am I kidding, people will likely just copy/paste the code, so I'll put in a better method at some point.
ra4king
Quote from: Eli Delventhal on May 16, 2011
Quote from: ra4king on May 15, 2011
You call repaint(), which means you use passive rendering, depending on the EDT (Event Dispatching Thread) to call
paintComponent(). Your game loop, meanwhile is in another thread that you start inside runGameLoop(). It is recommended, for
more professional games that use Java2D, to use BufferStrategy and have game logic and rendering in the same thread.
EDIT: Oh and I am talking about your Bouncing Ball example
Ah yes, this is true. I merely did that because it was quick to implement and I figured the most newbies would understand the
paintComponent/repaint model. I had to include something actually happening to illustrate how to do interpolation with your
drawing. But who am I kidding, people will likely just copy/paste the code, so I'll put in a better method at some point.
Yes I was fearing the copy/paste part
philfrei
I've been doing some testing over at this link, a complaint about jitteriness in a game loop:
http://www.java-gaming.org/topics/slight-jerkiness/24311/view.html
I wish to toss some comments and challenges to Eli!
1) for using a util.Timer, consider using the form that tries to keep the scheduled repeats based on the starting time, as in:
1 myTimer.scheduleAtFixedRate(new GameLoopIteration(), 0, 20);
The usual method, "myTimer.shedule(new GameLoopIteration(), 0, 20);" bases the next iteration time on the completion of the
previous iteration, and thus can drift. "ScheduleAtFixedRate" will still be stuck with using the OS time granularity, but it will
alternate between the enclosing trigger times. Example: timer repeat request: 20msec. System clock granularity: 15msec. The
timer will trigger either at intervals of 15 or 30, depending upon whether it is running ahead or behind.
2) Sleep is still dependent upon the constraints of the system, even if you have "Thread.sleep(1)" as you do in your "good" game
loops. According to my tests, it seems there is no guarantee that the sleep will not occasionally last a full increment of the OS
timing system. As I show (reply #15 in the above quoted thread) in some nano measurements of sleep(1), the sleep occasionally
lasts 15msec. (The 15msec granularity I get is part of Windows XP OS.)
I have an idea that I am going to try as an experiment, to schedule absolute times via a util.Timer. Let's see if the initiation of a
scheduled TimerTask, given an absolute time, is implemented to be more accurate than the OS constraints. It will be like laying
train tracks just ahead of the moving train. I did it before with some sound structures.
EDIT: nope, laying out absolute times for the timer tasks at 20msec intervals still resulted in iterations going back and forth
between 15.5 and 31 msec iterations.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Eli Delventhal
Thanks a lot for the comments and challenge. It is indeed a challenge to get it absolutely perfect, and I agree that using Timer is
not the best way to go. Instead, I would use yield() and one of the last two loops I posted, and maybe put in Thread.sleep(1) if
you're worried about using too much processor.
ra4king
Quote from: philfrei on July 27, 2011
2) Sleep is still dependent upon the constraints of the system, even if you have "Thread.sleep(1)" as you do in your "good" game
loops. According to my tests, it seems there is no guarantee that the sleep will not occasionally last a full increment of the OS
timing system. As I show (reply #15 in the above quoted thread) in some nano measurements of sleep(1), the sleep occasionally
lasts 15msec. (The 15msec granularity I get is part of Windows XP OS.)
Try having a thread that sleeps forever like this:
1
2
3
4
5
6
7
8
new Thread() {
public void run() {
try{
Thread.sleep(Long.MAX_VALUE);
}
catch(Exception exc) {}
}
}.start();
That forces Java to use the high resolution timer, which makes sleep(1) MUCH more accurate.
philfrei
Perils of running a conversation in two places...the code shown by avm1979 tested to work perfectly for me. I put some test results
on the other thread...mentioned prior.
@ra4king, your example worked perfectly, as well! Every sleep measured out to within a single msec. That's good enough for me.
So, appreciations to you both.
Also, after reading your reply, I did a search on "high resolution Timer java" and found this thread:
http://www.java-gaming.org/index.php?topic=1071.0
So, it seems sun.misc.Perf is still around. I found it in the basic library: rt.jar. I'm going to test that next. Also, came across the
suggestion that a ScheduledThreadPoolExecutor is an improved Timer and works. More stuff to test!
P.S., avm1979's idea of making the background sleeper a daemon makes good sense, and if you aren't already doing this, consider
adding that fine point. Example on the link below, post #16.
http://www.java-gaming.org/topics/slight-jerkiness/24311/view.html
ra4king
Ah yes I have seen both posts.
And what's a daemon and how would it help? O_o
cylab
Quote from: ra4king on July 28, 2011
Ah yes I have seen both posts.
And what's a daemon and how would it help? O_o
http://www.jguru.com/faq/view.jsp?EID=43724
ra4king
Quote from: cylab on July 28, 2011
Quote from: ra4king on July 28, 2011
Ah yes I have seen both posts.
And what's a daemon and how would it help? O_o
http://www.jguru.com/faq/view.jsp?EID=43724
Ah makes sense thanks!
Rejechted
I have a bit of an issue when your game consists of more than just bodies moving at fixed (non changing) velocities.
Say you introduce acceleration. Suddenly, linearly interpolating the drawx and drawy doesn't make any sense, since the velocity of
your ball has changed between frames A and B.
Additionally, what happens when you start to introduce shifts in color over time? Say we have an effect that is placed on an entity
that changes its texture color from white to red slowly over 5 seconds. Obviously its more noticeable when things are jittery
position-wise, but in a truly interpolated fixed timestep system, is the only way to achieve perfect accuracy to interpolate each and
every thing that can undergo a change before it is drawn?
philfrei
You have to keep in mind that human senses are limited. Certain mathematical niceties are simply too small to discriminate. So, in
a lot of situations linear interpolation is good enough, especially if you have a healthy frame rate going.
[Added] There is a minimum color distance that is observable as a discontinuity. This minimum distance might be numerically
different depending upon where in the color space you are located. Often the relationship between perception and the numbers
used to traverse a space is logarithmic rather than linear.
Also, there are a number of "color spaces" to choose from besides RGB for color traversal, which has to be considered.
In any case, the amount of change between frames should to be below the amount that triggers a perceptual discontinuity, or you
will get some sort of artifact. So you have to be responsible for knowing what that minimum is and providing some sort of throttle
or max delta function (if you want to avoid the discontinuities).
Rejechted
Right. So I guess by the sound of that, the interpolation of positions of bodies/images/particle emitters in the game, as well as
angles, is enough for human detection. I guess this makes sense, especially when you consider you're talking about sixtieths of a
second.
What about interpolating the position of the viewport for a GL Lookat call?
Eli Delventhal
It doesn't matter what you do as long as you have one frame of latency. You interpolate between whatever the last displayed
values were (position/color/scale) and the current ones. Whatever caused the changes in positions doesn't matter because you're
only ever directly representing position itself.
Make sense?
Chromanoid
At least the fixed timestep loop has some jittering related to the waiting at the end. It would be good to mention this in the OP
since people are using this code: http://forums.tigsource.com/index.php?topic=22054.0
Chromanoid
I made the following gameloop. It doesn't stutter on my system. However it doesn't sleep as much as other ones.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferStrategy;
public class Game extends javax.swing.JFrame {
private static final long serialVersionUID = 1L;
/* difference between time of update and world step time */
float localTime = 0f;
/** Creates new form Game */
public Game() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
this.setSize(800, 600);
}
/**
* Starts the game loop in a new Thread.
* @param fixedTimeStep
* @param maxSubSteps maximum steps that should be processed to catch up with real time.
*/
public final void start(final float fixedTimeStep, final int maxSubSteps) {
this.createBufferStrategy(2);
init();
new Thread() {
{
setDaemon(true);
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
}
@Override
public void run() {
long start = System.nanoTime();
while (true) {
long now = System.nanoTime();
float elapsed = (now - start) / 1000000000f;
start = now;
internalUpdateWithFixedTimeStep(elapsed, maxSubSteps, fixedTimeStep);
internalUpdateGraphicsInterpolated();
if (1000000000 * fixedTimeStep - (System.nanoTime() - start) > 1000000) {
try {
Thread.sleep(0, 999999);
} catch (InterruptedException ex) {
}
}
}
}
}.start();
}
/**
* Updates game state if possible and sets localTime for interpolation.
* @param elapsedSeconds
* @param maxSubSteps
* @param fixedTimeStep
*/
private void internalUpdateWithFixedTimeStep(float elapsedSeconds, int maxSubSteps, float fixedTimeStep
int numSubSteps = 0;
if (maxSubSteps != 0) {
// fixed timestep with interpolation
localTime += elapsedSeconds;
if (localTime >= fixedTimeStep) {
numSubSteps = (int) (localTime / fixedTimeStep);
localTime -= numSubSteps * fixedTimeStep;
}
}
if (numSubSteps != 0) {
// clamp the number of substeps, to prevent simulation grinding spiralling down to a halt
int clampedSubSteps = (numSubSteps > maxSubSteps) ? maxSubSteps : numSubSteps;
for (int i = 0; i < clampedSubSteps; i++) {
update(fixedTimeStep);
}
}
}
/**
* Calls render with Graphics2D context and takes care of double buffering.
*/
private void internalUpdateGraphicsInterpolated() {
BufferStrategy bf = this.getBufferStrategy();
Graphics2D g = null;
try {
g = (Graphics2D) bf.getDrawGraphics();
render(g, localTime);
} finally {
g.dispose();
}
// Shows the contents of the backbuffer on the screen.
bf.show();
//Tell the System to do the Drawing now, otherwise it can take a few extra ms until
//Drawing is done which looks very jerky
Toolkit.getDefaultToolkit().sync();
}
Ball[] balls;
BasicStroke ballStroke;
int showMode = 0;
/**
* init Game (override/replace)
*/
protected void init() {
balls = new Ball[10];
int r = 20;
for (int i = 0; i < balls.length; i++) {
Ball ball = new Ball(getWidth() / 2, i * 2.5f * r + 80, 10 + i * 300 / balls.length,
balls[i] = ball;
}
ballStroke = new BasicStroke(3);
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
showMode = ((showMode + 1) % 3);
}
});
}
/**
* update game. elapsedTime is fixed. (override/replace)
* @param elapsedTime
*/
protected void update(float elapsedTime) {
for (Ball ball : balls) {
ball.x += ball.vX * elapsedTime;
ball.y += ball.vY * elapsedTime;
if (ball.x > getWidth() - ball.r) {
ball.vX *= -1;
}
if (ball.x < ball.r) {
ball.vX *= -1;
}
if (ball.y > getHeight() - ball.r) {
ball.vY *= -1;
}
if (ball.y < ball.r) {
ball.vY *= -1;
}
}
}

/**
* render the game (override/replace)
* @param g
* @param interpolationTime time of the rendering within a fixed timestep (in seconds)
*/
protected void render(Graphics2D g, float interpolationTime) {
g.clearRect(0, 0, getWidth(), getHeight());
if (showMode == 0) {
g.drawString("red: raw, black: interpolated (click to switch modes)", 20, 50);
}
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
if (showMode == 1) {
g.drawString("red: raw (click to switch modes)", 20, 50);
}
if (showMode == 2) {
g.drawString("black: interpolated (click to switch modes)", 20, 50);
}
for (Ball ball : balls) {
g.setStroke(ballStroke);
if (showMode == 0 || showMode == 1) {
//w/o interpolation
g.setColor(Color.RED);
g.drawOval((int) (ball.x - ball.r), (int) (ball.y - ball.r), (int) ball.r * 2, (
}
if (showMode == 0 || showMode == 2) {
//with interpolation
g.setColor(Color.BLACK);
g.drawOval((int) (ball.x - ball.r + interpolationTime * ball.vX), (int) (ball.y -
}
}
}
public static class Ball {
public float x, y, vX, vY, r;
public Ball(float x, float y, float vX, float vY, float r) {
this.x = x;
this.y = y;
this.vX = vX;
this.vY = vY;
this.r = r;
}
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
/* Create and display the form */
new Thread() {
{
setDaemon(true);
start();
}
public void run() {
while (true) {
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (Throwable t) {
}
}
}
};
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
Game game = new Game();
game.setVisible(true);
game.start(1 / 60f, 5);
}
});
}
}
Eli Delventhal
I adjusted according the comment in there to more clearly mention stuttering, although it already said it might cause issues. I'm
guessing most people just copy/paste and don't actually read it. :/
Oh well.
yasuocodez
Eli,
I copied and ran your altered code for Variable timestep.
There seems to be a problem with the thread sleeping. Instead of sleeping for 10ms, it sleeps for 10 seconds( 10000ms ).
Have you or anyone else tested and used variable timestep method?
theagentd
Quote from: yasuocodez on January 05, 2012
Eli,
I copied and ran your altered code for Variable timestep.
There seems to be a problem with the thread sleeping. Instead of sleeping for 10ms, it sleeps for 10 seconds( 10000ms ).
Have you or anyone else tested and used variable timestep method?
How are you doing the sleep? Thread.sleep(10);?
yasuocodez
Nope, I ran the code exactly as was written above.
I commented out any update and rendering methods to let it run with out any outter methods.
When i did run with update and render methods, I ran some test with timers, and it updated every 10 seconds, as I could see on
the gui.
ra4king
Aha, there is a bug where Eli adds 10000 when sleeping, when he should only be adding 10. Fix it Eli!
Eli Delventhal
Interesting. That's from Kev's site, too. I fixed it.
Gingerious
So I noticed when I added 15 instead of 10 to the sleep on the variable timestep, it didn't error out on me. Did it error out on
anyone else?
The error was that the time to sleep was a negative value, but it look like it's only for the first time the loop runs. Every time after
that it's between 8 and 10. Any ideas?
ManIkWeet
I've noticed that the interpolation makes the ball example run smoother, but I fail to understand what the interpolotion value
represents, can someone shed some light on this please?
EDIT: I figured out what it does, but this does not work when trying to smooth a character based on user-input right?
Loading page 2...
Pages: [1] 2 3
IGNORE | PRINT



You cannot reply to this message, because it is very, very old.
Check out our latest community projects!
missing image missing image missing image missing image
missing image missing image missing image missing image


Powered by SMF 1.1.18 | SMF 2013, Simple Machines | Managed by Enhanced Four

You might also like