8
\$\begingroup\$

I have two interfaces. Entity for updates. Drawable for rendering. They both have two interface methods. One that is called every frame, one every second.

Entity.java

public interface Entity
{
    public void updatePerFrame ();
    public void updatePerSecond ();
}

Drawable.java

public interface Drawable
{
    public void drawPerFrame ();
    public void drawPerSecond ();
}

The main component class Game has two lists, one for Entity and one for Drawable.

public class Game {
    // if the game is running, default = true
    // if the game is paused, default = false
    private boolean isRunning;
    private boolean isPaused;

    // execution time for the last cycle of game loop
    private long lastLoopTime;
    private long curLoopTime;
    private long deltaLoopTime;
    private double deltaTime;

    // if a second has passed since last execution
    private boolean secondPassed;
    private long secondTimer;

    // nanoseconds spent per frame
    private final int OPTIMAL_FPS = 60;
    private final int OPTIMAL_TIME = 1000000000 / OPTIMAL_FPS;

    // entities
    private List<Entity> entities;

    // drawable
    private List<Drawable> drawables;

    // fps tracker
    FPSViewer fpsViewer;

    /**
     * Main Function, instantiating Game 
     */
    public static void main() {
        Game g = new Game();
        g.gameLoop();
    }

    /**
     * 
     */
    public Game () {
        // the game is on and not paused
        isRunning = true;
        isPaused = false;

        // a second has not yet passed
        secondPassed = false;
        secondTimer = System.currentTimeMillis();

        // init entities and drawables
        entities = new ArrayList<Entity>();
        drawables = new ArrayList<Drawable>();

        // begin with current time
        lastLoopTime = System.nanoTime();

        // create the fps viewer
        fpsViewer = new FPSViewer();
        entities.add(fpsViewer);
        drawables.add(fpsViewer);
    }

    /**
     * Game Loop
     */
    public void gameLoop () {

        while (isRunning == true) {

            // if the game is paused
            if (isPaused == true) {
                // set paused attributes
                // to-be-done

                // wait for user to unpause
                while (isPaused == true) {
                }

                // reset time synching
                lastLoopTime = System.nanoTime();

                // reset fps viewer
                fpsViewer.reset();
            }

            // get delta time
            curLoopTime = System.nanoTime();
            deltaLoopTime = curLoopTime - lastLoopTime;
            deltaTime = deltaLoopTime / (double)OPTIMAL_TIME;

            // set last to current
            lastLoopTime = curLoopTime;

            // check if a second has passed
            if ((System.currentTimeMillis() - secondTimer) > 1000) {
                secondPassed = true;
                secondTimer = System.currentTimeMillis();
            }

            // render everything
            render ();

            // update game
            gameUpdate();

            // reset if a second has passed
            if (secondPassed == true )
                secondPassed = false;

            // wait for game to catch up (if loop execution was too fast)
            try {
                Thread.sleep( (lastLoopTime - System.nanoTime() + OPTIMAL_TIME)/1000000 );
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     * Game Updates
     */
    private void gameUpdate () {
        for (Entity e : entities) {
            e.updatePerFrame ();
        }

        if (secondPassed == true) {
            for (Entity e : entities) {
                e.updatePerSecond();
            }
        }
    }

    /**
     * Renders all graphics
     */
    private void render () {
        for (Drawable d : drawables) {
            d.drawPerFrame ();
        }

        if (secondPassed == true) {
            for (Drawable d : drawables) {
                d.drawPerSecond();
            }
        }
    }
}

As a test there is the FPSViewer.

It currently sits at 62 fps. Which will be fixed in the next iteration.

public class FPSViewer implements Drawable, Entity {
    private long lastFpsUpdateTime;
    private long lastExecutionTime;
    private byte fps;

    public void drawPerFrame () {
    }

    public void drawPerSecond () {
        System.out.println ("Frame: " + fps);
    }

    /**
     * update fps counter
     */
    public void updatePerFrame () {
        lastFpsUpdateTime += (System.nanoTime() - lastExecutionTime);
        lastExecutionTime = System.nanoTime();
        fps++;
    }

    /**
     * Resets frame counter back to 0
     */
    public void updatePerSecond () {
        lastFpsUpdateTime = 0;
        fps = 0;
    }

    public void reset () {
        lastFpsUpdateTime = 0;
        lastExecutionTime = 0;
        fps = 0;
    }
}

Now to the feedback wanted, everything basically. Be merciless. Here are some things to focus on. The game will consist mostly of 2D Buttons with mouse and keyboard input.

\$\endgroup\$
  • \$\begingroup\$ Presumably Entitys shouldn't know anything about render state, so wouldn't need updatePerFrame(). And drawPerSecond() is obviously too slow for Drawable... why do you have two methods in the first place? \$\endgroup\$ – Clockwork-Muse Dec 4 '14 at 11:39
  • \$\begingroup\$ drawPerSecond () is used currently to render the fps once per second. updatePerFrame () is the standard update, called once per execution. The updatePerSecond () was me trying to get some timings done. If I wanted to time things on second basis instead of per loop execution (currently frame). \$\endgroup\$ – Emz Dec 4 '14 at 11:49
5
\$\begingroup\$

While it is very interesting to do the timekeeping manually just like you did, I'd like to talk to you about multithreading and scheduled ThreadPoolExecutors.

A ScheduledThreadPoolExecutor can handle all the timing steps for you.. Additionally a gameLoop often just gets a pre-rendered frame and outputs it. the rest will usually be done in separate threads. They will update the games state and render a frame to the buffer for outputting.

In a rough way what happens in most modern game can be described like in this article:

The game state itself doesn’t need to be updated 60 times per second. Player input, AI and the updating of the game state have enough with 25 frames per second. So let’s try to call the update_game() 25 times per second, no more, no less. The rendering, on the other hand, needs to be as fast as the hardware can handle. But a slow frame rate shouldn’t interfere with the updating of the game.

Furthermore there's a few small tricks to have the transitions look even smoother, that are also described in that article. Now to applying this to your current code:

private final ScheduledThreadPoolExecutor gameStateThread = new ScheduledThreadPoolExecutor(2);

/* other fields */

public Game() {
    // 1000 (ms / s) / 25 (ticks / s) = 40 ms/tick
    gameStateThread.scheduleAtFixedRate(() -> gameUpdateTick(), 1000, 1000 / TICKS_PER_SEC, TimeUnit.MILLISECONDS);
    gameStateThread.scheduleAtFixedRate(() -> gameUpdateSecond(), 1, 1, TimeUnit.SECONDS);
}

public void gameLoop() { 
     //simplifying here, you'd need to handle pausing, 
     //by checking it in the gameUpdate* calls
     while (true) {
         render();
     }
}

Additionally I'd strongly recommend to eagerly initialize your variables and declare your drawables and entities as final. Keep in mind that the default value for booleans is false, the changes proposed above also make the second-timekeeping unnecessary so I removed the stuff for that here:

public class Game {
    private boolean isRunning = true;
    private boolean isPaused = false;

    private long lastLoopTime = System.nanoTime();
    private long curLoopTime;
    private long deltaLoopTime;
    private double deltaTime;

    private static final int OPTIMAL_FPS = 60;
    private static final int OPTIMAL_TIME = 1000000000 / OPTIMAL_FPS;
    private static final int TICKS_PER_SEC = 25;

    private final List<Entity> entities = new ArrayList<>();
    private final List<Drawable> drawables = new ArrayList<>();

    //and here's the rest ;)
\$\endgroup\$
  • \$\begingroup\$ Some quick questions, I am fairly new to Java. final means it can't be of any other implementation than ArrayList<> in this case right? gameStateThread() will in this case be called like a "background-process"? \$\endgroup\$ – Emz Dec 4 '14 at 11:54
  • \$\begingroup\$ private boolean isPaused = false; that assignment is unnecessary, I know that. However what is the recommended approach? I wanted to make sure that it is implied that it has a default value. That its default value matters. Regarding the render loop, right now it does not have an fps cap right? So with the simplest of codes (that I have now) an FPS of above thousands is not to be unexpected? Which is a bit overkill for every standard monitor. \$\endgroup\$ – Emz Dec 4 '14 at 11:57
  • \$\begingroup\$ @Emz Concerning FPS-Cap, you're right, but be aware that this is by far not meant as the final version ;) I highly recommend reading the article I linked. final means, you can only assign to the reference once. It's fine to explicitly state default values. And yes, the gameStateThread is a so-called background worker. When ending the game you must make sure to shut it down, else it will keep running and hog memory \$\endgroup\$ – Vogel612 Dec 4 '14 at 12:11
  • \$\begingroup\$ With regards to final, that means I can never call new ArrayList<>() or any other implementation later on right? \$\endgroup\$ – Emz Dec 7 '14 at 21:56
  • \$\begingroup\$ Correct. It's not really necessary though.. why would you want to throw away the container after you decided on one... It's not like you're going to change the list's implementation at runtime \$\endgroup\$ – Vogel612 Dec 7 '14 at 22:00
7
\$\begingroup\$

I just have a couple of superficial points for now. It's been a while since I created a game loop, so I will let someone else comment on that, but at least your busy-loop (while (isPaused == true)) doesn't seem necessary (it will eat up a lot of resources without actually doing anything).

Boolean Checks

Instead of for example isRunning == true you can just write isRunning, which is more readable.

Comments

A lot of your comments are not needed, as the code is already self-explanatory, for example:

// if the game is running
// if the game is paused
// entities
// drawable
// init entities and drawables
// wait for user to unpause
// render everything
// update game
[etc]

These do not add any information, and thus actually make your code harder to read instead of easier.

Misc

  • you often (but not always) have a whitespace before ().
  • magic numbers: extract numbers in your code to (static) fields. This tells us what they actually represent (eg 1000 and 1000000).
  • always use curly brackets, even for one line statements.
\$\endgroup\$
  • \$\begingroup\$ the while (isPaused) {} will do more stuff later. Right now it is fairly useless, but then again, isPaused can't be set to true! My current IDE is worthless at auto-adding a space between a method and its parentheses. I forget to add them manually from time to time. Thanks so far! \$\endgroup\$ – Emz Dec 3 '14 at 22:13

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.