Creating and Packaging a Game in Java FX 2.2

June 23rd, 2013 by

It’s been a long way for Java FX from the days of the F3 project the current release 2.2. Today there are many options how to create a Java FX application .. you may be using Java, Scala, Groovy or Visage, you may create your application in a programmatic way using the comfortable integrated builders or you may create your views using XML layouts and easy data-bindings with a few annotations.

If you need to bind your UI component properties to a specific application state, there’s a nice properties- and bindings API that makes your life easier.

In the following tutorial, I’m going to create a simple game application – one version using FXML templates, model- and controller classes and using external stylesheets – the other version as a programmatic version in one java class.

Finally I’m showing how easy it is to create a shippable application either as runnable jar or as Java Web Start/JNLP application by using Gradle and the Java FX Plugin for Gradle.

 

Game Rules

Game Screen - GameOver

Game Screen - GameOver

Our game should follow these simple rules:

  • The game is won when all boxes are hit
  • The game is lost when the ball reaches the lower boundary of the game raster
  • When the ball hits the left, top or right wall it bounces back and its speed is incremented
  • The player may move the paddle using his mouse and drag the paddle on the y-axis
  • When the ball hits the paddle, its speed is incremented
  • A progress bar with a label gives an information how many boxes are left
  • The game is started when the player presses the start button
  • When the game is lost, pressing the start button restarts the game

Creating a View using FXML Bindings

There are different ways to construct the graphical user interface and arrange its elements and ui components.

We’re using xml based declaration here – if you prefer to create your user interface in a programmatic way, please feel free to have a look at the chapter “Single class, no XML version”.

When using a modern IDE like Eclipse/IntelliJ/NetBeans the editor should help us by offering suggestions as we type and by marking missing references to a field in the controller class.

So first of all, we need to import the classes we’re using here – this is done by adding an <?import /> element to the layout.

If we wanted to add a controller class for the view, we should specify it using fx:controller .. e.g.: fx:controller=”com.hascode.jfx.game.BallGameController”

When we want to bind an element from our view template to a field in the controller class, we’re using the attribute fx:id to reference to a field.

An example: Our Group element contains fx:id=”area” – so BallGameController must contain a field of type Group named area.

We should avoid any definition here that can be achieved using style-sheets.

<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.collections.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Hyperlink?>
 
<Group xmlns:fx="http://javafx.com/fxml" fx:controller="com.hascode.jfx.game.BallGameController" fx:id="area">
	<Circle fx:id="ball" radius="10.0" fill="BLACK" />
	<Rectangle fx:id="borderTop" x="0" y="30" width="500" height="2" />
	<Rectangle fx:id="borderBottom"  x="0" y="500" width="500" height="2"/>
	<Rectangle fx:id="borderLeft"  x="0" y="0" width="2" height="500"/>
	<Rectangle fx:id="borderRight"  x="498" y="0" width="2" height="500"/>
	<Rectangle fx:id="paddle"  x="200" y="460" width="100" height="15" layoutX="20" fill="BLACK"/>
	<Text fx:id="gameOverText" text="Game Over" fill="RED" layoutX="150" layoutY="330"/>
	<Text fx:id="winnerText" text="You've won!" fill="GREEN" layoutX="150" layoutY="330"/>
	<ToolBar minWidth="500">
		<Button fx:id="startButton" text="Start"/>
		<Button fx:id="quitButton" text="Quit"/>
		<ProgressBar fx:id="progressBar" progress="100"/>
		<Label fx:id="remainingBlocksLabel"/>
	</ToolBar>
	<ToolBar minWidth="500" layoutY="500">
		<Hyperlink text="www.hascode.com" layoutX="360" layoutY="505" />
	</ToolBar>
</Group>

Creating the Ball Game Application

This is the entry point for our application. Our class extends javafx.application.Application and initializes the stage and renders a scene using a defined FXML template.

In addition we’re setting the application title and an icon for the application by applying it to our stage.

package com.hascode.jfx.game;
 
import java.io.IOException;
 
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
 
public class BallGame extends Application {
	private static final String VIEW_GAME = "/view/GameView.fxml";
	private static final String STYLESHEET_FILE = "/stylesheet/style.css";
	public static final Image ICON = new Image(
			BallGame.class.getResourceAsStream("/image/head.png"));
 
	@Override
	public void start(final Stage stage) throws Exception {
		initGui(stage);
	}
 
	private void initGui(final Stage stage) throws IOException {
		Parent root = FXMLLoader.load(getClass().getResource(VIEW_GAME));
		Scene scene = SceneBuilder.create().root(root).width(500).height(530)
				.fill(Color.GRAY).build();
		scene.getStylesheets().add(STYLESHEET_FILE);
		stage.setScene(scene);
		stage.setTitle("hasCode.com - Java FX 2 Ball Game Tutorial");
		stage.getIcons().add(ICON);
		stage.show();
	}
 
	public static void main(final String... args) {
		Application.launch(args);
	}
 
}

Defining the Game Model

The model is a class that encapsules our game’s state and makes use of Java FX Properties and Bindings framework that allows us to bind properties of our UI elements to a specific state.

There is an interesting tutorial to be found at the Oracle.com website about using properties and bindings.

My design here is not very elegant but sufficient for the purpose of a tutorial:

package com.hascode.jfx.game;
 
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.image.ImageView;
 
public class GameModel {
	// amount of horizontal blocks.
	private final int INITIAL_BLOCKS_HORIZONTAL = 10;
 
	// amount of vertical blocks.
	private final int INITIAL_BLOCKS_VERTICAL = 5;
 
	// amount of blocks total = vertical * horizontal
	private final int INITIAL_AMOUNT_BLOCKS = getInitialBlocksHorizontal()
			* getInitialBlocksVertical();
 
	// coordinates of the ball
	private final DoubleProperty ballX = new SimpleDoubleProperty();
	private final DoubleProperty ballY = new SimpleDoubleProperty();
 
	// x coordinate of the paddle
	private final DoubleProperty paddleX = new SimpleDoubleProperty();
 
	// game is stopped?
	private final BooleanProperty gameStopped = new SimpleBooleanProperty();
 
	// game is lost?
	private final BooleanProperty gameLost = new SimpleBooleanProperty(false);
 
	// game is won?
	private final BooleanProperty gameWon = new SimpleBooleanProperty(false);
 
	// amount of boxes left
	private final DoubleProperty boxesLeft = new SimpleDoubleProperty(
			getInitialAmountBlocks());
 
	// ball is moving in direction: down?
	private boolean movingDown = true;
 
	// ball is moving in direction: right?
	private boolean movingRight = true;
 
	// ball moving speed
	private double movingSpeed = 1.0;
 
	// paddle drag/translate x
	private double paddleDragX = 0.0;
	private double paddleTranslateX = 0.0;
 
	// a collection of image elements
	private final ObservableList<ImageView> boxes = FXCollections
			.observableArrayList();
 
	public void reset() {
		getBoxesLeft().set(getInitialAmountBlocks());
		for (ImageView r : boxes) {
			r.setVisible(true);
		}
		setMovingSpeed(1.0);
		setMovingDown(true);
		setMovingRight(true);
		getBallX().setValue(250);
		getBallY().setValue(350);
		paddleX.setValue(175);
		gameStopped.set(true);
		getGameLost().set(false);
		getGameWon().set(false);
		setPaddleDragX(0.);
		setPaddleTranslateX(0.);
	}
 
        // getter & setter ommitted
 
}

The Game Controller

In the first step, we’re using @FXML annotations to bind those UI elements from the XML view template to the controller that we need for further modification or data bindings to the model.

To make this work, the controller class needs to implement javafx.fxml.Initializable.

Besides the ui element bindings, we’re creating a new instance of our model class and we’re creating a new timeline running infinitely to trigger our heartbeat event handler every 10 milliseconds.

package com.hascode.jfx.game;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Group;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.image.ImageView;
import javafx.scene.image.ImageViewBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.util.Duration;
 
public class BallGameController implements Initializable {
	// UI ELEMENTS
	@FXML
	private Group area;
 
	@FXML
	private Circle ball;
 
	@FXML
	private Rectangle borderTop;
 
	@FXML
	private Rectangle borderBottom;
 
	@FXML
	private Rectangle borderLeft;
 
	@FXML
	private Rectangle borderRight;
 
	@FXML
	private Rectangle paddle;
 
	@FXML
	private Text gameOverText;
 
	@FXML
	private Text winnerText;
 
	@FXML
	private Button startButton;
 
	@FXML
	private Button quitButton;
 
	@FXML
	private ProgressBar progressBar;
 
	@FXML
	private Label remainingBlocksLabel;
 
	// GAME MODEL
	private final GameModel model = new GameModel();
 
	// GAME HEARTBEAT
	private final EventHandler<ActionEvent> pulseEvent = new EventHandler<ActionEvent>() {
		@Override
		public void handle(final ActionEvent evt) {
			checkWin();
			checkCollisions();
			updateBallPosition();
		}
	};
 
	// THE TIMELINE, RUNS EVERY 10MS
	private final Timeline heartbeat = TimelineBuilder.create()
			.keyFrames(new KeyFrame(new Duration(10.0), pulseEvent))
			.cycleCount(Timeline.INDEFINITE).build();
 
	/*
	 * (non-Javadoc)
	 *
	 * @see javafx.fxml.Initializable#initialize(java.net.URL,
	 * java.util.ResourceBundle)
	 */
	@Override
	public void initialize(final URL url, final ResourceBundle bundle) {
		bindPaddleMouseEvents();
		bindStartButtonEvents();
		bindQuitButtonEvents();
		bindElementsToModel();
		initializeBoxes();
		initializeGame();
		area.requestFocus();
	}
}

The methods called from initialize() are described in detail in the following section:

Paddle Drag and Drop Events

We want to be able to move the paddle on the y-axis using drag and drop – so we’re binding a MousePressedEvent and a MouseDraggedEvent to change the paddle’s coordinates.

/**
 * binds events to drag the paddle using the mouse.
 */
private void bindPaddleMouseEvents() {
	paddle.setOnMousePressed(new EventHandler<MouseEvent>() {
		@Override
		public void handle(final MouseEvent evt) {
			model.setPaddleTranslateX(model.getPaddleTranslateX() + 150);
			model.setPaddleDragX(evt.getSceneX());
		}
	});
	paddle.setOnMouseDragged(new EventHandler<MouseEvent>() {
		@Override
		public void handle(final MouseEvent evt) {
			if (!model.getGameStopped().get()) {
				double x = model.getPaddleTranslateX() + evt.getSceneX()
						- model.getPaddleDragX();
				model.getPaddleX().setValue(x);
			}
		}
	});
}

Start Button Event Handler

The start button should start or restart the game and this means:

  • The game area needs to be reset
  • The model needs to know that the game is running
  • The timeline should continue to run
/**
 * binds events to the start button. by pressing the start button, the game
 * is initialized and the timeline execution is started.
 */
private void bindStartButtonEvents() {
	startButton.setOnAction(new EventHandler<ActionEvent>() {
		@Override
		public void handle(final ActionEvent evt) {
			initializeGame();
			model.getGameStopped().set(false);
			heartbeat.playFromStart();
		}
	});
}

Quit Button Event Handler

Pressing the quit button should shut down the application.

We’re binding a new action event handler to the quit button and make use of javafx.application.Platform exit method instead of using System.exit().

/**
 * creates event handler for the quit button. pressing it immediatly quits
 * the application.
 */
private void bindQuitButtonEvents() {
	quitButton.setOnAction(new EventHandler<ActionEvent>() {
		@Override
		public void handle(final ActionEvent evt) {
			Platform.exit();
		}
	});
}

Model Data Binding

We’re binding several element properties to the model class here:

  • the start button is enabled/disabled state
  • the ball’s x and y coordinates
  • the paddle’s x coordinate
  • the visibility of the game-over-text and the winner-text
  • the progress bar’s progress state
  • the progress bar’s label – we’re using the javafx.beans.binding.Bindings class here to create a dynamic string, bound to the model that displays the amount of boxes left
/**
 * binds ui elements to model state
 */
private void bindElementsToModel() {
	startButton.disableProperty().bind(model.getGameStopped().not());
	ball.centerXProperty().bind(model.getBallX());
	ball.centerYProperty().bind(model.getBallY());
	paddle.xProperty().bind(model.getPaddleX());
	gameOverText.visibleProperty().bind(model.getGameLost());
	winnerText.visibleProperty().bind(model.getGameWon());
	progressBar.progressProperty().bind(
			model.getBoxesLeft().subtract(model.getInitialAmountBlocks())
					.multiply(-1).divide(model.getInitialAmountBlocks()));
	remainingBlocksLabel.textProperty().bind(
			Bindings.format("%.0f boxes left", model.getBoxesLeft()));
}

Initializing the target Boxes

We’re creating a bunch of boxes to shoot at with the ball.

For rendering each box we’re using an image (the same as the application’s icon) here.

The amount of rows and columns is defined in our model class.

/**
 * initializes the boxes.
 */
private void initializeBoxes() {
	int startX = 15;
	int startY = 30;
	for (int v = 1; v <= model.getInitialBlocksVertical(); v++) {
		for (int h = 1; h <= model.getInitialBlocksHorizontal(); h++) {
			int x = startX + (h * 40);
			int y = startY + (v * 40);
			ImageView imageView = ImageViewBuilder.create()
					.image(BallGame.ICON).layoutX(x).layoutY(y).build();
			model.getBoxes().add(imageView);
		}
	}
	area.getChildren().addAll(model.getBoxes());
}

Game Initialization

We’re doing not much here .. just resetting our model..

/**
 * initializes the game, is called for every new game
 */
private void initializeGame() {
	model.reset();
}

Check if the game is won

The game is one when there’s no box left on the game stage. If the game is won, we need to tell it to the model and to stop the hearbeat event.

/**
 * checks if the game is won.
 */
private void checkWin() {
	if (0 == model.getBoxesLeft().get()) {
		model.getGameWon().set(true);
		model.getGameStopped().set(true);
		heartbeat.stop();
	}
}

Check for ball collisions

We’re using javafx.scene.control.Control‘s intersect method to check for a collision with the walls, the paddle or the dead zone aka borderBottom.

When the ball hits the paddle or the left, right or top wall, its speed is incremented, when the ball hits the left or right wall, we’re changing the ball’s direction on the x-axis, when the ball hits the top wall, we’re changing its direction on the y-axis.

When the ball intersects the borderBottom/ dead zone, the game is over and we’re stopping the Timeline and update the model to reflect the fact that the game is lost.

/**
 * checks if the ball has collisions with the walls or the paddle.
 */
private void checkCollisions() {
	checkBoxCollisions();
	if (ball.intersects(paddle.getBoundsInLocal())) {
		model.incrementSpeed();
		model.setMovingDown(false);
	}
	if (ball.intersects(borderTop.getBoundsInLocal())) {
		model.incrementSpeed();
		model.setMovingDown(true);
	}
	if (ball.intersects(borderBottom.getBoundsInLocal())) {
		model.getGameStopped().set(true);
		model.getGameLost().set(true);
		heartbeat.stop();
	}
	if (ball.intersects(borderLeft.getBoundsInLocal())) {
		model.incrementSpeed();
		model.setMovingRight(true);
	}
	if (ball.intersects(borderRight.getBoundsInLocal())) {
		model.incrementSpeed();
		model.setMovingRight(false);
	}
	if (paddle.intersects(borderRight.getBoundsInLocal())) {
		model.getPaddleX().set(350);
	}
	if (paddle.intersects(borderLeft.getBoundsInLocal())) {
		model.getPaddleX().set(0);
	}
}

Eliminate a box when hit by the ball

When the ball hits a box, we’re hiding the box on the game raster and update the model to know how much boxes are left.

/**
 * checks if the ball collides with one or more of the boxes. if there's a
 * collision, the box is removed.
 */
private void checkBoxCollisions() {
	for (ImageView r : model.getBoxes()) {
		if (r.isVisible() && ball.intersects(r.getBoundsInParent())) {
			model.getBoxesLeft().set(model.getBoxesLeft().get() - 1);
			r.setVisible(false);
		}
	}
}

Calculate ball movement speed and direction

Depending on the current speed of the ball, we’re calculating the new ball position on the x- and y-axis and finally we’re updating the model to reflect these changes.

/**
 * updates the ball position by calculating the ball's speed, position and
 * direction.
 */
private void updateBallPosition() {
	double x = model.isMovingRight() ? model.getMovingSpeed() : -model
			.getMovingSpeed();
	double y = model.isMovingDown() ? model.getMovingSpeed() : -model
			.getMovingSpeed();
	model.getBallX().set(model.getBallX().get() + x);
	model.getBallY().set(model.getBallY().get() + y);
}

Adjust look and feel using a Stylesheet

This is our stylesheet/style.css – we’re just adding some basic styles that are applied to .root, and some drop-shadow effects to the text and some game elements.

The complete reference for Java FX CSS stylesheets can be found here.

.root {
	-fx-font-size: 16pt;
	-fx-font-family: "Arial";
	-fx-width: 500px;
	-fx-height: 530px;
}
 
#gameOverText,#winnerText {
	-fx-font-size: 40pt;
	-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 100), 1, 1, 4, 2 );
}
 
#paddle,#ball {
	-fx-effect: dropshadow(three-pass-box, #000000, 10.0,0.0,0.5,4.0);
}

Gradle Java FX Plugin – Building the App

I am using Danno Ferrin’s Java FX plugin for Gradle here to create a runnable jar and the Java Web Start / JNLP files.

The plugin really eases the build process here and there are only two steps needed to build the application with this plugin.

First of all, add the following lines to your build.gradle in the project directory:

apply from: 'http://dl.bintray.com/content/shemnon/javafx-gradle/0.3.0/javafx.plugin'
javafx {
    mainClass = 'com.hascode.jfx.game.BallGame'
}

The first statement adds the Java FX plugin, in the second one is needed to specify the main class for the build process.

After running the following command, you can find the application build in the directory build/distributions

gradle assemble

Download the Game as runnable Jar

I have put the compiled game as runnable Jar on Bitbucket:

https://bitbucket.org/hascode/jfx-ball-game/downloads/jfx-ball-game.jar

You may run the game with Oracle JRE >= Java 7 Update 6 installed like this:

java -cp jfx-ball-game.jar

Single Class, no XML Version

If you’d like to see an alternative not using FXML but a lot of chained element builders I have build the game in a single Java class (loong) ..

Please feel free to have a look at my SingleClassNoXmlBallGame.java at Bitbucket.

Tutorial Sources

Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/jfx-ball-game.git

Screencast

This is our final game in action (screencast on YouTube).

Resources

Article Updates

  • 2018-06-01: Embedded YouTube video removed (GDPR/DSGVO).

Tags: , , , , , , , ,

Search
Categories