Game of Asteroids using JavaFX

We are going to use JavaFX to create a game, where we steer the space ship through asteroids while shooting them down.

We can start by creating an abstract class HeavenlyBody that could be extended by concrete classes SpaceShip and Asteroid. The abstract class has instance variable shapeHeavenlyBody of type Polygon.

private Polygon shapeHeavenlyBody;

The constructor’s parameters besides creating polygon include initial positioning on the screen:

public HeavenlyBody(Polygon polygon, int x, int y) {
this.shapeHeavenlyBody = polygon;
this.shapeHeavenlyBody.setTranslateX(x);
this.shapeHeavenlyBody.setTranslateY(y);
}

HeavenlyBody class also will have a getter for the shape:

public Polygon getShapeHeavenlyBody() {
return shapeHeavenlyBody;
}

To implement the rotation, we will have:

public void turnLeft() {
this.shapeHeavenlyBody.setRotate(this.shapeHeavenlyBody.getRotate()
- 5);
}

public void turnRight() {
this.shapeHeavenlyBody.setRotate(this.shapeHeavenlyBody.getRotate()
+ 5);
}

Methods: setRotate(), getRotate(), setTranslateX(), and setTranslateY(), the Polygon class inherits from parent class Node:

We are going to add method responsible for the movement, in the direction of where spaceship is pointing, we are going to calculate that direction using sine and cosine functions of Math class:

public void move() {
//finding out the direction
double changeX = Math.cos(Math.toRadians(this.shapeHeavenlyBody.getRotate()));
double changeY = Math.sin(Math.toRadians(this.shapeHeavenlyBody.getRotate()));

shapeHeavenlyBody.setTranslateX(shapeHeavenlyBody.getTranslateX() + changeX);
shapeHeavenlyBody.setTranslateY(shapeHeavenlyBody.getTranslateY() + changeY);

}

At this point our HeavenlyBody class looks like this:

import javafx.scene.shape.Polygon;

public abstract class HeavenlyBody {

private Polygon shapeHeavenlyBody;

public HeavenlyBody(Polygon polygon, int x, int y) {
this.shapeHeavenlyBody = polygon;
this.shapeHeavenlyBody.setTranslateX(x);
this.shapeHeavenlyBody.setTranslateY(y);
}

public Polygon getShapeHeavenlyBody() {
return shapeHeavenlyBody;
}

public void turnLeft() {
this.shapeHeavenlyBody.setRotate(this.shapeHeavenlyBody.getRotate() - 5);
}

public void turnRight() {
this.shapeHeavenlyBody.setRotate(this.shapeHeavenlyBody.getRotate() + 5);
}

public void move() {
//finding out the direction
double changeX = Math.cos(Math.toRadians(this.shapeHeavenlyBody.getRotate()));
double changeY = Math.sin(Math.toRadians(this.shapeHeavenlyBody.getRotate()));

shapeHeavenlyBody.setTranslateX(shapeHeavenlyBody.getTranslateX() + changeX);
shapeHeavenlyBody.setTranslateY(shapeHeavenlyBody.getTranslateY() + changeY);

}
}

Our SpaceShip class extends HeavinlyBody:

import javafx.scene.shape.Polygon;

public class SpaceShip extends HeavenlyBody {

SpaceShip() {
super(new Polygon(-5,-5,10,0,-5,5), 150,100);
}
}

This is what it looks like for now:

Lets add asteroid to our application:

import javafx.scene.shape.Polygon;

public class Asteroid extends HeavenlyBody {

Asteroid() {
super(new Polygon(20, -20, 20, 20, -20, 20, -20, -20),50,50);
}
}

Lets also add method that stops our application when spaceship and asteroid collide. We can use method intersect() from parent class Shape:

public Boolean collide(Polygon other) {
Shape collisionArea = Shape.intersect(this.shapeHeavenlyBody, other);
return collisionArea.getBoundsInLocal().getWidth() != -1;
}

Now our App looks like this:

Let's have at least five asteroids of various sizes that move in slightly different directions. We are going to have class AsteroidCreator:

import javafx.scene.shape.Polygon;
import java.util.Random;

public class AsteroidCreator {
public Polygon createAsteroids() {
Random rnd = new Random();
int size = rnd.nextInt(10) + 5 ;
return new Polygon(size,-size,-size,-size,-size,size, size, size);
}
}

We are also going to set initial rotation of asteroids in our constructor. Asteroid class would look like this:

public class Asteroid extends HeavenlyBody {

Asteroid() {
super(new AsteroidCreator().createAsteroids(), 0, 0);
this.getAsteroidAngle();
}

public void getAsteroidAngle() {

Random rnd = new Random();
super.getShapeHeavenlyBody().setRotate(rnd.nextInt(90));

}
}

Now our App looks like this:

Lets make sure that our spaceship does not leave the game field. For that we will override the move() method in Spaceship class:

public void move() {

double changeX = Math.cos(Math.toRadians(this.getShapeHeavenlyBody().getRotate()));
double changeY = Math.sin(Math.toRadians(this.getShapeHeavenlyBody().getRotate()));

this.getShapeHeavenlyBody().setTranslateX(getShapeHeavenlyBody().getTranslateX() + changeX);
this.getShapeHeavenlyBody().setTranslateY(this.getShapeHeavenlyBody().getTranslateY() + changeY);

if (this.getShapeHeavenlyBody().getTranslateX() < 0) {
this.getShapeHeavenlyBody().setTranslateX(1);
}

if (this.getShapeHeavenlyBody().getTranslateX() > Main.WIDTH) {
this.getShapeHeavenlyBody().setTranslateX(299);
}

if (this.getShapeHeavenlyBody().getTranslateY() < 0) {
this.getShapeHeavenlyBody().setTranslateY(1);
}

if (this.getShapeHeavenlyBody().getTranslateY() > Main.HEIGHT) {
this.getShapeHeavenlyBody().setTranslateY(199);
}
}

Once our asteroids leave the boundaries of our playing field, they would reappear on the opposite side, for that we override method in Asteroid class:

public void move() {
double changeX = Math.cos(Math.toRadians(this.getShapeHeavenlyBody().getRotate()));
double changeY = Math.sin(Math.toRadians(this.getShapeHeavenlyBody().getRotate()));

this.getShapeHeavenlyBody().setTranslateX(getShapeHeavenlyBody().getTranslateX() + changeX * 0.5);
this.getShapeHeavenlyBody().setTranslateY(this.getShapeHeavenlyBody().getTranslateY() + changeY * 0.5);

if (this.getShapeHeavenlyBody().getTranslateX() < 0) {
this.getShapeHeavenlyBody().setTranslateX(this.getShapeHeavenlyBody().getTranslateX() - Main.WIDTH);
}

if (this.getShapeHeavenlyBody().getTranslateX() > Main.WIDTH) {
this.getShapeHeavenlyBody().setTranslateX(this.getShapeHeavenlyBody().getTranslateX() % Main.WIDTH);
}

if (this.getShapeHeavenlyBody().getTranslateY() < 0) {
this.getShapeHeavenlyBody().setTranslateY(this.getShapeHeavenlyBody().getTranslateY() - Main.HEIGHT);
}

if (this.getShapeHeavenlyBody().getTranslateY() > Main.HEIGHT) {
this.getShapeHeavenlyBody().setTranslateY(this.getShapeHeavenlyBody().getTranslateY() % Main.HEIGHT);
}
}

This is what we get:

For now we are just running away from asteroids. Lets add capability to shoot them down. We are going to create class Projectiles that extends HeavinlyBody:

import javafx.scene.shape.Polygon;

public class Projectile extends HeavenlyBody {

public Projectile(int x, int y) {
super(new Polygon(2, -2, 2, 2, -2, 2, -2, -2), x, y);
}

public void move() {
double changeX = Math.cos(Math.toRadians(this.getShapeHeavenlyBody().getRotate()));
double changeY = Math.sin(Math.toRadians(this.getShapeHeavenlyBody().getRotate()));

this.getShapeHeavenlyBody().setTranslateX(getShapeHeavenlyBody().getTranslateX() + changeX * 3);
this.getShapeHeavenlyBody().setTranslateY(this.getShapeHeavenlyBody().getTranslateY() + changeY * 3);
}
}

We also are going to add the ability to keep the count of destroyed asteroids. For that we are going to use AtomicInteger class:

Now lets look at Main class that is responsible for javaFX application:

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

public class Main extends Application {

public static int WIDTH = 300;
public static int HEIGHT = 200;

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

@Override
public void start(Stage window) {
Pane gameWindow = new Pane();
gameWindow.setPrefSize(WIDTH,HEIGHT);

Text text = new Text(10, 20, "Destroyed asteroids: 0");
gameWindow.getChildren().add(text);

Map<KeyCode, Boolean> pressedKeys = new HashMap<>();
AtomicInteger points = new AtomicInteger();
SpaceShip ship = new SpaceShip();
ArrayList<Asteroid> asteroids = new ArrayList<>();
List<Projectile> projectiles = new ArrayList<>();
List<Asteroid> collidedAsteroids = new ArrayList<>();
List<Projectile> collidedProjectiles = new ArrayList<>();
generateAsteroids(gameWindow, asteroids);

gameWindow.getChildren().add(ship.getShapeHeavenlyBody());

Scene scene = new Scene(gameWindow);

scene.setOnKeyPressed(event -> {
pressedKeys.put(event.getCode(), Boolean.TRUE);
});

scene.setOnKeyReleased(event -> {
pressedKeys.put(event.getCode(), Boolean.FALSE);
});

new AnimationTimer() {

@Override
public void handle(long now) {

if(pressedKeys.getOrDefault(KeyCode.LEFT, false)) {
ship.turnLeft();
}

if(pressedKeys.getOrDefault(KeyCode.RIGHT, false)) {
ship.turnRight();
}

if(pressedKeys.getOrDefault(KeyCode.UP, false)) {
ship.move();
}

if (pressedKeys.getOrDefault(KeyCode.SPACE, false)) {
// we shoot
Projectile projectile = new Projectile((int) ship.getShapeHeavenlyBody().getTranslateX(), (int) ship.getShapeHeavenlyBody().getTranslateY());
projectile.getShapeHeavenlyBody().setRotate(ship.getShapeHeavenlyBody().getRotate());
projectiles.add(projectile);

gameWindow.getChildren().add(projectile.getShapeHeavenlyBody());
}

asteroids.stream().forEach(asteroid -> {asteroid.move();
if (asteroid.collide(ship.getShapeHeavenlyBody())) {
stop();
}
});
projectiles.forEach(projectile -> {projectile.move();

for (Asteroid asteroid: asteroids) {
if (asteroid.collide(projectile.getShapeHeavenlyBody())) {
collidedAsteroids.add(asteroid);
collidedProjectiles.add(projectile);
text.setText("Destroyed asteroids: " + points.addAndGet(1));
}
}
});

collidedProjectiles.forEach(projectile -> gameWindow.getChildren().remove(projectile.getShapeHeavenlyBody()));
collidedAsteroids.forEach(asteroid -> gameWindow.getChildren().remove(asteroid.getShapeHeavenlyBody()));
asteroids.removeAll(collidedAsteroids);
projectiles.removeAll(collidedProjectiles);
if (asteroids.size() == 0) {
generateAsteroids(gameWindow, asteroids);
}
}
}.start();
window.setScene(scene);
window.setTitle("ASTEROIDS");
window.show();
}

public void generateAsteroids(Pane gameWindow, ArrayList<Asteroid> asteroids){
Random rnd = new Random();
int numberAsteroids = rnd.nextInt(8) + 3;
for (int i = 0; i < numberAsteroids; i++) {
Asteroid asteroid = new Asteroid();
asteroids.add(asteroid);
}
asteroids.stream().forEach(asteroid -> gameWindow.getChildren().add(asteroid.getShapeHeavenlyBody()));
}

}

After all the necessary imports we have our Main class extending Application class that we need to run javaFX application. In the following two lines we set height and width of our playing field:

public static int WIDTH = 300;
public static int HEIGHT = 200;

Method main launches our javaFX application. Extending class Application requires us to override start() method. We will use class Pane to create the main container:

Pane gameWindow = new Pane();
gameWindow.setPrefSize(WIDTH,HEIGHT);

We are going to use Text class to add the count of deleted asteroids:

Text text = new Text(10, 20, "Destroyed asteroids: 0");
gameWindow.getChildren().add(text);

We are going to create new objects: HashMap pressedKeys to keep track of pressed keys, points, ship, ArrayList that contains asteroids, ArrayList that contains projectiles, ArrayLists for collided asteroids and collided projectiles:

Map<KeyCode, Boolean> pressedKeys = new HashMap<>();
AtomicInteger points = new AtomicInteger();
SpaceShip ship = new SpaceShip();
ArrayList<Asteroid> asteroids = new ArrayList<>();
List<Projectile> projectiles = new ArrayList<>();
List<Asteroid> collidedAsteroids = new ArrayList<>();
List<Projectile> collidedProjectiles = new ArrayList<>();

Then we generate asteroids:

generateAsteroids(gameWindow, asteroids);

using:

public void generateAsteroids(Pane gameWindow, ArrayList<Asteroid> asteroids){
Random rnd = new Random();
int numberAsteroids = rnd.nextInt(8) + 3;
for (int i = 0; i < numberAsteroids; i++) {
Asteroid asteroid = new Asteroid();
asteroids.add(asteroid);
}
asteroids.stream().forEach(asteroid -> gameWindow.getChildren().add(asteroid.getShapeHeavenlyBody()));
}

Adding ship:

gameWindow.getChildren().add(ship.getShapeHeavenlyBody());

We are going to pass our pane (gameWindow) as argument to create Scene object and design event listeners that keep track of pressed and released buttons:

Scene scene = new Scene(gameWindow);

scene.setOnKeyPressed(event -> {
pressedKeys.put(event.getCode(), Boolean.TRUE);
});

scene.setOnKeyReleased(event -> {
pressedKeys.put(event.getCode(), Boolean.FALSE);
});

After that we are going to create object of AnimationTimer that requires us to override handle() method.

In this we are turning our spaceship left, right, and moving it in a direction that it is pointing.

if(pressedKeys.getOrDefault(KeyCode.LEFT, false)) {
ship.turnLeft();
}

if(pressedKeys.getOrDefault(KeyCode.RIGHT, false)) {
ship.turnRight();
}

if(pressedKeys.getOrDefault(KeyCode.UP, false)) {
ship.move();
}

Here we are creating projectiles, getting our asteroids to move, checking if our projectiles collide with asteroids, removing asteroids, keeping count of destroyed asteroids, generating new asteroids. Finally, we start our AnimationTimer with start() method.

if (pressedKeys.getOrDefault(KeyCode.SPACE, false)) {
// we shoot
Projectile projectile = new Projectile((int) ship.getShapeHeavenlyBody().getTranslateX(), (int) ship.getShapeHeavenlyBody().getTranslateY());
projectile.getShapeHeavenlyBody().setRotate(ship.getShapeHeavenlyBody().getRotate());
projectiles.add(projectile);

gameWindow.getChildren().add(projectile.getShapeHeavenlyBody());
}

asteroids.stream().forEach(asteroid -> {asteroid.move();
if (asteroid.collide(ship.getShapeHeavenlyBody())) {
stop();
}
});
projectiles.forEach(projectile -> {projectile.move();

for (Asteroid asteroid: asteroids) {
if (asteroid.collide(projectile.getShapeHeavenlyBody())) {
collidedAsteroids.add(asteroid);
collidedProjectiles.add(projectile);
text.setText("Destroyed asteroids: " + points.addAndGet(1));
}
}
});

collidedProjectiles.forEach(projectile -> gameWindow.getChildren().remove(projectile.getShapeHeavenlyBody()));
collidedAsteroids.forEach(asteroid -> gameWindow.getChildren().remove(asteroid.getShapeHeavenlyBody()));
asteroids.removeAll(collidedAsteroids);
projectiles.removeAll(collidedProjectiles);
if (asteroids.size() == 0) {
generateAsteroids(gameWindow, asteroids);
}
}
}.start();

We are setting our scene to window, giving it title, and displaying it:

window.setScene(scene);
window.setTitle("ASTEROIDS");
window.show();

Here how it looks like:

The complete code could be found here:

Happy coding!

Java, Android developer. Some Python.