## Sunday, May 17, 2015

### A* Algorithm (or A Star Algorithm) in JavaFX

In a previous blog we created a simple tower defense engine. It's all working nice, but we want the enemies to enter at a given start point and move to an end point. It would all be easy if the path is a given and hence can't be changed.

But as the enemy moves we want to create towers in their way and hence we could block their path. When that happens the enemies will have to find a new path.

One of the commonly used path finding algorithms is the A* Algorithm (aka A Star Algorithm). It's very good described on Wikipedia. The implementation in JavaFX is straightforward, you simply have to follow the pseudo code on Wikipedia using standard Java means.

I'll post the code here, including the pseudo code and how it got translated. Here's what we'll achieve:

What we need first are cells that we can access and in which we can store the variables

• g = distance from start to current cell
• h = distance from current cell to goal
• f = g + h

We use a simple Euclidian distance calculation. You can choose whatever you prefer and whatever your game needs, e. g. distance calculations considering hills and slopes.

The algorithm is implemented as a common algorithm. So you have to map the cells of your grid to the grid algorithm and vice versa when the path is found. To facilitate the mapping, you can store your actual grid cell in the A* algorithm's cell.

And then there's the attribute which tells us if a cell is traversable (i. e. a path candidate) or not.

So the code for a cell could look like this.

```
package application.astar;

/**
* A virtual cell which defines if it is traversable. The cell is used to store the f,g,h values of the A* algorithm.
*
* @param <T> You can use {@link #obj} to store information about e. g. an external object.
*/
public class AStarCell<T> implements Cloneable {

int col;
int row;
boolean isTraversable;

/**
* A pointer to an object of your choice. Unused in the A* algorithm.
* Usually you convert your grid to the (virtual) A* grid, then find the path.
* Afterwards you'd want to find out which of your cells are on the path.
*/
T obj;

double g;
double f;
double h;

AStarCell<T> cameFrom;

public AStarCell( int col, int row, boolean isPath, T obj) {
this.col=col;
this.row=row;
this.isTraversable = isPath;
this.obj = obj;
}

public T getObject() {
return obj;
}

public double getF() {
return f;
}

public double getG() {
return g;
}

public double getH() {
return h;
}

/**
* Cloning only used in order to show the steps of the A* algorithm.
*/
public AStarCell<T> clone() {

AStarCell<T> clonedCell = new AStarCell<T>( col, row, isTraversable, obj);
clonedCell.f =f;
clonedCell.g = g;
clonedCell.h = h;

if( cameFrom != null) {
clonedCell.cameFrom = cameFrom.clone();
}

return clonedCell;

}
}

```
Then we need to put the cells into a grid. The grid also knows about the neighbors of the cells, so we implement the algorithm to find the neighbors in there. It's just a matter of checking 8 coordinates.

```
package application.astar;

/**
* Virtual grid for the A* algorithm. Used to determine the neighbors of a cell.
*/
public class AStarGrid<T> {

AStarCell<T>[][] gridCells;
int cols;
int rows;

public AStarGrid( int cols, int rows) {
this.cols = cols;
this.rows = rows;
gridCells = new AStarCell[rows][cols];
}

public void setCell( T cell, int col, int row, boolean path) {
gridCells[row][col] =  new AStarCell<T>(col,row, path, cell);
}

public AStarCell<T> getCell( int col, int row) {
return gridCells[row][col];
}

/**
* Get neighboring cells relative to the given cell. By default they are top/right/bottom/left.
* If allowDiagonals is enabled, then also top-left, top-right, bottom-left, bottom-right cells are in the results.
* @param cell
* @param allowDiagonals
* @return
*/
public AStarCell<T>[] getNeighbors(AStarCell<T> cell, boolean allowDiagonals) {

AStarCell<T>[] neighbors = new AStarCell[ allowDiagonals ? 8 : 4];

int currentColumn = cell.col;
int currentRow = cell.row;

int neighborColumn;
int neighborRow;

// top
neighborColumn = currentColumn;
neighborRow = currentRow - 1;

if (neighborRow >= 0) {
if( gridCells[neighborRow][neighborColumn].isTraversable) {
neighbors = gridCells[neighborRow][neighborColumn];
}
}

// bottom
neighborColumn = currentColumn;
neighborRow = currentRow + 1;

if (neighborRow < rows) {
if( gridCells[neighborRow][neighborColumn].isTraversable) {
neighbors = gridCells[neighborRow][neighborColumn];
}
}

// left
neighborColumn = currentColumn - 1;
neighborRow = currentRow;

if ( neighborColumn >= 0) {
if( gridCells[neighborRow][neighborColumn].isTraversable) {
neighbors = gridCells[neighborRow][neighborColumn];
}
}

// right
neighborColumn = currentColumn + 1;
neighborRow = currentRow;

if ( neighborColumn < cols) {
if( gridCells[neighborRow][neighborColumn].isTraversable) {
neighbors = gridCells[neighborRow][neighborColumn];
}
}

if (allowDiagonals) {

// top/left
neighborColumn = currentColumn - 1;
neighborRow = currentRow - 1;

if (neighborRow >= 0 && neighborColumn >= 0) {
if( gridCells[neighborRow][neighborColumn].isTraversable) {
neighbors = gridCells[neighborRow][neighborColumn];
}
}

// bottom/right
neighborColumn = currentColumn + 1;
neighborRow = currentRow + 1;

if (neighborRow < rows && neighborColumn < cols) {
if( gridCells[neighborRow][neighborColumn].isTraversable) {
neighbors = gridCells[neighborRow][neighborColumn];
}
}

// top/right
neighborColumn = currentColumn + 1;
neighborRow = currentRow - 1;

if (neighborRow >= 0 && neighborColumn < cols) {
if( gridCells[neighborRow][neighborColumn].isTraversable) {
neighbors = gridCells[neighborRow][neighborColumn];
}
}

// bottom/left
neighborColumn = currentColumn - 1;
neighborRow = currentRow + 1;

if (neighborRow < rows && neighborColumn >= 0) {
if( gridCells[neighborRow][neighborColumn].isTraversable) {
neighbors = gridCells[neighborRow][neighborColumn];
}
}

}

return neighbors;
}

}
```

Now we got the elements to implement the algorithm. It's really easy to do. Here's the code:
```
package application.astar;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Set;

/**
* This is a 1:1 translation of the algorithm on wikipedia: http://en.wikipedia.org/wiki/A*_search_algorithm
*

function A*(start,goal)
closedset := the empty set    // The set of nodes already evaluated.
openset := {start}    // The set of tentative nodes to be evaluated, initially containing the start node
came_from := the empty map    // The map of navigated nodes.

g_score[start] := 0    // Cost from start along best known path.
// Estimated total cost from start to goal through y.
f_score[start] := g_score[start] + heuristic_cost_estimate(start, goal)

while openset is not empty
current := the node in openset having the lowest f_score[] value
if current = goal
return reconstruct_path(came_from, goal)

remove current from openset
for each neighbor in neighbor_nodes(current)
if neighbor in closedset
continue
tentative_g_score := g_score[current] + dist_between(current,neighbor)

if neighbor not in openset or tentative_g_score < g_score[neighbor]
came_from[neighbor] := current
g_score[neighbor] := tentative_g_score
f_score[neighbor] := g_score[neighbor] + heuristic_cost_estimate(neighbor, goal)
if neighbor not in openset

return failure

function reconstruct_path(came_from,current)
total_path := [current]
while current in came_from:
current := came_from[current]
total_path.append(current)

*/

@SuppressWarnings("rawtypes")
public class AStarAlgorithm {

/**
* Get the cell with the minimum f value.
*/
public class CellComparator implements Comparator<AStarCell>
{
@Override
public int compare(AStarCell a, AStarCell b)
{
return Double.compare(a.f, b.f);
}
}

/**
* Find a path from start to goal using the A* algorithm
*/
@SuppressWarnings("unchecked")
public List<AStarCell> getPath( AStarGrid grid, AStarCell start, AStarCell goal, boolean allowDiagonals) {

AStarCell current = null;
boolean containsNeighbor;

int cellCount = grid.rows * grid.cols;

// closedset := the empty set    // The set of nodes already evaluated.
Set<AStarCell> closedSet = new HashSet<>( cellCount);

// openset := {start}    // The set of tentative nodes to be evaluated, initially containing the start node
PriorityQueue<AStarCell> openSet = new PriorityQueue<AStarCell>( cellCount, new CellComparator());

// g_score[start] := 0    // Cost from start along best known path.
start.g = 0d;

// Estimated total cost from start to goal through y.
// f_score[start] := g_score[start] + heuristic_cost_estimate(start, goal)
start.f = start.g + heuristicCostEstimate(start, goal);

// while openset is not empty
while( !openSet.isEmpty()) {

// current := the node in openset having the lowest f_score[] value
// note: we have a priorityqueue => for performance reasons we also remove the item instead of removing it later (as suggested in the algorithm)
// remove current from openset
current = openSet.poll();

// if current = goal
//        return reconstruct_path(came_from, goal)
if( current == goal) {
return reconstructPath( goal);
}

// remove current from openset
// already done in openSet.poll(), see above

// for each neighbor in neighbor_nodes(current)
for( AStarCell neighbor: grid.getNeighbors( current, allowDiagonals)) {

if( neighbor == null) {
continue;
}

// if neighbor in closedset
//   continue
if( closedSet.contains( neighbor)) {
continue;
}

// tentative_g_score := g_score[current] + dist_between(current,neighbor)
double tentativeScoreG = current.g + distBetween( current, neighbor);

// if neighbor not in openset or tentative_g_score < g_score[neighbor]
if( !(containsNeighbor=openSet.contains( neighbor)) || Double.compare(tentativeScoreG, neighbor.g) < 0) {

// came_from[neighbor] := current
neighbor.cameFrom = current;

// g_score[neighbor] := tentative_g_score
neighbor.g = tentativeScoreG;

// f_score[neighbor] := g_score[neighbor] + heuristic_cost_estimate(neighbor, goal)
neighbor.h = heuristicCostEstimate(neighbor, goal);
neighbor.f = neighbor.g + neighbor.h;

// if neighbor not in openset
if( !containsNeighbor) {
}
}
}

}

// nothing found
return new ArrayList<>();
}

/**
* Create final path of the A* algorithm.
* The path is from goal to start.
*/
// function reconstruct_path(came_from,current)
private List<AStarCell> reconstructPath( AStarCell current) {

List<AStarCell> totalPath = new ArrayList<>(200); // arbitrary value, we'll most likely have more than 10 which is default for java

// total_path := [current]

// while current in came_from:
// current := came_from[current]
while( (current = current.cameFrom) != null) {

// total_path.append(current)

}

}

/**
* Distance between a given cell and its neighbor.
* Used in the algorithm as distance calculation between the current cell and a neighbor.
* In our case we use the same distance which we use from the current cell to the goal.
*/
private double distBetween(AStarCell current, AStarCell neighbor) {
return heuristicCostEstimate( current, neighbor);
}

/**
* Distance between two cells. We use the euclidian distance here.
* Used in the algorithm as distance calculation between a cell and the goal.
*/
private double heuristicCostEstimate(AStarCell from, AStarCell to) {

return Math.sqrt((from.col-to.col)*(from.col-to.col) + (from.row - to.row)*(from.row-to.row));

}

}

```
Since that was very easy, I got some spare time left to implement a GUI around it. Among other featuers the GUI allows you to

• click/drag primary mouse button to create obstacles
• click/drag secondary mouse button to remove obstacles

Path finding is started once you release the mouse button In order to help you understand the algorithm better I added the values which we store in the cells.

• g = top/left value
• h = top/right value
• f = g + h = center value

The colorization of the cells is as follows:

• path = blue
• obstacle = black
• cells of closed set = green
• cells of open set = red

You can use the step slider in order to show the calculations step by step.

A screenshot:

And here's a video of how it looks like in action:

Interested ones can get the AStarFX code on github. If you'd like to have other grid dimensions, simply modify the file Settings.java.

It will be nice to see this algorithm work in our tower defense game.

Have fun and keep on coding :-)

## Saturday, May 16, 2015

### Simple Game Engine: Tower Defense in JavaFX

Someone asked on StackOverflow about how to move enemies on the screen in a tower defense game. I thought that it should be easy to do this with our game engine. And it was.

There's a lot of code we can reuse and 2 things that we basically needed to change:

• the input is now via mouse
• the towers should rotate towards their enemies and fire

The code in this blog is an extended version of the one I posted on StackOverflow. You can use whatever images you like. This is just a prototype, so I took a brief look at some sites and I found nice ones at lostgarden. These awesome images make our game look very nice:

Since we are just extending our previously created engine a bit, I'll only post the main things here and the full code at the end of this blog.

The easy part: Instead of the keyboard input we use a mouse listener with which we place an instance of our tower:
```  // add event handler to create towers
createTower(e.getX(), e.getY());
});
```
and we create the tower as we would do any other player
``` private void createTower( double x, double y) {

Image image = playerImage;

// center image at position
x -= image.getWidth() / 2;
y -= image.getHeight() / 2;

// create player
Tower player = new Tower(playfieldLayer, image, x, y, 180, 0, 0, 0, Settings.PLAYER_SHIP_HEALTH, 0, Settings.PLAYER_SHIP_SPEED);

// register player

}
```

There's nothing special to it. In your final game you'll probably want to place the towers on a pre-defined grid.

The more interesting part is to find the target for each tower and rotate towards it. The finding mechanism was already covered in the Anansi blog. A target can be found when it's within search range. Once it's within search range, we want to rotate the tower towards the target. And once the tower is rotated towards the target and the tower's guns can hit the target because it is within firing range, we want to fire at the target. Usually we do an animation (we already learned how to do that in a previous blog post) when firing, but I simply change the color to red in order to indicate some action of the guns. I'll leave the bullet part out for sake of simplicity.

So coming back to our code, we need to find a target that is within search range:
```
public void findTarget( List targetList) {

// we already have a target
if( getTarget() != null) {
return;
}

SpriteBase closestTarget = null;
double closestDistance = 0.0;

for (SpriteBase target: targetList) {

if (!target.isAlive())
continue;

//get distance between follower and target
double distanceX = target.getCenterX() - getCenterX();
double distanceY = target.getCenterY() - getCenterY();

//get total distance as one number
double distanceTotal = Math.sqrt(distanceX * distanceX + distanceY * distanceY);

// check if enemy is within range
if( Double.compare( distanceTotal, targetRange) > 0) {
continue;
}

if (closestTarget == null) {

closestTarget = target;
closestDistance = distanceTotal;

} else if (Double.compare(distanceTotal, closestDistance) < 0) {

closestTarget = target;
closestDistance = distanceTotal;

}
}

setTarget(closestTarget);

}
```

The movement of the towers is reduced to rotation. We don't want to instantly rotate to the target, instead we'd like to rotate smoothly to it, with some kind of speed limitation. The problem when coding this kind of things is that the angle isn't a full 360 degree circle, instead it's a range from -180 to 180. So we have to consider this. The common problem is that e. g. if the tower faces top/right and the enemy moves down, then once the angle becomes negative (i. e. -180 degrees), then the tower would normally rotate the long way, i. e. clockwise towards the target. So we have to consider this, we want the short way, not the long way. A post in LostInActionScript helped solve the problem.

``` public void move() {

SpriteBase follower = this;

// reset within firing range
withinFiringRange = false;

// rotate towards target
if( target != null)
{

// calculate rotation angle; follower must rotate towards the target
// we need to consider the angle ranges from -180..180 by transforming the coordinates to a range of 0..360 and checking the values
// the calculation is done in degrees

double xDist = target.getCenterX() - follower.getCenterX();
double yDist = target.getCenterY() - follower.getCenterY();

double angleToTarget = Math.atan2(yDist, xDist) - Math.PI / 2; // -Math.PI / 2 because our sprite faces downwards

double targetAngle = Math.toDegrees( angleToTarget);
double currentAngle = follower.r;

// check current angle range
if( Math.abs(currentAngle) > 360) {
if( currentAngle < 0) {
currentAngle = currentAngle % 360 + 360;
} else {
currentAngle = currentAngle % 360;
}
}

// calculate angle difference between follower and target
double diff = targetAngle - currentAngle;

// normalize the target angle
if( Math.abs( diff) < 180) {
// values within range => ok
} else {
if( diff > 0) {
targetAngle -= 360;
} else {
targetAngle += 360;
}
}

// get the angle between follower and target
diff = targetAngle - currentAngle;

// add the differnce angle to the current angle while considering easing when rotation comes closer to the target point
currentAngle = currentAngle + diff / roatationEasing;

// apply rotation
follower.r = currentAngle;

// determine if the player ship is within firing range; currently if the player ship is within 10 degrees (-10..+10)
withinFiringRange = Math.abs( this.targetAngle-this.currentAngle) < 20;

}

super.move();

}
```

Well, that's basically it. If you'd like to see it in action, here's a video:

This is looking very nice. However, for a proper tower defense game the enemies need to follow a given path. It'll be interesting to find out how to implement that. A frequently used algorithm for that is the A* algorithm. Well, but first we'll have to find out how the A* algorithm works. So this is to-be-continued in another blog post.

And here's the source code for the interested ones.

Game.java
```
package game.towerdefense;

import game.towerdefense.sprites.Enemy;
import game.towerdefense.sprites.SpriteBase;
import game.towerdefense.sprites.Tower;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextBoundsType;
import javafx.scene.transform.Scale;
import javafx.stage.Stage;

public class Game extends Application {

Random rnd = new Random();

Pane backgroundLayer;
Pane playfieldLayer;
Pane scoreLayer;

Image backgroundImage;
Image playerImage;
Image enemyImage;

List<Tower> towers = new ArrayList<>();;
List<Enemy> enemies = new ArrayList<>();;

Text scoreText = new Text();
int score = 0;

Scene scene;

@Override
public void start(Stage primaryStage) {

Group root = new Group();

// create layers
backgroundLayer = new Pane();
playfieldLayer = new Pane();
scoreLayer = new Pane();

// ensure the playfield size so that we can click on it
playfieldLayer.setPrefSize( Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);

// add event handler to create towers
createTower(e.getX(), e.getY());
});

scene = new Scene( root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);

primaryStage.setScene( scene);

// fullscreen
primaryStage.setFullScreen( Settings.STAGE_FULLSCREEN);
primaryStage.setFullScreenExitHint("");

// scale by factor of 2 (in settings we have half-hd) to get proper dimensions in fullscreen (full-hd)
if( primaryStage.isFullScreen()) {
Scale scale = new Scale( Settings.STAGE_FULLSCREEN_SCALE, Settings.STAGE_FULLSCREEN_SCALE);
scale.setPivotX(0);
scale.setPivotY(0);
scene.getRoot().getTransforms().setAll(scale);
}

primaryStage.show();

createBackgroundLayer();
createPlayfieldLayer();
createScoreLayer();
createTowers();

AnimationTimer gameLoop = new AnimationTimer() {

@Override
public void handle(long now) {

spawnEnemies( true);

// check if target is still valid
towers.forEach( tower -> tower.checkTarget());

// tower movement: find target
for( Tower tower: towers) {
tower.findTarget( enemies);
}

// movement
towers.forEach(sprite -> sprite.move());
enemies.forEach(sprite -> sprite.move());

// check collisions
checkCollisions();

// update sprites in scene
towers.forEach(sprite -> sprite.updateUI());
enemies.forEach(sprite -> sprite.updateUI());

// check if sprite can be removed
enemies.forEach(sprite -> sprite.checkRemovability());

// remove removables from list, layer, etc
removeSprites( enemies);

// update score, health, etc
}

};
gameLoop.start();

}

backgroundImage = new Image( getClass().getResource("images/background.png").toExternalForm());
playerImage = new Image( getClass().getResource("images/tower.png").toExternalForm());
enemyImage = new Image( getClass().getResource("images/ship.png").toExternalForm());
}

private void createBackgroundLayer() {
ImageView background = new ImageView( backgroundImage);
}

private void createPlayfieldLayer() {

// shadow effect to show depth
// setting it on the entire group/layer preserves the shadow angle even if the node son the layer are rotated

}

private void createScoreLayer() {

scoreText.setFont( Font.font( null, FontWeight.BOLD, 48));
scoreText.setStroke(Color.BLACK);
scoreText.setFill(Color.RED);

scoreText.setText( String.valueOf( score));

double x = (Settings.SCENE_WIDTH - scoreText.getBoundsInLocal().getWidth()) / 2;
double y = 0;
scoreText.relocate(x, y);

scoreText.setBoundsType(TextBoundsType.VISUAL);

}
private void createTowers() {

// position initial towers
List<Point2D> towerPositionList = new ArrayList<>();

for( Point2D pos: towerPositionList) {

createTower( pos.getX(), pos.getY());

}

}

private void createTower( double x, double y) {

Image image = playerImage;

// center image at position
x -= image.getWidth() / 2;
y -= image.getHeight() / 2;

// create player
Tower player = new Tower(playfieldLayer, image, x, y, 180, 0, 0, 0, Settings.PLAYER_SHIP_HEALTH, 0, Settings.PLAYER_SHIP_SPEED);

// register player

}

private void spawnEnemies( boolean random) {

if( random && rnd.nextInt(Settings.ENEMY_SPAWN_RANDOMNESS) != 0) {
return;
}

// image
Image image = enemyImage;

// random speed
double speed = rnd.nextDouble() * 1.0 + 2.0;

// x position range: enemy is always fully inside the trench, no part of it is outside
// y position: right on top of the view, so that it becomes visible with the next game iteration
double trenchMinX; // left pixel pos of trench
double trenchMaxX; // right pixel pos of trench

// 2 waves: 0 = left, 1 = right
if( rnd.nextInt(2) == 0) {
trenchMinX = 220; // left pixel pos
trenchMaxX = 530; // right pixel pos
} else {
trenchMinX = 760; // left pixel pos
trenchMaxX = 1050; // right pixel pos
}

double x = trenchMinX + rnd.nextDouble() * (trenchMaxX - trenchMinX - image.getWidth());
double y = -image.getHeight();

// create a sprite
Enemy enemy = new Enemy( playfieldLayer, image, x, y, 0, 0, speed, 0, 1,1);

// manage sprite

}

private void removeSprites(  List<? extends SpriteBase> spriteList) {
Iterator<? extends SpriteBase> iter = spriteList.iterator();
while( iter.hasNext()) {
SpriteBase sprite = iter.next();

if( sprite.isRemovable()) {

// remove from layer
sprite.removeFromLayer();

// remove from list
iter.remove();
}
}
}

private void checkCollisions() {

for( Tower tower: towers) {
for( Enemy enemy: enemies) {
if( tower.hitsTarget( enemy)) {

enemy.getDamagedBy( tower);

// TODO: explosion
if( !enemy.isAlive()) {

enemy.setRemovable(true);

// increase score
score++;

}
}
}
}
}

scoreText.setText( String.valueOf( score));
}

public static void main(String[] args) {
launch(args);
}

}

```

Settings.java
```
package game.towerdefense;

public class Settings {

// fullscreen or windowed mode
public static boolean STAGE_FULLSCREEN = false;

// scale by factor of 2 (in settings we have half-hd) to get proper dimensions in fullscreen (full-hd)
public static double STAGE_FULLSCREEN_SCALE = 2;

public static double SCENE_WIDTH = 1280;
public static double SCENE_HEIGHT = 720;

public static double TOWER_DAMAGE = 1;

// distance within which a tower can lock on to an enemy
public static double TOWER_RANGE = 400;

public static double PLAYER_SHIP_SPEED = 4.0;
public static double PLAYER_SHIP_HEALTH = 100.0;

public static int ENEMY_HEALTH = 200;
public static int ENEMY_SPAWN_RANDOMNESS = 30;

}

```

HealthBar.java
```
package game.towerdefense.ui;

import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;

public class HealthBar extends Pane {

Rectangle outerHealthRect;
Rectangle innerHealthRect;

public HealthBar() {

double height = 10;

double outerWidth = 60;
double innerWidth = 40;

double x=0.0;
double y=0.0;

outerHealthRect = new Rectangle( x, y, outerWidth, height);
outerHealthRect.setStroke(Color.BLACK);
outerHealthRect.setStrokeWidth(2);
outerHealthRect.setStrokeType( StrokeType.OUTSIDE);
outerHealthRect.setFill(Color.RED);

innerHealthRect = new Rectangle( x, y, innerWidth, height);
innerHealthRect.setStrokeType( StrokeType.OUTSIDE);
innerHealthRect.setFill(Color.LIMEGREEN);

}

public void setValue( double value) {
innerHealthRect.setWidth( outerHealthRect.getWidth() * value);
}

}

```

Tower.java
```
package game.towerdefense.sprites;

import game.towerdefense.Settings;

import java.util.List;

import javafx.scene.image.Image;
import javafx.scene.layout.Pane;

public class Tower extends SpriteBase {

SpriteBase target; // TODO: use weakreference

double turnRate = 0.6;

double speed;

double targetRange = Settings.TOWER_RANGE; // distance within which a tower can lock on to an enemy

double rotationLimitDeg=0;
double roatationEasing = 10; // the higher the value, the slower the rotation
double targetAngle = 0;
double currentAngle = 0;

boolean withinFiringRange = false;

public Tower(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage, double speed) {

super(layer, image, x, y, r, dx, dy, dr, health, damage);

this.speed = speed;

this.setDamage(Settings.TOWER_DAMAGE);

init();
}

private void init() {

// red colorization

}

@Override
public void move() {

SpriteBase follower = this;

// reset within firing range
withinFiringRange = false;

// rotate towards target
if( target != null)
{

// calculate rotation angle; follower must rotate towards the target
// we need to consider the angle ranges from -180..180 by transforming the coordinates to a range of 0..360 and checking the values
// the calculation is done in degrees

double xDist = target.getCenterX() - follower.getCenterX();
double yDist = target.getCenterY() - follower.getCenterY();

double angleToTarget = Math.atan2(yDist, xDist) - Math.PI / 2; // -Math.PI / 2 because our sprite faces downwards

double targetAngle = Math.toDegrees( angleToTarget);
double currentAngle = follower.r;

// check current angle range
if( Math.abs(currentAngle) > 360) {
if( currentAngle < 0) {
currentAngle = currentAngle % 360 + 360;
} else {
currentAngle = currentAngle % 360;
}
}

// calculate angle difference between follower and target
double diff = targetAngle - currentAngle;

// normalize the target angle
if( Math.abs( diff) < 180) {
// values within range => ok
} else {
if( diff > 0) {
targetAngle -= 360;
} else {
targetAngle += 360;
}
}

// get the angle between follower and target
diff = targetAngle - currentAngle;

// add the differnce angle to the current angle while considering easing when rotation comes closer to the target point
currentAngle = currentAngle + diff / roatationEasing;

// apply rotation
follower.r = currentAngle;

// determine if the player ship is within firing range; currently if the player ship is within 10 degrees (-10..+10)
withinFiringRange = Math.abs( this.targetAngle-this.currentAngle) < 20;

}

super.move();

}

public void checkTarget() {

if( target == null) {
return;
}

if( !target.isAlive() || target.isRemovable()) {
setTarget( null);
return;
}

//get distance between follower and target
double distanceX = target.getCenterX() - getCenterX();
double distanceY = target.getCenterY() - getCenterY();

//get total distance as one number
double distanceTotal = Math.sqrt(distanceX * distanceX + distanceY * distanceY);

if( Double.compare( distanceTotal, targetRange) > 0) {
setTarget( null);
}

}

public void findTarget( List<? extends SpriteBase> targetList) {

// we already have a target
if( getTarget() != null) {
return;
}

SpriteBase closestTarget = null;
double closestDistance = 0.0;

for (SpriteBase target: targetList) {

if (!target.isAlive())
continue;

//get distance between follower and target
double distanceX = target.getCenterX() - getCenterX();
double distanceY = target.getCenterY() - getCenterY();

//get total distance as one number
double distanceTotal = Math.sqrt(distanceX * distanceX + distanceY * distanceY);

// check if enemy is within range
if( Double.compare( distanceTotal, targetRange) > 0) {
continue;
}

if (closestTarget == null) {

closestTarget = target;
closestDistance = distanceTotal;

} else if (Double.compare(distanceTotal, closestDistance) < 0) {

closestTarget = target;
closestDistance = distanceTotal;

}
}

setTarget(closestTarget);

}

public SpriteBase getTarget() {
return target;
}

public void setTarget(SpriteBase target) {
this.target = target;
}

@Override
public void checkRemovability() {

if( Double.compare( health, 0) < 0) {
setTarget(null);
setRemovable(true);
}

}

public boolean hitsTarget( SpriteBase enemy) {

return target == enemy && withinFiringRange;

}

public void updateUI() {

// change effect (color/shadow) depending on whether we're firing or not
if( withinFiringRange) {
} else {
imageView.setEffect(null);
}

super.updateUI();

}
}

```

Enemy.java
```
package game.towerdefense.sprites;

import game.towerdefense.Settings;
import game.towerdefense.ui.HealthBar;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;

public class Enemy extends SpriteBase {

HealthBar healthBar;

double healthMax;

public Enemy(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {

super(layer, image, x, y, r, dx, dy, dr, health, damage);

healthMax = Settings.ENEMY_HEALTH;

setHealth(healthMax);

init();
}

private void init() {
}

@Override
public void checkRemovability() {

if( Double.compare( getY(), Settings.SCENE_HEIGHT) > 0) {
setRemovable(true);
}

}

// create health bar; has to be created here because addToLayer is called in super constructor
// and it wouldn't exist yet if we'd create it as class member
healthBar = new HealthBar();

}

public void removeFromLayer() {

super.removeFromLayer();

this.layer.getChildren().remove(this.healthBar);

}

/**
* Health as a value from 0 to 1.
* @return
*/
public double getRelativeHealth() {
return getHealth() / healthMax;
}

public void updateUI() {

super.updateUI();

// update health bar
healthBar.setValue( getRelativeHealth());

// locate healthbar above enemy, centered horizontally
healthBar.relocate(x + (imageView.getBoundsInLocal().getWidth() - healthBar.getBoundsInLocal().getWidth()) / 2, y - healthBar.getBoundsInLocal().getHeight() - 4);
}
}
```

SpriteBase.java
```
package game.towerdefense.sprites;

import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;

public abstract class SpriteBase {

Image image;
ImageView imageView;

Pane layer;

double x;
double y;
double r;

double dx;
double dy;
double dr;

double health;
double damage;

boolean removable = false;

double w;
double h;

boolean canMove = true;

public SpriteBase(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {

this.layer = layer;
this.image = image;
this.x = x;
this.y = y;
this.r = r;
this.dx = dx;
this.dy = dy;
this.dr = dr;

this.health = health;
this.damage = damage;

this.imageView = new ImageView(image);
this.imageView.relocate(x, y);
this.imageView.setRotate(r);

this.w = image.getWidth(); // imageView.getBoundsInParent().getWidth();
this.h = image.getHeight(); // imageView.getBoundsInParent().getHeight();

}

}

public void removeFromLayer() {
this.layer.getChildren().remove(this.imageView);
}

public Pane getLayer() {
return layer;
}

public void setLayer(Pane layer) {
this.layer = layer;
}

public double getX() {
return x;
}

public void setX(double x) {
this.x = x;
}

public double getY() {
return y;
}

public void setY(double y) {
this.y = y;
}

public double getR() {
return r;
}

public void setR(double r) {
this.r = r;
}

public double getDx() {
return dx;
}

public void setDx(double dx) {
this.dx = dx;
}

public double getDy() {
return dy;
}

public void setDy(double dy) {
this.dy = dy;
}

public double getDr() {
return dr;
}

public void setDr(double dr) {
this.dr = dr;
}

public double getHealth() {
return health;
}

public double getDamage() {
return damage;
}

public void setDamage(double damage) {
this.damage = damage;
}

public void setHealth(double health) {
this.health = health;
}

public boolean isRemovable() {
return removable;
}

public void setRemovable(boolean removable) {
this.removable = removable;
}

public void move() {

if( !canMove)
return;

x += dx;
y += dy;
r += dr;

}

public boolean isAlive() {
return Double.compare(health, 0) > 0;
}

public ImageView getView() {
return imageView;
}

public void updateUI() {

imageView.relocate(x, y);
imageView.setRotate(r);

}

public double getWidth() {
return w;
}

public double getHeight() {
return h;
}

public double getCenterX() {
return x + w * 0.5;
}

public double getCenterY() {
return y + h * 0.5;
}

// TODO: per-pixel-collision
public boolean collidesWith( SpriteBase otherSprite) {

return ( otherSprite.x + otherSprite.w >= x && otherSprite.y + otherSprite.h >= y && otherSprite.x <= x + w && otherSprite.y <= y + h);

}

/**
* Reduce health by the amount of damage that the given sprite can inflict
* @param sprite
*/
public void getDamagedBy( SpriteBase sprite) {
health -= sprite.getDamage();
}

/**
* Set health to 0
*/
public void kill() {
setHealth( 0);
}

/**
* Set flag that the sprite can be removed from the UI.
*/
public void remove() {
setRemovable(true);
}

/**
* Set flag that the sprite can't move anymore.
*/
public void stopMovement() {
this.canMove = false;
}

public abstract void checkRemovability();

}```
And you may want the sprites for testing. Here they are, but you can use whatever you prefer. Just put them into the ./game/towerdefense/images folder of your project.

tower.png

ship.png

background.png

## Friday, May 15, 2015

### Simple Game Engine: Pong in JavaFX

Someone asked on StackOverflow about how to make the keyboard controls work in his game of Pong. That reminded me that I've never created that classic game. So I thought I'd give it a shot with our simple game engine.

Pong is a simple game in which two players try to keep a ball in the game by moving their paddles. In this blog we just create the base in which:

• a ball moves around the screen
• the ball bounces off the paddles
• a player can control a paddle
• another paddle is controlled by AI

The game loop consists of

• processing player input
• move the paddles and the balls
• check ball for paddle collision
• update ui
• remove sprites, e. g. if ball is outside scene
• update score

So all in all we have
```AnimationTimer gameLoop = new AnimationTimer() {

@Override
public void handle(long now) {

// player input
players.forEach(sprite -> sprite.processInput());

spawnBalls();

// movement
players.forEach(sprite -> sprite.move());
balls.forEach(sprite -> sprite.move());

// check collisions
checkCollisions();

// update sprites in scene
players.forEach(sprite -> sprite.updateUI());
balls.forEach(sprite -> sprite.updateUI());

// check if sprite can be removed
balls.forEach(sprite -> sprite.checkRemovability());

// remove removables from list, layer, etc
removeSprites( balls);

// update score, health, etc
}

};
```
We keep the game in retro style, i. e. simple. There's really no need to use image files when we create the images ourselves. Here's a technique that may interest some of you. We simply create a node and then put a snapshot of it into an image:
``` private void loadGame() {

WritableImage wi;

// -------------------------

Rectangle rect = new Rectangle( w, h);

wi = new WritableImage( (int) w, (int) h);
rect.snapshot(null, wi);

playerImage = wi;
enemyImage = wi;

// create ball image
// -------------------------

Circle circle = new Circle( r);

wi = new WritableImage( (int) r * 2, (int) r * 2);
circle.snapshot(null, wi);

ballImage = wi;

}
```
This blog is only supposed to be a start for your own game, so we keep it simple. The ball is merely bouncing off in horizontal direction. In the end one should consider the ball's position on the paddle and the paddle movement speed and calculate some bouncing angle.
```public class Ball extends SpriteBase {

...
public void bounceOff( SpriteBase sprite) {

// TODO: consider angle
dx = -dx;
}

@Override
public void checkRemovability() {

if( Double.compare( getX(), 0) < 0 || Double.compare( getX(), Settings.SCENE_WIDTH) > 0 || Double.compare( getY(), 0) < 0 || Double.compare( getY(), Settings.SCENE_HEIGHT) > 0) {
setRemovable(true);
}

}
}
```
Similarly we keep the enemy simple by moving the paddle vertically. For the game one would have to consider the incoming ball's angle, determine the impact position and calculate the vertical movement of the paddle.
```public class Enemy extends Player {

private enum Direction {
UP,
DOWN;
}

Direction direction = Direction.UP;

...

@Override
public void processInput() {

if( direction == Direction.UP) {
dy = -speed;
} else {
dy = speed;
}

}

@Override
protected void checkBounds() {

super.checkBounds();

if( y == playerShipMinY) {
direction = Direction.DOWN;
} else if( y == playerShipMaxY) {
direction = Direction.UP;
}

}
}
```
The rest of the code is similar to what we've learned so far. We use a base class for the sprites, an input class, evaluate input in the player class, show and update score, etc.

Here's a screenshot of what the game looks like:

And here's the full source if you'd like to try it out for yourself and enhance it:

Game.java
```package game.pong;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Game extends Application {

Random rnd = new Random();

Pane playfieldLayer;
AnchorPane scoreLayer;

Image playerImage;
Image enemyImage;
Image ballImage;

List<Player> players = new ArrayList<>();
List<Ball> balls = new ArrayList<>();

Text playerScoreText = new Text();
Text enemyScoreText = new Text();

Map<Player,Text> scoreDisplay = new HashMap<>();

Scene scene;

@Override
public void start(Stage primaryStage) {

Group root = new Group();

// create layers
playfieldLayer = new Pane();
playfieldLayer.setPrefSize(Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);

scoreLayer = new AnchorPane();
scoreLayer.setPrefSize(Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);

scene = new Scene( root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);

primaryStage.setScene( scene);
primaryStage.show();

createScoreLayer();
createPlayers();

AnimationTimer gameLoop = new AnimationTimer() {

@Override
public void handle(long now) {

// player input
players.forEach(sprite -> sprite.processInput());

spawnBalls();

// movement
players.forEach(sprite -> sprite.move());
balls.forEach(sprite -> sprite.move());

// check collisions
checkCollisions();

// update sprites in scene
players.forEach(sprite -> sprite.updateUI());
balls.forEach(sprite -> sprite.updateUI());

// check if sprite can be removed
balls.forEach(sprite -> sprite.checkRemovability());

// remove removables from list, layer, etc
removeSprites( balls);

// update score, health, etc
}

};
gameLoop.start();

}

WritableImage wi;

// -------------------------

Rectangle rect = new Rectangle( w, h);

wi = new WritableImage( (int) w, (int) h);
rect.snapshot(null, wi);

playerImage = wi;
enemyImage = wi;

// create ball image
// -------------------------

Circle circle = new Circle( r);

wi = new WritableImage( (int) r * 2, (int) r * 2);
circle.snapshot(null, wi);

ballImage = wi;

}

private void createScoreLayer() {

playerScoreText.setFont( Font.font( null, FontWeight.BOLD, 32));
enemyScoreText.setFont( Font.font( null, FontWeight.BOLD, 32));

AnchorPane.setTopAnchor(playerScoreText, 0.0);
AnchorPane.setLeftAnchor(playerScoreText, 10.0);

AnchorPane.setTopAnchor(enemyScoreText, 0.0);
AnchorPane.setRightAnchor(enemyScoreText, 10.0);

}

private void createPlayers() {

// create player instances
Player player = createPlayer();
Player enemy = createEnemy();

// register player

// assign score display
scoreDisplay.put(player, playerScoreText);
scoreDisplay.put(enemy, enemyScoreText);

}

private Player createPlayer() {

// player input
Input input = new Input( scene);

// register input listeners
input.addListeners(); // TODO: remove listeners on game over

Image image = playerImage;

// offset x position, center vertically
double y = (Settings.SCENE_HEIGHT - image.getHeight()) * 0.5;

// create player
Player player = new Player(playfieldLayer, image, x, y, 0, 0, 0, 0, 1, 0, Settings.PADDLE_SPEED, input);

return player;

}

private Player createEnemy() {

Image image = enemyImage;

// offset x position, center vertically
double x = Settings.SCENE_WIDTH - Settings.PADDLE_OFFSET_X - image.getWidth();
double y = (Settings.SCENE_HEIGHT - image.getHeight()) * 0.5;

// create player
Enemy player = new Enemy(playfieldLayer, image, x, y, 0, 0, 0, 0, 1, 0, Settings.PADDLE_SPEED);

return player;

}

private void spawnBalls() {
if( balls.size() == 0) {
createBall();
}
}
private void createBall() {

Image image = ballImage;

// offset x position, center vertically
double x = (Settings.SCENE_WIDTH - image.getWidth()) / 2;
double y = (Settings.SCENE_HEIGHT - image.getHeight()) / 2;

// create ball
Ball ball = new Ball( playfieldLayer, image, x, y, 0, -Settings.BALL_SPEED, 0, 0, 1, 1);

// register ball

}

private void removeSprites(  List<? extends SpriteBase> spriteList) {
Iterator<? extends SpriteBase> iter = spriteList.iterator();
while( iter.hasNext()) {
SpriteBase sprite = iter.next();

if( sprite.isRemovable()) {

// remove from layer
sprite.removeFromLayer();

// remove from list
iter.remove();
}
}
}

private void checkCollisions() {

for( Player player: players) {
for( Ball ball: balls) {
if( player.collidesWith(ball)) {

// bounce ball
ball.bounceOff(player);

// TODO: proper score handling: score only to the player if the ball leaves screen afterwards

}
}
}
}

for( Player player: players) {
scoreDisplay.get( player).setText( "" + (int) player.getScore());
}

}

public static void main(String[] args) {
launch(args);
}

}
```
Ball.java
```package game.pong;

import javafx.scene.image.Image;
import javafx.scene.layout.Pane;

public class Ball extends SpriteBase {

public Ball(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {
super(layer, image, x, y, r, dx, dy, dr, health, damage);
}

public void bounceOff( SpriteBase sprite) {

// TODO: ensure the ball doesn't get stuck inside a player
dx = -dx;
}

@Override
public void checkRemovability() {

if( Double.compare( getX(), 0) < 0 || Double.compare( getX(), Settings.SCENE_WIDTH) > 0 || Double.compare( getY(), 0) < 0 || Double.compare( getY(), Settings.SCENE_HEIGHT) > 0) {
setRemovable(true);
}

}
}
```
Enemy.java
```package game.pong;

import javafx.scene.image.Image;
import javafx.scene.layout.Pane;

public class Enemy extends Player {

private enum Direction {
UP,
DOWN;
}

Direction direction = Direction.UP;

public Enemy(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage, double speed) {
super(layer, image, x, y, r, dx, dy, dr, health, damage, speed, null);
}

@Override
public void processInput() {

if( direction == Direction.UP) {
dy = -speed;
} else {
dy = speed;
}

}

@Override
protected void checkBounds() {

super.checkBounds();

if( y == playerShipMinY) {
direction = Direction.DOWN;
} else if( y == playerShipMaxY) {
direction = Direction.UP;
}

}
}
```
Player.java
```package game.pong;

import javafx.scene.image.Image;
import javafx.scene.layout.Pane;

public class Player extends SpriteBase {

double playerShipMinX;
double playerShipMaxX;
double playerShipMinY;
double playerShipMaxY;

Input input;

double speed;

public Player(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage, double speed, Input input) {

super(layer, image, x, y, r, dx, dy, dr, health, damage);

this.speed = speed;
this.input = input;

init();
}

private void init() {

// calculate movement bounds of the player ship
playerShipMinX = x; // limit to vertical movement
playerShipMaxX = x; // limit to vertical movement
playerShipMinY = 0;
playerShipMaxY = Settings.SCENE_HEIGHT -image.getHeight();

}

public void processInput() {

// ------------------------------------
// movement
// ------------------------------------

// vertical direction
if( input.isMoveUp()) {
dy = -speed;
} else if( input.isMoveDown()) {
dy = speed;
} else {
dy = 0d;
}

// horizontal direction
if( input.isMoveLeft()) {
dx = -speed;
} else if( input.isMoveRight()) {
dx = speed;
} else {
dx = 0d;
}

}

@Override
public void move() {

super.move();

// ensure the ship can't move outside of the screen
checkBounds();

}

protected void checkBounds() {

// vertical
if( Double.compare( y, playerShipMinY) < 0) {
y = playerShipMinY;
} else if( Double.compare(y, playerShipMaxY) > 0) {
y = playerShipMaxY;
}

// horizontal
if( Double.compare( x, playerShipMinX) < 0) {
x = playerShipMinX;
} else if( Double.compare(x, playerShipMaxX) > 0) {
x = playerShipMaxX;
}

}

@Override
public void checkRemovability() {
}

}
```
Settings.java
```package game.pong;

public class Settings {

public static double SCENE_WIDTH = 600;
public static double SCENE_HEIGHT = 400;

public static double PADDLE_WIDTH = 20;
public static double PADDLE_HEIGHT = 100;

public static double BALL_RADIUS = 10;

public static double PADDLE_OFFSET_X = 50;

public static double PADDLE_SPEED = 4.0;
public static double BALL_SPEED = 4.0;

}
```
Input.java
```package game.pong;

import java.util.BitSet;

import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

public class Input {

/**
* Bitset which registers if any {@link KeyCode} keeps being pressed or if it is released.
*/
private BitSet keyboardBitSet = new BitSet();

// -------------------------------------------------
// default key codes
// will vary when you let the user customize the key codes or when you add support for a 2nd player
// -------------------------------------------------

private KeyCode upKey = KeyCode.UP;
private KeyCode downKey = KeyCode.DOWN;
private KeyCode leftKey = KeyCode.LEFT;
private KeyCode rightKey = KeyCode.RIGHT;
private KeyCode primaryWeaponKey = KeyCode.SPACE;
private KeyCode secondaryWeaponKey = KeyCode.CONTROL;

Scene scene;

public Input( Scene scene) {
this.scene = scene;
}

}

public void removeListeners() {

scene.removeEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
scene.removeEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);

}

/**
* "Key Pressed" handler for all input events: register pressed key in the bitset
*/
private EventHandler<KeyEvent> keyPressedEventHandler = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {

// register key down
keyboardBitSet.set(event.getCode().ordinal(), true);

}
};

/**
* "Key Released" handler for all input events: unregister released key in the bitset
*/
private EventHandler<KeyEvent> keyReleasedEventHandler = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {

// register key up
keyboardBitSet.set(event.getCode().ordinal(), false);

}
};

// -------------------------------------------------
// Evaluate bitset of pressed keys and return the player input.
// If direction and its opposite direction are pressed simultaneously, then the direction isn't handled.
// -------------------------------------------------

public boolean isMoveUp() {
return keyboardBitSet.get( upKey.ordinal()) && !keyboardBitSet.get( downKey.ordinal());
}

public boolean isMoveDown() {
return keyboardBitSet.get( downKey.ordinal()) && !keyboardBitSet.get( upKey.ordinal());
}

public boolean isMoveLeft() {
return keyboardBitSet.get( leftKey.ordinal()) && !keyboardBitSet.get( rightKey.ordinal());
}

public boolean isMoveRight() {
return keyboardBitSet.get( rightKey.ordinal()) && !keyboardBitSet.get( leftKey.ordinal());
}

public boolean isFirePrimaryWeapon() {
return keyboardBitSet.get( primaryWeaponKey.ordinal());
}

public boolean isFireSecondaryWeapon() {
return keyboardBitSet.get( secondaryWeaponKey.ordinal());
}

}
```
SpriteBase.java
```package game.pong;

import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;

public abstract class SpriteBase {

Image image;
ImageView imageView;

Pane layer;

double x;
double y;
double r;

double dx;
double dy;
double dr;

double health;
double damage;

boolean removable = false;

double w;
double h;

boolean canMove = true;

double score = 0;

public SpriteBase(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {

this.layer = layer;
this.image = image;
this.x = x;
this.y = y;
this.r = r;
this.dx = dx;
this.dy = dy;
this.dr = dr;

this.health = health;
this.damage = damage;

this.imageView = new ImageView(image);
this.imageView.relocate(x, y);
this.imageView.setRotate(r);

this.w = image.getWidth(); // imageView.getBoundsInParent().getWidth();
this.h = image.getHeight(); // imageView.getBoundsInParent().getHeight();

}

}

public void removeFromLayer() {
this.layer.getChildren().remove(this.imageView);
}

public Pane getLayer() {
return layer;
}

public void setLayer(Pane layer) {
this.layer = layer;
}

public double getX() {
return x;
}

public void setX(double x) {
this.x = x;
}

public double getY() {
return y;
}

public void setY(double y) {
this.y = y;
}

public double getR() {
return r;
}

public void setR(double r) {
this.r = r;
}

public double getDx() {
return dx;
}

public void setDx(double dx) {
this.dx = dx;
}

public double getDy() {
return dy;
}

public void setDy(double dy) {
this.dy = dy;
}

public double getDr() {
return dr;
}

public void setDr(double dr) {
this.dr = dr;
}

public double getHealth() {
return health;
}

public double getDamage() {
return damage;
}

public void setDamage(double damage) {
this.damage = damage;
}

public void setHealth(double health) {
this.health = health;
}

public boolean isRemovable() {
return removable;
}

public void setRemovable(boolean removable) {
this.removable = removable;
}

public void move() {

if( !canMove)
return;

x += dx;
y += dy;
r += dr;

}

public boolean isAlive() {
return Double.compare(health, 0) > 0;
}

public ImageView getView() {
return imageView;
}

public void updateUI() {

imageView.relocate(x, y);
imageView.setRotate(r);

}

public double getWidth() {
return w;
}

public double getHeight() {
return h;
}

public double getCenterX() {
return x + w * 0.5;
}

public double getCenterY() {
return y + h * 0.5;
}

// TODO: per-pixel-collision
public boolean collidesWith( SpriteBase otherSprite) {

return ( otherSprite.x + otherSprite.w >= x && otherSprite.y + otherSprite.h >= y && otherSprite.x <= x + w && otherSprite.y <= y + h);

}

/**
* Reduce health by the amount of damage that the given sprite can inflict
* @param sprite
*/
public void getDamagedBy( SpriteBase sprite) {
health -= sprite.getDamage();
}

/**
* Set health to 0
*/
public void kill() {
setHealth( 0);
}

/**
* Set flag that the sprite can be removed from the UI.
*/
public void remove() {
setRemovable(true);
}

/**
* Set flag that the sprite can't move anymore.
*/
public void stopMovement() {
this.canMove = false;
}

public void addScore( double value) {
this.score += value;
}

public double getScore() {
return score;
}
public abstract void checkRemovability();

}
```

### Simple Game Engine: Top-Down Scrolling Game in JavaFX

Someone asked on StackOverflow How do I move a sprite across a screen? I thought I'd help and create a minimalistic engine from our Anansi engine. It demonstates the necessary areas that you need to cover when you create a game.

This blog post is a recap of what we've learned so far.

We need a main class in which we load the game, create the scene and which contains the game loop for

• input check
• move player sprites
• create and move AI sprites
• check sprite collision
• update sprites in the UI
• update score

```public class Game extends Application {

Random rnd = new Random();

Pane playfieldLayer;
Pane scoreLayer;

Image playerImage;
Image enemyImage;

List<Player> players = new ArrayList<>();
List<Enemy> enemies = new ArrayList<>();

Text collisionText = new Text();
boolean collision = false;

Scene scene;

@Override
public void start(Stage primaryStage) {

Group root = new Group();

// create layers
playfieldLayer = new Pane();
scoreLayer = new Pane();

scene = new Scene( root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);

primaryStage.setScene( scene);
primaryStage.show();

createScoreLayer();
createPlayers();

AnimationTimer gameLoop = new AnimationTimer() {

@Override
public void handle(long now) {

// player input
players.forEach(sprite -> sprite.processInput());

spawnEnemies( true);

// movement
players.forEach(sprite -> sprite.move());
enemies.forEach(sprite -> sprite.move());

// check collisions
checkCollisions();

// update sprites in scene
players.forEach(sprite -> sprite.updateUI());
enemies.forEach(sprite -> sprite.updateUI());

// check if sprite can be removed
enemies.forEach(sprite -> sprite.checkRemovability());

// remove removables from list, layer, etc
removeSprites( enemies);

// update score, health, etc
}

};
gameLoop.start();

}

playerImage = new Image( getClass().getResource("player.png").toExternalForm());
enemyImage = new Image( getClass().getResource("enemy.png").toExternalForm());
}

private void createScoreLayer() {

collisionText.setFont( Font.font( null, FontWeight.BOLD, 64));
collisionText.setStroke(Color.BLACK);
collisionText.setFill(Color.RED);

// TODO: quick-hack to ensure the text is centered; usually you don't have that; instead you have a health bar on top
collisionText.setText("Collision");
double x = (Settings.SCENE_WIDTH - collisionText.getBoundsInLocal().getWidth()) / 2;
double y = (Settings.SCENE_HEIGHT - collisionText.getBoundsInLocal().getHeight()) / 2;
collisionText.relocate(x, y);
collisionText.setText("");

collisionText.setBoundsType(TextBoundsType.VISUAL);

}
private void createPlayers() {

// player input
Input input = new Input( scene);

// register input listeners
input.addListeners(); // TODO: remove listeners on game over

Image image = playerImage;

// center horizontally, position at 70% vertically
double x = (Settings.SCENE_WIDTH - image.getWidth()) / 2.0;
double y = Settings.SCENE_HEIGHT * 0.7;

// create player
Player player = new Player(playfieldLayer, image, x, y, 0, 0, 0, 0, Settings.PLAYER_HEALTH, 0, Settings.PLAYER_SPEED, input);

// register player

}

private void spawnEnemies( boolean random) {

if( random && rnd.nextInt(Settings.ENEMY_SPAWN_RANDOMNESS) != 0) {
return;
}

// image
Image image = enemyImage;

// random speed
double speed = rnd.nextDouble() * 1.0 + 2.0;

// x position range: enemy is always fully inside the screen, no part of it is outside
// y position: right on top of the view, so that it becomes visible with the next game iteration
double x = rnd.nextDouble() * (Settings.SCENE_WIDTH - image.getWidth());
double y = -image.getHeight();

// create a sprite
Enemy enemy = new Enemy( playfieldLayer, image, x, y, 0, 0, speed, 0, 1,1);

// manage sprite

}

private void removeSprites(  List<? extends SpriteBase> spriteList) {
Iterator<? extends SpriteBase> iter = spriteList.iterator();
while( iter.hasNext()) {
SpriteBase sprite = iter.next();

if( sprite.isRemovable()) {

// remove from layer
sprite.removeFromLayer();

// remove from list
iter.remove();
}
}
}

private void checkCollisions() {

collision = false;

for( Player player: players) {
for( Enemy enemy: enemies) {
if( player.collidesWith(enemy)) {
collision = true;
}
}
}
}

if( collision) {
collisionText.setText("Collision");
} else {
collisionText.setText("");
}
}

public static void main(String[] args) {
launch(args);
}

}

```

Then we need a base class for sprites. The class unifies the common mechanisms that every movable object on the screen has. The sprites have attributes like

• layout (usually an image)
• position and rotation
• health
• damage they can cause

We provide methods for accessing and modifying these attributes. Specific changes are overridden in the Subclasses like Player and Enemy.

```public abstract class SpriteBase {

Image image;
ImageView imageView;

Pane layer;

double x;
double y;
double r;

double dx;
double dy;
double dr;

double health;
double damage;

boolean removable = false;

double w;
double h;

boolean canMove = true;

public SpriteBase(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {

this.layer = layer;
this.image = image;
this.x = x;
this.y = y;
this.r = r;
this.dx = dx;
this.dy = dy;
this.dr = dr;

this.health = health;
this.damage = damage;

this.imageView = new ImageView(image);
this.imageView.relocate(x, y);
this.imageView.setRotate(r);

this.w = image.getWidth(); // imageView.getBoundsInParent().getWidth();
this.h = image.getHeight(); // imageView.getBoundsInParent().getHeight();

}

}

public void removeFromLayer() {
this.layer.getChildren().remove(this.imageView);
}

public Pane getLayer() {
return layer;
}

public void setLayer(Pane layer) {
this.layer = layer;
}

public double getX() {
return x;
}

public void setX(double x) {
this.x = x;
}

public double getY() {
return y;
}

public void setY(double y) {
this.y = y;
}

public double getR() {
return r;
}

public void setR(double r) {
this.r = r;
}

public double getDx() {
return dx;
}

public void setDx(double dx) {
this.dx = dx;
}

public double getDy() {
return dy;
}

public void setDy(double dy) {
this.dy = dy;
}

public double getDr() {
return dr;
}

public void setDr(double dr) {
this.dr = dr;
}

public double getHealth() {
return health;
}

public double getDamage() {
return damage;
}

public void setDamage(double damage) {
this.damage = damage;
}

public void setHealth(double health) {
this.health = health;
}

public boolean isRemovable() {
return removable;
}

public void setRemovable(boolean removable) {
this.removable = removable;
}

public void move() {

if( !canMove)
return;

x += dx;
y += dy;
r += dr;

}

public boolean isAlive() {
return Double.compare(health, 0) > 0;
}

public ImageView getView() {
return imageView;
}

public void updateUI() {

imageView.relocate(x, y);
imageView.setRotate(r);

}

public double getWidth() {
return w;
}

public double getHeight() {
return h;
}

public double getCenterX() {
return x + w * 0.5;
}

public double getCenterY() {
return y + h * 0.5;
}

// TODO: per-pixel-collision
public boolean collidesWith( SpriteBase otherSprite) {

return ( otherSprite.x + otherSprite.w >= x && otherSprite.y + otherSprite.h >= y && otherSprite.x <= x + w && otherSprite.y <= y + h);

}

/**
* Reduce health by the amount of damage that the given sprite can inflict
* @param sprite
*/
public void getDamagedBy( SpriteBase sprite) {
health -= sprite.getDamage();
}

/**
* Set health to 0
*/
public void kill() {
setHealth( 0);
}

/**
* Set flag that the sprite can be removed from the UI.
*/
public void remove() {
setRemovable(true);
}

/**
* Set flag that the sprite can't move anymore.
*/
public void stopMovement() {
this.canMove = false;
}

public abstract void checkRemovability();

}

```

Every sprite has some kind of AI. The player sprite's AI is the user, we evaluate the user's input.
```public class Player extends SpriteBase {

double playerShipMinX;
double playerShipMaxX;
double playerShipMinY;
double playerShipMaxY;

Input input;

double speed;

public Player(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage, double speed, Input input) {

super(layer, image, x, y, r, dx, dy, dr, health, damage);

this.speed = speed;
this.input = input;

init();
}

private void init() {

// calculate movement bounds of the player ship
// allow half of the ship to be outside of the screen
playerShipMinX = 0 - image.getWidth() / 2.0;
playerShipMaxX = Settings.SCENE_WIDTH - image.getWidth() / 2.0;
playerShipMinY = 0 - image.getHeight() / 2.0;
playerShipMaxY = Settings.SCENE_HEIGHT -image.getHeight() / 2.0;

}

public void processInput() {

// ------------------------------------
// movement
// ------------------------------------

// vertical direction
if( input.isMoveUp()) {
dy = -speed;
} else if( input.isMoveDown()) {
dy = speed;
} else {
dy = 0d;
}

// horizontal direction
if( input.isMoveLeft()) {
dx = -speed;
} else if( input.isMoveRight()) {
dx = speed;
} else {
dx = 0d;
}

}

@Override
public void move() {

super.move();

// ensure the ship can't move outside of the screen
checkBounds();

}

private void checkBounds() {

// vertical
if( Double.compare( y, playerShipMinY) < 0) {
y = playerShipMinY;
} else if( Double.compare(y, playerShipMaxY) > 0) {
y = playerShipMaxY;
}

// horizontal
if( Double.compare( x, playerShipMinX) < 0) {
x = playerShipMinX;
} else if( Double.compare(x, playerShipMaxX) > 0) {
x = playerShipMaxX;
}

}

}

```

The enemy sprites in our simple engine just move from one direction to the other.
```public class Enemy extends SpriteBase {

public Enemy(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {
super(layer, image, x, y, r, dx, dy, dr, health, damage);
}

@Override
public void checkRemovability() {

if( Double.compare( getY(), Settings.SCENE_HEIGHT) > 0) {
setRemovable(true);
}

}
}

```

Then we need an input mechanism. We create a dedicated class for that so that we can easily create various instances in case we'd like to create a multiplayer game.
```public class Input {

/**
* Bitset which registers if any {@link KeyCode} keeps being pressed or if it is released.
*/
private BitSet keyboardBitSet = new BitSet();

// -------------------------------------------------
// default key codes
// will vary when you let the user customize the key codes or when you add support for a 2nd player
// -------------------------------------------------

private KeyCode upKey = KeyCode.UP;
private KeyCode downKey = KeyCode.DOWN;
private KeyCode leftKey = KeyCode.LEFT;
private KeyCode rightKey = KeyCode.RIGHT;
private KeyCode primaryWeaponKey = KeyCode.SPACE;
private KeyCode secondaryWeaponKey = KeyCode.CONTROL;

Scene scene;

public Input( Scene scene) {
this.scene = scene;
}

}

public void removeListeners() {

scene.removeEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
scene.removeEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);

}

/**
* "Key Pressed" handler for all input events: register pressed key in the bitset
*/
private EventHandler<KeyEvent> keyPressedEventHandler = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {

// register key down
keyboardBitSet.set(event.getCode().ordinal(), true);

}
};

/**
* "Key Released" handler for all input events: unregister released key in the bitset
*/
private EventHandler<KeyEvent> keyReleasedEventHandler = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {

// register key up
keyboardBitSet.set(event.getCode().ordinal(), false);

}
};

// -------------------------------------------------
// Evaluate bitset of pressed keys and return the player input.
// If direction and its opposite direction are pressed simultaneously, then the direction isn't handled.
// -------------------------------------------------

public boolean isMoveUp() {
return keyboardBitSet.get( upKey.ordinal()) && !keyboardBitSet.get( downKey.ordinal());
}

public boolean isMoveDown() {
return keyboardBitSet.get( downKey.ordinal()) && !keyboardBitSet.get( upKey.ordinal());
}

public boolean isMoveLeft() {
return keyboardBitSet.get( leftKey.ordinal()) && !keyboardBitSet.get( rightKey.ordinal());
}

public boolean isMoveRight() {
return keyboardBitSet.get( rightKey.ordinal()) && !keyboardBitSet.get( leftKey.ordinal());
}

public boolean isFirePrimaryWeapon() {
return keyboardBitSet.get( primaryWeaponKey.ordinal());
}

public boolean isFireSecondaryWeapon() {
return keyboardBitSet.get( secondaryWeaponKey.ordinal());
}

}

```

And then we need some special settings in which we store the attributes of our game.
```public class Settings {

public static double SCENE_WIDTH = 400;
public static double SCENE_HEIGHT = 800;

public static double PLAYER_SPEED = 4.0;
public static double PLAYER_HEALTH = 100.0;

public static int ENEMY_SPAWN_RANDOMNESS = 100;

}
```

You can use any image for the sprites. I took mine from Wikipedia:

player.png

enemy.png

We can now put all the classes and images together into a single Java package and start the main class Game.java.

The gameplay is simple: The Zombie sprites are scrolling down from top to bottom, you have to evade them by moving the Smiley sprite via the cursor keys. A "Collision" text appears when the player sprite collides with the enemy sprites. The collision detection is a simple intersection check of the rectangular area of the sprites. For the final game one could consider a per-pixel collision detection.

Here's a screenshot of how it looks like:

Have fun! I hope this helps some of you in starting to create your own game.