8
\$\begingroup\$

I'm experimenting with building a game engine from scratch in Java, and I have a couple questions. My main game loop looks like this:

        int FPS = 60;
        while(isRunning){
            /* Current time, before frame update */
            long time = System.currentTimeMillis();
            update();
            draw();
            /* How long each frame should last - time it took for one frame */
            long delay = (1000 / FPS) - (System.currentTimeMillis() - time);
            if(delay > 0){
                try{
                    Thread.sleep(delay);
                }catch(Exception e){};
            }
        }

As you can see, I've set the framerate at 60FPS, which is used in the delay calculation. The delay makes sure that each frame takes the same amount of time before rendering the next. In my update() function I do x++ which increases the horizontal value of a graphics object I draw with the following:

bbg.drawOval(x,40,20,20);

What confuses me is the speed. when I set FPS to 150, the rendered circle goes across the speed really fast, while setting the FPS to 30 moves across the screen at half the speed. Doesn't framerate just affect the "smoothness" of the rendering and not the speed of objects being rendered? I think I'm missing a big part, I'd love some clarification.

\$\endgroup\$
  • 4
    \$\begingroup\$ Here is a good article about game loop: fix your timestep \$\endgroup\$ – Kostya Regent Apr 6 '15 at 13:28
  • 2
    \$\begingroup\$ As a side note, we generally try to put stuff that has not to be performed every loop outside of loops. In your code, your 1000 / FPS division could be done and the result assigned to a variable before your while(isRunning) loop. This helps save a couple of CPU instruction for doing something more than once uselessly. \$\endgroup\$ – Alexandre Vaillancourt Apr 6 '15 at 19:19
21
\$\begingroup\$

You're moving the circle by one pixel per frame. It should not come as a big surprise that, if your rendering loop runs at 30 FPS, your circle will move 30 at pixels per second.

You basically have three possible ways to deal with this issue:

  1. Just pick one frame rate and stick to it. That's what a lot of old-school games did — they'd run at a fixed rate of 50 or 60 FPS, usually synced to the screen refresh rate, and just design their game logic to do everything necessary within that fixed time interval. If, for some reason, that didn't happen, the game would just have to skip a frame (or possibly crash), effectively slowing both the drawing and the game physics to half speed.

    In particular, games that used features like hardware sprite collision detection pretty much had to work like this, because their game logic was inextricably tied to the rendering, which was done in hardware at a fixed rate.

  2. Use a variable timestep for your game physics. Basically, this means rewriting your game loop to look something like this:

    long lastTime = System.currentTimeMillis();
    while (isRunning) {
        long time = System.currentTimeMillis();
        float timestep = 0.001 * (time - lastTime);  // in seconds
        if (timestep <= 0 || timestep > 1.0) {
            timestep = 0.001;  // avoid absurd time steps
        }
        update(timestep);
        draw();
        // ... sleep until next frame ...
        lastTime = time;
    }
    

    and, inside update(), adjusting the physics formulas to account for the variable timestep, e.g. like this:

    speed += timestep * acceleration;
    position += timestep * (speed - 0.5 * timestep * acceleration);
    

    One problem with this method is that it can be tricky to keep the physics (mostly) independent of the timestep; you really don't want the distance players can jump to depend on their frame rate. The formula I showed above works nicely for constant acceleration, e.g. under gravity (and the one in the linked post does pretty well even if the acceleration varies over time), but even with the most perfect possible physics formulas, working with floats is likely to produce a bit of "numerical noise" that, in particular, can make exact replays impossible. If that's something you think you might want, you may wish to prefer the other methods.

  3. Decouple the update and draw steps. Here, the idea is that you update your game state using a fixed timestep, but run a varying number of updates between each frame. That is, your game loop might look something like this:

    long lastTime = System.currentTimeMillis();
    while (isRunning) {
        long time = System.currentTimeMillis();
        if (time - lastTime > 1000) {
            lastTime = time;  // we're too far behind, catch up
        }
        int updatesNeeded = (time - lastTime) / updateInterval;
        for (int i = 0; i < updatesNeeded; i++) {
            update();
            lastTime += updateInterval;
        }
        draw();
        // ... sleep until next frame ...
    }
    

    To make the perceived movement smoother, you may also wish to have your draw() method interpolate things like object positions smoothly between the previous and next game states. This means you need to pass the correct interpolation offset to the draw() method, e.g. like this:

        int remainder = (time - lastTime) % updateInterval;
        draw( (float)remainder / updateInterval );  // scale to 0.0 - 1.0
    

    You would also need to have your update() method actually calculate the game state one step ahead (or possibly several, if you wish to do higher-order spline interpolation), and have it save previous object positions before updating them, so that the draw() method can interpolate between them. (It's also possible to just extrapolate predicted positions based on object velocities and accelerations, but this can look jerky especially if objects move in complicated ways, causing the predictions to often fail.)

    One advantage of interpolating is that, for some types of games, it can allow you to significantly reduce the game logic update rate, while still maintaining an illusion of smooth motion. For example, you might be able to update your game state only, say, 5 times per second, while still drawing 30 to 60 interpolated frames per second. In you do this, you may also wish to consider interleaving your game logic with the drawing (i.e. having a parameter to your update() method that tells it to only run x% of a full update before returning), and/or running the game physics / logic and the rendering code in separate threads (beware of synchronization glitches!).

Of course, it's also possible to combine these methods in various ways. For example, in a client–server multiplayer game, you might have the server (which does not need to draw anything) run its updates at a fixed timestep (for consistent physics and exact replayability), while having the client do predictive updates (to be overridden by the server, in case of any disagreement) at a variable timestep for better performance. It's also possible to usefully mix interpolation and variable-timestep updates; for example, in the client–server scenario just described, there's really not much point in having the client use shorter update timesteps than the server, so you can set a lower limit on the client timestep, and interpolate in the drawing stage to allow higher FPS.

(Edit: Added code to avoid absurd update intervals / counts, in case, say, the computer is temporarily suspended or otherwise frozen for more than a second while the game loop is running. Thanks to Mooing Duck for reminding me about the need for that.)

\$\endgroup\$
  • 1
    \$\begingroup\$ Thank you so much for taking the time to answer my question, I really appreciate it. I really like #3's approach, it makes the most sense for me. Two questions, what is the updateInterval defined by, and why do you divide by it? \$\endgroup\$ – Carpetfizz Apr 7 '15 at 4:55
  • 1
    \$\begingroup\$ @Carpetfizz: updateInterval is just the number of milliseconds you want between game state updates. For, say, 10 updates per second, you'd set updateInterval = (1000 / 10) = 100. \$\endgroup\$ – Ilmari Karonen Apr 7 '15 at 7:53
  • 1
    \$\begingroup\$ currentTimeMillis is not a monotonic clock. Use nanoTime instead, unless you want network time syncing to mess with the speed of things in your game. \$\endgroup\$ – immibis Apr 7 '15 at 11:44
  • \$\begingroup\$ @MooingDuck: Well spotted. I've fixed it now, I think. Thanks! \$\endgroup\$ – Ilmari Karonen Apr 7 '15 at 18:56
  • \$\begingroup\$ @IlmariKaronen: Actually, looking at the code, it might be simpler just to while(lastTime+=updateInterval <= time). That's just an thought though, not a correction. \$\endgroup\$ – Mooing Duck Apr 7 '15 at 19:05
8
\$\begingroup\$

Your code is currently running each time a frame renders. If the frame rate is higher or lower than your specified frame rate, your results would change as the updates don't have the same timing.

To solve this, you should refer to Delta Timing.

The purpose of Delta Timing is to eliminate the effects of lag on computers that try to handle complex graphics or a lot of code, by adding up to the speed of objects so that they will eventually move at the same speed, regardless of lag.

To do this:

It is done by calling a timer every frame per second that holds the time between now and last call in milliseconds.

You would need to then multiply the delta time by the value you want to change by time. For example:

distanceTravelledSinceLastFrame = Speed * DeltaTime
\$\endgroup\$
  • 3
    \$\begingroup\$ Also, put caps on the minimum and maximum deltatimes. If the computer hibernates then resumes, you don't want things launching off screen. If a miracle appears and time() returns the same twice, you don't want div/0 errors and wasted processing. \$\endgroup\$ – Mooing Duck Apr 6 '15 at 21:29
  • \$\begingroup\$ @MooingDuck: That's a very good point. I've edited my own answer to reflect it. (Usually, you shouldn't be dividing anything by the timestep in a typical game state update, so a zero timestep ought to be safe, but allowing it does add an extra source of potential errors for little or no gain, and thus should be avoided.) \$\endgroup\$ – Ilmari Karonen Apr 7 '15 at 8:10
5
\$\begingroup\$

That's because you limit your frame rate, but you only do one update per frame. So let's assume the game runs at the target 60 fps, you get 60 logic updates per second. If the frame rate drops to 15 fps, you'd only have 15 logic updates per second.

Instead, try accumulating the frame time passed so far and then update your game logic once for every given timespan that passed, e.g. to run your logic at 100 fps you'd run the update once for every 10 ms accumulated (and subtract those from the counter).

Add an alternative (better for visuals) update your logic based on the time passed.

\$\endgroup\$
  • 1
    \$\begingroup\$ i.e. update(elapsedSeconds); \$\endgroup\$ – Jon Apr 6 '15 at 9:15
  • 2
    \$\begingroup\$ And inside, position += velocity * elapsedSeconds; \$\endgroup\$ – Jon Apr 6 '15 at 9:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.