Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (498)
Games in Android Showcase (115)
games submitted by our members
Games in WIP (562)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1] 2 3
  ignore  |  Print  
  Game loops!  (Read 98938 times)
0 Members and 3 Guests are viewing this topic.
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Posted 2011-05-13 23:55:38 »

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  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
private boolean isRunning;

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  
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  
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);
     
      // 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  
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  
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  
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  
295  
296  
297  
298  
299  
300  
301  
302  
303  
304  
305  
306  
307  
308  
309  
310  
311  
312  
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)
      {
         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)
            {
               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;
            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())
         {
            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.

See my work:
OTC Software
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #1 - Posted 2011-05-14 00:15:24 »

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.

See my work:
OTC Software
Offline ra4king

JGO Kernel


Medals: 346
Projects: 3
Exp: 5 years


I'm the King!


« Reply #2 - Posted 2011-05-14 03:20:45 »

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! Smiley

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline dishmoth
« Reply #3 - Posted 2011-05-15 17:19:41 »

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. Wink

- 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

Offline ra4king

JGO Kernel


Medals: 346
Projects: 3
Exp: 5 years


I'm the King!


« Reply #4 - Posted 2011-05-15 18:13:59 »

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?

Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #5 - Posted 2011-05-15 19:29:33 »

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.

See my work:
OTC Software
Offline ra4king

JGO Kernel


Medals: 346
Projects: 3
Exp: 5 years


I'm the King!


« Reply #6 - Posted 2011-05-15 23:47:40 »

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 Smiley

Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #7 - Posted 2011-05-16 16:56:54 »

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 Smiley
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. Smiley

See my work:
OTC Software
Offline ra4king

JGO Kernel


Medals: 346
Projects: 3
Exp: 5 years


I'm the King!


« Reply #8 - Posted 2011-05-16 23:45:25 »

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 Smiley
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. Smiley
Yes I was fearing the copy/paste part Cheesy

Offline philfrei
« Reply #9 - Posted 2011-07-27 18:33:21 »

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. Sad

"Greetings my friends! We are all interested in the future, for that is where you and I are going to spend the rest of our lives!" -- The Amazing Criswell
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #10 - Posted 2011-07-27 20:50:32 »

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.

See my work:
OTC Software
Offline ra4king

JGO Kernel


Medals: 346
Projects: 3
Exp: 5 years


I'm the King!


« Reply #11 - Posted 2011-07-28 00:49:26 »

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.

Offline philfrei
« Reply #12 - Posted 2011-07-28 06:08:56 »

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

"Greetings my friends! We are all interested in the future, for that is where you and I are going to spend the rest of our lives!" -- The Amazing Criswell
Offline ra4king

JGO Kernel


Medals: 346
Projects: 3
Exp: 5 years


I'm the King!


« Reply #13 - Posted 2011-07-28 10:27:19 »

Ah yes I have seen both posts.

And what's a daemon and how would it help? O_o

Offline cylab

JGO Ninja


Medals: 49



« Reply #14 - Posted 2011-07-28 15:23:13 »

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

Mathias - I Know What [you] Did Last Summer!
Offline ra4king

JGO Kernel


Medals: 346
Projects: 3
Exp: 5 years


I'm the King!


« Reply #15 - Posted 2011-07-28 17:32:03 »

Ah makes sense thanks!

Offline Rejechted

Senior Member


Medals: 1
Projects: 1


Just a guy making some indie games :D


« Reply #16 - Posted 2011-09-21 18:25:52 »

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?

Blog for our project (Codenamed Lead Crystal): http://silvergoblet.tumblr.com
Offline philfrei
« Reply #17 - Posted 2011-09-21 20:04:06 »

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).

"Greetings my friends! We are all interested in the future, for that is where you and I are going to spend the rest of our lives!" -- The Amazing Criswell
Offline Rejechted

Senior Member


Medals: 1
Projects: 1


Just a guy making some indie games :D


« Reply #18 - Posted 2011-09-21 22:15:53 »

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?

Blog for our project (Codenamed Lead Crystal): http://silvergoblet.tumblr.com
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #19 - Posted 2011-09-23 23:22:32 »

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?

See my work:
OTC Software
Offline Chromanoid

Junior Member


Medals: 3



« Reply #20 - Posted 2011-10-09 11:47:58 »

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
Offline Chromanoid

Junior Member


Medals: 3



« Reply #21 - Posted 2011-10-11 10:50:51 »

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  
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  
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  
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);
            }

            @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, 0, r);
            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);
        }
        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, (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 - ball.r + interpolationTime * ball.vY), (int) ball.r * 2, (int) ball.r * 2);
            }
        }
    }

    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);
            }
        });
    }
}
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #22 - Posted 2011-10-11 19:30:07 »

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.

See my work:
OTC Software
Offline yasuocodez

Junior Newbie





« Reply #23 - Posted 2012-01-05 12:01:22 »

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?
Offline theagentd
« Reply #24 - Posted 2012-01-05 15:38:51 »

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);?

Myomyomyo.
Offline yasuocodez

Junior Newbie





« Reply #25 - Posted 2012-01-05 16:10:15 »

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.
Offline ra4king

JGO Kernel


Medals: 346
Projects: 3
Exp: 5 years


I'm the King!


« Reply #26 - Posted 2012-01-05 16:19:15 »

Aha, there is a bug where Eli adds 10000 when sleeping, when he should only be adding 10. Fix it Eli! Tongue

Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #27 - Posted 2012-01-06 18:56:35 »

Interesting. That's from Kev's site, too. I fixed it.

See my work:
OTC Software
Offline Gingerious

Junior Member


Medals: 2



« Reply #28 - Posted 2012-01-19 18:51:57 »

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?
Offline ManIkWeet

Senior Newbie





« Reply #29 - Posted 2012-03-05 21:08:42 »

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?
Pages: [1] 2 3
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

BurntPizza (28 views)
2014-09-21 02:42:18

BurntPizza (18 views)
2014-09-21 01:30:30

moogie (20 views)
2014-09-21 00:26:15

UprightPath (27 views)
2014-09-20 20:14:06

BurntPizza (29 views)
2014-09-19 03:14:18

Dwinin (46 views)
2014-09-12 09:08:26

Norakomi (74 views)
2014-09-10 13:57:51

TehJavaDev (101 views)
2014-09-10 06:39:09

Tekkerue (50 views)
2014-09-09 02:24:56

mitcheeb (71 views)
2014-09-08 06:06:29
List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06

List of Learning Resources
by SilverTiger
2014-07-31 11:54:12

HotSpot Options
by dleskov
2014-07-08 01:59:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!