Creating a game loop in JavaFX is incredibly simple. In fact, creating games with JavaFX in general is wonderfully easy. There’s extensive support for animation, rendering, and media, so a lot of the hard work is done for you.

What We’ll Achieve In This Tutorial

By the end of this tutorial, you’ll have:

  • A working game loop
  • Handle user input
  • Smooth player movement

And all in a prototype space shooter.

Simple game loop combined with a rendering pipeline creates a top-down space shooter.

There are tonnes of different types of games, but we’ve chosen the top-down space shooter style because it’s an easy way to demonstrate the basics.

Creating our Game

The good news is that creating a game is really simple once you get the hang of the basics. We’re going to create our space shooter in 3 simple steps:

  • Create a custom animation timer to animate smoothly
  • Create a Player and handle user input to move it
  • Put together a simple rendering pipeline that puts our player on the screen

We’ll do each one in turn, starting from a simple JavaFX project with Maven support. We’re using Maven because it makes dependency management so much easier. If you’re not sold, read our article on it here!

Alright it’s time to dive in.

1. Creating the Game Loop with an Animation Timer

The core to any game is a smooth game loop. There is no system in the world that can guarantee a rendering rate of exactly the same frame rate for every frame. But there are tricks to making a game loop work the way you want it to.

A game loop can be created by extending the AnimationTimer class to operate on the length of the frame, rather than the time in nanoseconds. That’s going to ensure that we apply the correct movement to players and other moving components.. More importantly, it’s going to avoid ‘jittering’ – commonly encountered when calculations are needed like if we introduced a particle system!

Starting Code

If you’re not familiar with the JavaFX AnimationTimer, check out our tutorial on pausing it, or testing its speed. Extending it is really simple, though. It’s an abstract class with the abstract method handle().

In our tutorial on pausing the animation timer, we extended the AnimationTimer class to include the ability to track duration, pause and reset the timer to zero. Let’s nick that code as a starting point.

i. Change code to include a game canvas!

One exception is that we’ll change the FXML file to include a Canvas element in place of what we had before.

<AnchorPane fx:id="gameAnchor" prefHeight="450" prefWidth="750.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.edencoding.controllers.GameController">
    <Canvas fx:id="gameCanvas" height="450.0" width="750.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
</AnchorPane>

We’ll also bind the width and height of the canvas to the size of the AnchorPane that encapsulates it to ensure we never have any blank area in our game window.

private void initialiseCanvas() {
    gameCanvas.widthProperty().bind(gameAnchor.widthProperty());
    gameCanvas.heightProperty().bind(gameAnchor.heightProperty());
}

Of course your other option is to make the window not re-sizeable. Your choice!

ii. Change our code to track frame duration, not absolute time

First of all, let’s change the name of the class from PausableAnimationTimer to GameLoop. That’s clearer.

When you extend the AnimationTimer class, you’re compelled by Java to override the handle method. We used that method to really good effect in defining the pause functionality, but it’s really important to remember it’s still the handle() method that gets called by JavaFX’s animation pulse system.

To give our game app the ability to use the timer, we define a new abstract method and call it right at the end of the handle() method in our GameLoop. That way, when the Animation Pulse comes through, it’ll trigger our code too.

We’ll call the new method tick().

Now, in the original PausableAnimationTimer, we passed the tick() method the raw nano time.

@Override
public void handle(long now) {
    ....
    if (!isPaused) {
        long duration= now - animationStart;
        ...
        tick(duration);
    }
    ...
}

This is practically useless in game loops. The time is completely arbitrary and what we really need is the time since the last frame. So let’s store the last frame time in nanos and use that to work out the duration of the frame in seconds:

@Override
public void handle(long now) {
    float lastFrameTimeNanos;
    ....
    if (!isPaused) {
        ...
        float secondsSinceLastFrame = (float) ((now - lastFrameTimeNanos) / 1e9);
        lastFrameTimeNanos = now;
        tick(secondsSinceLastFrame);
    }
    ...
}

That’s absolutely all we need for our animation timer.

2. Create a Player and handle user input

Here, we’re going to create a simple player – a spaceship we’ll be navigating about our scene. Then, we’ll make a handy Key manager so we always know which keys the User’s pressing. Finally in this section, we’ll combine the two to navigate our user about the screen.

a. Creating a Player

There are a number of ways we could create our player. Depending on the drawing style of our game, we could implement sprite animation, or render complex shapes rather than images. Each comes with its own requirements for rendering.

We’ll opt for a simple single-image entity, which we’ll rotate and move when the player changes position in the world. It might not have the compelling art style of isometric sprite animation, but for a top-down space game, it’ll do just fine.

There are some amazing free resources for games at opengameart.org. We used this spaceship from user scofanogd. We’ve made a few tweaks to make it fit with the background we’ll use later

Apart from an image, every entity in the game needs the same things so we can generalise the Player code and re-use it for every entity! The common features are:

  • Position
  • Scale
  • Rotation
  • Movement rules

And of course, it needs a method to update the location of the entity each frame. Check out the Entity code in the drop-down, or check out the project in full at the end.

b. Making a game-friendly key system

One of the many great things about JavaFX is its powerful events system. It can track clipboard, drag board, mouse and key events with absolute ease.

It’s powerful, but it’s based on events rather than polling. That means if we don’t catch it as it’s happening, we miss it.

In game-development systems like the Lightweight Java Game Library (LWJGL), it’s more common to give users the functionality to check when they want whether a chosen key is ‘down’.

To create that functionality, we’ll wrap the JavaFX event management in a polling-friendly wrapper.

To convert event handling to polling-based, we’ll have to set up a listener to run every time a key is pressed and keep track of the keys ourselves.

We have no need to maintain more than one set of keys, so we’ll make our KeyPolling class a Singleton.

To set up our KeyPolling class, we just need to set the Scene that we want to keep track of in the main class (SpaceShooter.java)

KeyPolling.getInstance().pollScene(scene);

Because it’s static, we can easily access it in our GameController, and because all the logic is properly encapsulated in the KeyPolling class, all we need to worry about is asking whether our User Input keys are currently pressed!

Simple game loop combined with a rendering pipeline creates a top-down space shooter.

If you run the code you’ve got, you’ll just see grey – no spaceship and definitely no movement yet. That’s because although we’ve set up the Entity and we’ve set up the Key listener, we haven’t linked them into the animation timer. That’s the rendering pipeline.

3. Putting the rendering pipeline into the game loop

To link the input with the canvas, we’ll need a rendering pipeline that follows three simple steps:

  1. Prepare’s the canvas by clearing the last frame
  2. Updates the player’s movement based on the user’s input, and
  3. Prints the current frame’s content including the background and player

If you’re new to game development, this is probably the most confusing part. We don’t move pieces about like we would in the real world. Instead, we clear the entire contents of the screen and recreate the entire world every frame.

To make all of this easier, we’ll collect the rendering code into a class called Renderer. Before the game loop, we’ll pass it the Canvas so it can draw on it each frame.

Renderer renderer = new Renderer(this.gameCanvas);

We’ll walk through how to get the Renderer going inside the game loop one step at a time. If you want the full code, it’s right here.

a. Clearing the last frame

Clearing the last frame is pretty simple. In other rendering pipelines, you’d have to clear buffer bits and swap frame buffers (complicated!). Luckily, with JavaFX, we just draw a giant rectangle the same shape of the canvas…

public void prepare(){
    context.setFill( new Color(0.68, 0.68, 0.68, 1.0) );
    context.fillRect(0,0, canvas.getWidth(),canvas.getHeight());
}

It’s not graceful, but it works..

Now the canvas is clear, we can draw the next frame’s background and player positions.

b. Calculating Player Movement

Every frame, the Renderer’s going to print all its entities to the canvas. We’ve only got one Entity for this game, but as you add enemies, resources and more, it’s useful to make a list. We then print each using it’s stored image, position, rotation and scale.

So, ahead of using the Renderer in the game loop, we need to pass the player Entity to it.

renderer.addEntity(player);

The intricacies of how to print something to a canvas aren’t as relevant to the game loop as how we’re working out the player’s movement. If you’re interested, it’s all above, though.

Every frame, we’ll use our KeyPolling class, which knows, which keys are currently down, to determine whether to add thrust, or turn our spaceship.

We’ll do that by defining a method updatePlayerMovement(secondsSinceLastFrame).

It’s going to go through the keys we define as important and – if pressed – apply the desired effect. In this case, we’re doing to control the spaceship with Up, Down, Left and Right. Although, we could also use W, A, S and D

In fact, a pretty common build on this type of game would be multiplayer with separate controls.

Finally, the method calls the player.update(). With our newly calcualted values of thrust and torque (turn), the spaceship knows where to go next.

private void updatePlayerMovement(float frameDuration) {
    if (keys.isDown(KeyCode.UP)) {
        player.addThrust(20 * frameDuration);
    } else if (keys.isDown(KeyCode.DOWN)) {
        player.addThrust(-20 * frameDuration);
    }

    if (keys.isDown(KeyCode.RIGHT)) {
        player.addTorque(120f * frameDuration);
    } else if (keys.isDown(KeyCode.LEFT)) {
        player.addTorque(-120f * frameDuration);
    }

    player.update();
}

c. Drawing the Results To the Screen

Once we’ve updated the position of the player, we can draw the background and player in a single method:

public void render() {
    ....
    if(background!=null){
        context.drawImage(background, 0, 0);
    }

    for (Entity entity : entities) {
        transformContext(entity);
        Point2D pos = entity.getDrawPosition();

        context.drawImage(
                entity.getImage(),
                pos.getX(),
                pos.getY(),
                entity.getWidth(),
                entity.getHeight()
        );
    }
    ...
}

Finally, we sew it all together by defining the AnimationTimer’s tick() method to perform that rendering pipeline. We’ll do this right after we’ve defined the Renderer and passed it the player Entity.

GameLoopTimer timer = new GameLoopTimer() {
    @Override
    public void tick(float secondsSinceLastFrame) {
        renderer.prepare();
        updatePlayerMovement(secondsSinceLastFrame);
        renderer.render();
    }
};
timer.start();

You’ll find a link to the code for the whole project at the end, but it wouldn’t feel complete without including the GameController here for clarity. In case you want to see where the code above fits in.

Conclusions

In three steps we’ve created an animation game loop for our game, established Entities to draw to the screen and created a simple Rendering pipeline. It’s a pretty simple rendering pipeline,but that’s the beauty of using JavaFX.

We used JavaFX’s native 2D canvas and support for image loading to draw both the brackground and the player entities.

The whole code for the project can be found on my GitHub here in the SpaceShooter subdirectory.

Simple game loop combined with a rendering pipeline creates a top-down space shooter.