Java Main Game Loop
Asked Answered
F

3

22

I am writing a game loop, I found the code in the example below here. I have also looked at other ways to do a game loop, such as from this article. I couldn't get any of those ones working though. So I kept with the one from the first link.

What I would like to know:

  • Is the way I wrote my game loop a good way to do this?
    • Any suggestions?
  • Should I be using Thread.sleep(); in my game loop?

Here is my current code:

public void run(){
    long lastLoopTime = System.nanoTime();
    final int TARGET_FPS = 60;
    final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
    long lastFpsTime = 0;
    while(true){
        long now = System.nanoTime();
        long updateLength = now - lastLoopTime;
        lastLoopTime = now;
        double delta = updateLength / ((double)OPTIMAL_TIME);

        lastFpsTime += updateLength;
        if(lastFpsTime >= 1000000000){
            lastFpsTime = 0;
        }

        this.updateGame(delta);

        this.repaint();

        try{
            Room.gameTime = (lastLoopTime - System.nanoTime() + OPTIMAL_TIME) / 1000000;
            System.out.println(Room.gameTime);
            Thread.sleep(Room.gameTime);
        }catch(Exception e){
        }
    }
Fur answered 16/8, 2013 at 22:44 Comment(1)
I have a post about game loops and keeping it steady here.Rectangular
P
22

Eventually you'll want to move to something like LWJGL, but let me stress, keep doing what you're doing here for now. It will teach you fundamentals.

Good job on your loop. Looks nice, let me offer a few pointers:

  • Repaint will not render the screen immediately. It tells the RepaintManager to render when its ready. Use invalidate paintImmediately instead. paintImmediately will block execution until the component has been redrawn so you can measure rendering time.

  • Thread.sleep typically has a few milliseconds drift. You should be using it to keep your loop from using too much CPU, but make sure you understand if you sleep 10 milliseconds you might sleep 5 milliseconds or you might sleep 20.

  • Lastly:

    double delta = updateLength / ((double)OPTIMAL_TIME);
    

    If updateLength is less than OPTIMAL_TIME, don't call update. In other words, if delta is less than one, don't update. This tutorial explains why better than I ever could.

Padauk answered 16/8, 2013 at 22:57 Comment(6)
Thanks! If I use invalidate alone, nothing happens. Should that be happening?Fur
Whoops, no I goofed. I work with C# and Java daily. Got mixed up. Edited. Try paintImmediately insteadPadauk
I had to move paintImmediately after the try-catch otherwise it does some strange rendering... should I be doing that?Fur
paintImmediately needs to be run on the main thread. You can either run all your code on the UI thread, update and rendering (what I recommend) or you can use repaint.Padauk
That is the main thread. I think... Here is the full class: github.com/TheColorRed/JGame/blob/master/src/JGame/Room/…Fur
No its a second thread. See the constructor for your JPanel? It creates a thread and passes itself in. That thread calls method run.Padauk
G
9

Overall, it is a good loop, but there are a few missing aspects to what I have found in experience to be the best loop.

You will eventually want to move to LWJGL or some other java game API, but for now, learn the basics of how game-loops work, and what best suits your needs.

  • Firstly, in answer to one of your points, no. You will do better staying away from

    Thread.sleep()

    this can stray from the real amount of time you set it to sleep.
    e.g. if you set it to sleep for 10 milliseconds, it could sleep the program for 5 to 20 milliseconds.

  • The second problem I cam immediately see is that you do not have any way to stop the game-loop for a custom stop() method. Try

    boolean running = true;
    while (running) {
    // Your Code Here //
    }

  • Thirdly, you may want to consider changing how you use your delta variable. The way in the code below may be a better use and construction for you.

    This is an example of my game-loop that I use in my programs:

    @Override
    public void run() {
    
    long initialTime = System.nanoTime();
    final double timeU = 1000000000 / UPS;
    final double timeF = 1000000000 / FPS;
    double deltaU = 0, deltaF = 0;
    int frames = 0, ticks = 0;
    long timer = System.currentTimeMillis();
    
        while (running) {
    
            long currentTime = System.nanoTime();
            deltaU += (currentTime - initialTime) / timeU;
            deltaF += (currentTime - initialTime) / timeF;
            initialTime = currentTime;
    
            if (deltaU >= 1) {
                getInput();
                update();
                ticks++;
                deltaU--;
            }
    
            if (deltaF >= 1) {
                render();
                frames++;
                deltaF--;
            }
    
            if (System.currentTimeMillis() - timer > 1000) {
                if (RENDER_TIME) {
                    System.out.println(String.format("UPS: %s, FPS: %s", ticks, frames));
                }
                frames = 0;
                ticks = 0;
                timer += 1000;
            }
        }
    }
    
Garth answered 17/5, 2014 at 0:7 Comment(7)
Hi I know it's an old post/question , but what getInput() method performs? And why you are using a thread instead of a Timer? is it better to use a Thread?Rebarbative
1. The 'getInput()' function just separates the user input processing from the main game logic (i.e press right, game registers right in 'getInput()', object moves right in 'update()'). 2. I use a Thread instead of a Timer because it separates the entire game logic from background processes so it will run on a different cpu core. Take a look at this for more info on threads: tomshardware.co.uk/forum/306079-28-what-core-threadGarth
Thank you for your answer I have done a lot of tests and using a Thread instead of Timer (executing on 60FPS) makes my app using a lot more of CPU on my MacBook Pro (3,1 GHz Intel Core i7 with 16GB of ram). Very strange I will try to do other investigations, but for the moment I will use the Timer. On the other hand you gave me a good idea with your getInput method. ;)Rebarbative
What's RENDER_TIME?Anaerobe
'RENDER_TIME' is an FPS flag - i.e: a static boolean variable that toggles if the command interface (cmd) should print the frames rendered per second every second. Hence 'frames' and 'ticks' are counters for frames drawn and game updates performed respectively, reset every second after they're possably output to the command interface.Garth
Just to confirm - the 'FPS' variable is the tareget frames per second to run at, the 'frames' counter is how many frames are actually rendered.Garth
While using this loop my CPU usage on MBP 2015 hits ~150%, and at the same time using loop from this thread: java-gaming.org/index.php?topic=24220.0 around 60-70%Marleenmarlen
R
0

the simplest way to refresh repainting could be like this:

public class GameLoop extends JPanel {
private final BufferedImage back_buffer;
bool state = true;

public void init() {
        while (state) {
            updatePlayer();
            delay(5);
        }
    }

public void delay(int time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        repaint();
    }

and the repaint function is who update all of the graphics

@Override
    public void paint(Graphics g) {
        super.paint(g);
        
        grapicDoble.setColor(Color.white);
        grapicDoble.fillRect(0, 0, 500, 500);
        game.reset();
        
        g.drawImage(back_buffer, 0, 0, this);
    }
Ross answered 18/8, 2021 at 2:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.