Game loops!

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

I made the following gameloop. It doesn’t stutter on my system. However it doesn’t sleep as much as other ones.

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

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. :confused:

Oh well.

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

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.

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

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

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?

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?

It works in any circumstance. All drawing is one frame behind so that you can interpolate everything.

Wut?

Welcome to page 2, ra4king.

I really don’t know why we even have article format.

O___O I didn’t notice that this was page two! I thought this a whole new article by some random n00b :stuck_out_tongue:

If all drawing is one frame behind, don’t you notice input lag?

I’ve only done a quick skim, but don’t use floating point for digital counters.

Which counter are you referring to? If it’s the timer then it doesn’t matter, any precision lost will be so minute.

I have a new issue with the fixed timestep, it seems that for alot of computers the game is very choppy (game herz 30 with 60 fps)…
This happens on my desktop very rarely and I have a hard time finding the fix, anyone knowing about this?

Remove the Thread.sleep() call in there, and use Thread.yield() only.

Didn’t work, the weird thing is that when I print on render and tick, the output is:
tick
render
render
tick
render
render
tick…

So it should work, maybe there’s a problem calculating the interpolation?