Tag Archives: sphere

JavaFX 2 GameTutorial Part 4

JavaFX 2 Game Tutorial Part 4

Figure 1 JavaFX 2 Game Tutorial Part 4

 

Introduction

This is part four of a six part series related to a JavaFX 2 Game Tutorial. If you’ve missed Part 1, Part 2, or Part 3, I encourage you to go through them before beginning this tutorial. To recap, in Part 3 I gave you a little history of the many classic arcade style games and the different input devices that were used. I then showed you how to create a simple game similar to the famous arcade ‘Asteroids’. The controls (movement of the ship) were, however, more similar to those of the PC game ‘Star Craft’. In Part 3, you should have a good understanding of how to receive input from your keyboard and mouse.

This tutorial is about tweaking Part 2’s game engine and updating the existing ‘Asteroids’ style game from Part 3 to handle collision detection. In this tutorial, I will briefly talk about sprites and how to handle collision detection. The spaceship will now have the ability to generate a force field to protect itself from enemies and asteroids. This is reminiscent of the classic arcade ‘Asteroids Deluxe’.  If you want to run the demo, scroll down and click on the WebStart button below. Please read the requirements before launching the game.

What is a Sprite?

According to Wikipedia, “a sprite is a two-dimensional image or animation that is integrated into a larger scene.” From the Java gaming world view, a sprite is an object containing image frames and additional data based on the context of an actor to be animated onto the scene area. In the days of Walt Disney, when cartoons were drawn with a pencil and paper, the artist  produced many drawings that became animations. This example points to the creation of the flip book. I’m sure you’ve created flip books as a kid. I know I did. I used to doodle and make cool animations with all the corners of my notebooks. In our Asteroid type game, I created a sprite object which contains all of the images (ImageView) of the ship pre-rotated just like a flip book. To animate the ship turning, I made the current frame visible and the rest of the frames not visible. Similar to a flip book, it will appear to be rotated about its center (pivot) point. A sprite can also contain other information, such as velocity or health points.

Collision Detection

When actors or sprites animate across the scene, the game engine will check each sprite against other sprites to determine if they have collided into one another. This process should be very efficient especially when you have numerous sprites moving about the screen. There are tradeoffs when it comes to being efficient. Because each cycle in the game loop will check for collision, being more accurate usually degrades your performance. Many games will use the bounding region of the image to determine if two sprites have collided into one another.  Some games use rectangles as bounding regions. Shown below in figure 2 are two sprites colliding:

Bounding box as a rectangular collision region.

Figure 2 Bounding box as a rectangular collision region.

I’m sure you’ll know by now that most actors (images) in games don’t appear rectangular when pixels surrounding the actor are transparent. However, the actor or image is indeed rectangular even if the pixels are transparent or not.


Actor Image

Figure 3 depicts an actor image

Those games which use rectangular bounding regions usually make the bounding box inscribed in the sprite’s image. Shown below in figure 4 two rectangular bounding regions (orange and green) are inscribed in the spaceship image.

Two rectangles used as collision bounding boxes.

Figure 4 Two rectangles used as collision bounding boxes.

I’m sure you will notice that the nose tip of the ship and wings are not covered by either bounding box. This means that when an asteroid overlaps the unbounded region of the sprite the collision will not occur. Some games use this strategy; you will notice that the sprites’ rectangular bounding regions are small and placed in key areas of the sprite image.  Greater precision will be found with better algorithms for polygons and other non-rectangular shapes. In this blog entry, I basically use circles as bounding regions and not rectangles. I could have made each sprite contain an array of collision shapes, but instead, I chose to have only one collision region for each sprite. Each collision region will be a circle shape on the scene graph. For the spaceship, I inscribed the circle based on the center point of the ship with the radius extended to the cock pit of the ship. Shown below in figure 5 the bounded circular collision area of the ship is depicted as a red circle.

The ship's collision region.

Figure 5 The ship’s collision region.

I chose a circle as the bounding region because of the relative ease to determine the collision of two objects based on the distance formula (Pythagorean theorem) which only requires each sprite’s bounding region’s center point and their radii.  After calculating the distance based on the two center points, you will compare the result to see if it is less than or equal to the sum of the two radii. If the result does indeed come out to be less than or equal to the sum of the two radii then a collision has occurred.  Figure 6 depicts  how the distance formula relates to the two center points of circular bounding regions.

Distance formula

Figure 6 distance formula between two center points.

The following code creates the main game loop from the GameWorld class:

  @Override
  public void handle(javafx.event.ActionEvent event) {

     // update actors
     updateSprites();

     // check for collision
     checkCollisions();

     // removed dead things
     cleanupSprites();

  }

The code below creates the checkCollision() method from the GameWorld class:

    protected void checkCollisions() {
        // check other sprite's collisions
        spriteManager.resetCollisionsToCheck();
        // check each sprite against other sprite objects.
        for (Sprite spriteA : spriteManager.getCollisionsToCheck()) {
            for (Sprite spriteB : spriteManager.getAllSprites()) {
                if (handleCollision(spriteA, spriteB)) {
                    // The break helps optimize the collisions
                    //  The break statement means one object only hits another
                    // object as opposed to one hitting many objects.
                    // To be more accurate comment out the break statement.
                    break;
                }
            }
        }
    }

The derived Game World (TheExpanse) class’ implementation of its handleCollision() method:

    /**
     * How to handle the collision of two sprite objects.
     *
     * @param spriteA Sprite from the first list.
     * @param spriteB Sprite from the second list.
     * @return boolean returns a true if the two sprites have collided otherwise false.
     */
    @Override
    protected boolean handleCollision(Sprite spriteA, Sprite spriteB) {
        if (spriteA != spriteB) {
            if (spriteA.collide(spriteB)) {

                if (spriteA != myShip) {
                    spriteA.handleDeath(this);
                }
                if (spriteB != myShip) {
                    spriteB.handleDeath(this);
                }
            }
        }

        return false;
    }

The Sprite Class’ default implementation of its collide() method using the distance formula:

    public boolean collide(Sprite other) {

        if (collisionBounds == null || other.collisionBounds == null) {
            return false;
        }

        // determine it's size
        Circle otherSphere = other.collisionBounds;
        Circle thisSphere = collisionBounds;
        Point2D otherCenter = otherSphere.localToScene(otherSphere.getCenterX(), otherSphere.getCenterY());
        Point2D thisCenter = thisSphere.localToScene(thisSphere.getCenterX(), thisSphere.getCenterY());
        double dx = otherCenter.getX() - thisCenter.getX();
        double dy = otherCenter.getY() - thisCenter.getY();
        double distance = Math.sqrt(dx * dx + dy * dy);
        double minDist = otherSphere.getRadius() + thisSphere.getRadius();

        return (distance < minDist);
    }

The Sprite Class’ default implementation of its handleDeath() method:

    public void handleDeath(GameWorld gameWorld) {
        gameWorld.getSpriteManager().addSpritesToBeRemoved(this);
    }

The Atom (an asteroid or missile) class will override the handleDeath() method:


    public void handleDeath(GameWorld gameWorld) {
        implode(gameWorld);
        super.handleDeath(gameWorld);
    }

JavaFX 2 Sprite and Collision Demo

This simple demo game will be a mix between StarCraft and Asteroids. When using the mouse to navigate the ship, you will notice that the controls will resemble StarCraft’s Battle Cruiser. The objective is to fire your weapon at the spheres before they hit your spaceship or other spheres which implode upon impact. Because this is a simple tutorial or even a game in its early stages of development, the game doesn’t keep track of the score. I encourage you to go to GitHub to download the code and enhance the game. For the sake of brevity, I will not be showing all of the code changes, but I trust you will visit GitHub here: https://github.com/carldea/JFXGen for all the demos and source code.

Requirements:

  • Java 7 or later
  • JavaFX 2.1 or later
  • Windows XP or later (Should be available soon for Linux/MacOS)

A simple Asteroid type game called ‘The Expanse’.

Instructions:

  • Right mouse click (on Windows) to fly ship.
  • Left mouse click (left click on Windows mouse) to fire weapon.
  • Key press ’2′ to change to large missiles. (blue circular projectiles)
  • Other key press defaults to smaller missiles. (red circular projectiles)
  • Space bar key press will toggle a force field to protect the ship from enemies and asteroids.

Click on the Launch button below to start the demo:

Tutorial demo

Part 4 ‘The Expanse’ sprites/collision

Part 4 ‘Sprites/Collision’

Next up is Part 5 (Sound) where you will be using JavaFX to produce sound effects for your game.

References

Definition of a sprite: http://en.wikipedia.org/wiki/Sprite_%28computer_graphics%29

Walt Disney: http://en.wikipedia.org/wiki/Walt_Disney

How to make a flip book: http://www.bitrebels.com/design/how-to-create-a-flip-book/

JavaFX’s ImageView : http://docs.oracle.com/javafx/2/api/javafx/scene/image/ImageView.html

Collision detection: http://zetcode.com/tutorials/javagamestutorial/collision/

AABBs Collision detection in Java: http://www.youtube.com/watch?v=JIxV-LXqa1g

Pythagorean theorem: http://en.wikipedia.org/wiki/Pythagorean_theorem

Distance formula: http://en.wikipedia.org/wiki/Distance

Serious game of Asteroids Deluxe (Youtube): http://www.youtube.com/watch?v=6DG-GJENHgg

JavaFX 2 GameTutorial Part 3

The Expanse

figure 1 The Expanse – Input (Keyboard & Mouse) Demo

Introduction

This is part 3 of a six part series related to a JavaFX 2 Game Tutorial. If you’ve missed Part 1 and Part 2, I encourage you to go through them before beginning this tutorial. To recap Part 2 I discussed the inner workings of a gaming loop where we used an animation (JavaFX Timeline) to update sprites, check collisions, and clean up game world elements. I then felt compelled to create a simple game engine to enable the ease of developing 2D games. This tutorial is about using the game engine and demonstrating input using your mouse and keyboard. In this tutorial I will give you some background history, event handling fundamentals, a demo game, and finally the implementation. The demo will showcase a spaceship capable of shooting at floating spheres similar to the video game Asteroids. If you want to run the demo, scroll down and click on the WebStart button below. Please read the requirements before launching the game.

History

Back in the day (during the 80s) as a kid growing up there were arcade centers, bowling alleys, pizza parlors, and 7 Eleven stores where I spent huge amounts of time putting quarters on the glass display areas to be next in line to the guy who was currently playing an intense video game. As everyone was crowded around him watching him beat the all time high score we all cheered as we witnessed greatness. One of those incredibly awesome arcade games was ‘Asteroids‘ created by Atari Inc.(to play visit play.vg)

Speaking of high scores, not too many folks know, but Scott Safran (February 3, 1967 – March 27, 1989) had the highest record of all time playing Asteroids. He achieved this at his local 7-Eleven conveniece store by playing for approximately twenty hours non stop. Later in life (while still young), he passed away from a tragic accident on March 27, 1989. In honor of Scott, I created this tutorial. I hope people will remember him as one of the greatest video gamers of all time (I’m sure a good brother and son also).

Regarding the game, Asteroids, vector based hardware was used to render shapes as opposed to raster graphics (bitmap). On an added note, Space Invaders by Midway Inc. was created using raster graphics. It’s exciting to point out that there are discussions about JavaFX 2.x having the ability to use bitmaps called the JavaFX Canvas Node which can provide raster graphics to enable developers to take advantage of pixel level manipulations. I am still amazed at the construction of these arcade style cabinets which house the CRT, motherboard, and the controllers (input devices) such as buttons, joystick, track balls, and turning knobs.

Classic Arcade Games

Below are some classic arcade games with many types of input devices:

  • Buttons only: Asteroids, Space Invaders, Rip Off, Phoenix
  • Joystick only: Q*bert, PacMan
  • Turn knob only: Pong
  • Track ball only: Marble Madness
  • Steering column and buttonsStar Wars, Pole position, Spy Hunter
  • Bike handle bars: Stunt Cycle, Paper Boy
  • Buttons and Throttle bar: Lunar Lander
  • Periscope and Buttons: Sea Wolf
  • Buttons and Yoke: Tron, Battle Zone
  • Buttons, Turn knob, and Yoke:  Star Trek, Tempest
  • Buttons and track ball: Missile Command, Centipede
  • Buttons and Joystick: Defender, Gauntlet, Frogger, Joust, Berzerk, Mario Bros., Donkey Kong, Xevious, Galaga, Kung Fu, Contra, Street Fighter, Double Dragon, Ninja magic (or spirit), DigDug, Dragon’s Lair.

Input / (Mouse, Keyboard)

Leaving the past behind, we currently encounter new kinds of input devices such as touch screens, accelerometers, infrared receivers, cameras, etc. The most common input on the desktop today is the mouse and keyboard. Of course, touch screen is all the rage with mobile devices and tablets, however in this tutorial we will only be focusing on the ‘Mouse‘ and ‘Keyboard‘ as inputs to control your game. Based on the JavaFX Roadmapmulti-touch input is in the works (by the time you read this it’s already implemented).

When intercepting keyboard and mouse events, JavaFX 2.x has many types of events which provide an opportunity for the developer to implement event handlers that intercept the events triggered. The JavaFX 2.x API for the Node or Scene contains many methods having the prefix ‘on’ such as the onMousePressProperty() or onKeyPressProperty() method. Whenever you implement these methods you will simply implement the handle() method using Java’s generic type to specify the event object to be passed in for interrogation. So, when you instantiate an EventHandler<MouseEvent> class you will implement a handle() method that takes a MouseEvent as a parameter to be passed in.

The code snippets shown below add two event handlers to the JavaFX Scene. The first handler will respond to mouse events. In our simple game when a mouse press occurs, this handler will respond by firing the weapon or navigating the ship. The second handler shown below will respond to key event. When a key is pressed, this handler will process KeyEvent objects. In our game, the keystroke ‘2‘ will change your secondary weapon into a bigger blaster (slower). Any other keystroke will default back to the smaller blaster (faster).

Move ship and Fire weapon

        EventHandler fireOrMove = new EventHandler() {
            @Override
            public void handle(MouseEvent event) {
                if (event.getButton() == MouseButton.PRIMARY) {
                   // Fire weapon systems. On Windows left mouse button
                } else if (event.getButton() == MouseButton.SECONDARY) {
                   // Navigate ship thrust. On Windows right mouse button
                }
            }
        };
        primaryStage.getScene().setOnMousePressed(fireOrMove);

Changing the weapon

        EventHandler changeWeapons = new EventHandler() {
           @Override
           public void handle(KeyEvent event) {
               myShip.changeWeapon(event.getCode());
           }
        };
        primaryStage.getScene().setOnKeyPressed(changeWeapons);

JavaFX 2 Input Demo – ‘The Expanse’

The simple demo game will be a mix between StarCraft and Asteroids. When using the mouse to navigate the ship it will resemble StarCraft’s Battle Cruiser. If you remember from Part 2 of this series, I created spheres bouncing around. I reused the code from Part 2 ‘Atom Smasher’ to act as asteroids like in the famous arcade game. Except in this game you cannot get harmed at all. The objective is to fire your weapon at the spheres before they hit other spheres which implode upon impact. Because this is a simple tutorial, or even a game in its early stages of development, the game doesn’t keep track of the score. I encourage you to go to GitHub to download the code and enhance the game. Later, you will see a high level UML class diagram describing the classes that make up the game. For the sake of brevity, I will not be going through each class in great detail, but I trust you will visit GitHub here: https://github.com/carldea/JFXGen for all the demos and source code.

Requirements:

  • Java 7 or later
  • JavaFX 2.1 or later
  • Windows XP or later (Should be available soon for Linux/MacOS)
A simple Asteroid type game called ‘The Expanse’.

Instructions:

  • Right mouse click (on Windows) to fly ship.
  • Left mouse click (left click on Windows mouse) to fire weapon.
  • Key press ‘2’ to change to large missiles. (blue circular projectiles)
  • Other key press defaults to smaller missiles. (red circular projectiles)
Part 3 Demo
Part 3 ‘The Expanse’

Shown below is figure 2 of a high level class diagram depicting all the classes created for this demo. The GameWorld and Sprite classes are part of the game engine from the previous post. The rest of the classes are new which make up this demo for this tutorial.


InputPart3

The InputPart3 is the driver or main JavaFX application that runs the game. This creates a GameWorld object to be initialized and starts the game loop.
Shown below is the source code of the main JavaFX application InputPart3 . Click to expand.

package carlfx.demos.navigateship;

import carlfx.gameengine.GameWorld;
import javafx.application.Application;
import javafx.stage.Stage;

/**
 * The main driver of the game.
 * @author cdea
 */
public class InputPart3 extends Application {

    GameWorld gameWorld = new TheExpanse(59, "JavaFX 2 GameTutorial Part 3 - Input");
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        // setup title, scene, stats, controls, and actors.
        gameWorld.initialize(primaryStage);

        // kick off the game loop
        gameWorld.beginGameLoop();

        // display window
        primaryStage.show();
    }

}

TheExpanse

The TheExpanse class inherits from the GameWorld class.  This is practically identical to Part 2’s ‘AtomSmasher’ where the driver application will invoke the GameWorld instance’s initialize() method to set up all the game elements such as the input, spaceship, and those pesky floating spheres. The job of this class is to make sure asteroids or spheres bounce off walls and remove any missiles which reach the edge of the screen. It’s main responsibly is to manage the assets and create new levels. When there are no moving objects and the player moves the ship on the screen new spheres will be generated for the next level. The key take away from this class is the setupInput() method. The setupInput() method which I created is responsible for establishing your event handlers to be able to listen to key events and mouse event.

package carlfx.demos.navigateship;

import carlfx.gameengine.GameWorld;
import carlfx.gameengine.Sprite;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

import java.util.Random;

/**
 * This is a simple game world simulating a bunch of spheres looking
 * like atomic particles colliding with each other. When the game loop begins
 * the user will notice random spheres (atomic particles) floating and
 * colliding. The user will navigate his/her ship by right clicking the mouse to
 * trust forward and left click to fire weapon to atoms.
 *
 * @author cdea
 */
public class TheExpanse extends GameWorld {

    // mouse pt label
    Label mousePtLabel = new Label();

    // mouse press pt label
    Label mousePressPtLabel = new Label();

    TextField xCoordinate = new TextField("234");
    TextField yCoordinate = new TextField("200");
    Button moveShipButton = new Button("Rotate ship");

    Ship myShip = new Ship();

    public TheExpanse(int fps, String title) {
        super(fps, title);
    }

    /**
     * Initialize the game world by adding sprite objects.
     *
     * @param primaryStage The game window or primary stage.
     */
    @Override
    public void initialize(final Stage primaryStage) {
        // Sets the window title
        primaryStage.setTitle(getWindowTitle());
        //primaryStage.setFullScreen(true);

        // Create the scene
        setSceneNodes(new Group());
        setGameSurface(new Scene(getSceneNodes(), 800, 600));
        getGameSurface().setFill(Color.BLACK);
        primaryStage.setScene(getGameSurface());
        // Setup Game input
        setupInput(primaryStage);

        // Create many spheres
        generateManySpheres(2);

        // Display the number of spheres visible.
        // Create a button to add more spheres.
        // Create a button to freeze the game loop.
        //final Timeline gameLoop = getGameLoop();
        getSpriteManager().addSprites(myShip);
        getSceneNodes().getChildren().add(myShip.node);

        // mouse point
        VBox stats = new VBox();

        HBox row1 = new HBox();
        mousePtLabel.setTextFill(Color.WHITE);
        row1.getChildren().add(mousePtLabel);
        HBox row2 = new HBox();
        mousePressPtLabel.setTextFill(Color.WHITE);
        row2.getChildren().add(mousePressPtLabel);

        stats.getChildren().add(row1);
        stats.getChildren().add(row2);

        // mouse point
        HBox enterCoord1 = new HBox();
        enterCoord1.getChildren().add(xCoordinate);
        enterCoord1.getChildren().add(yCoordinate);
        enterCoord1.getChildren().add(moveShipButton);
        stats.getChildren().add(enterCoord1);
        moveShipButton.setOnAction(new EventHandler() {
            @Override
            public void handle(ActionEvent actionEvent) {
                double x = Double.parseDouble(xCoordinate.getText());
                double y = Double.parseDouble(yCoordinate.getText());
                myShip.plotCourse(x, y, false);
            }
        });

        // ===================================================
        // Debugging purposes
        // uncomment to test mouse press and rotation angles.
        //getSceneNodes().getChildren().add(stats);
    }

    /**
     * Sets up the mouse input.
     *
     * @param primaryStage The primary stage (app window).
     */
    private void setupInput(Stage primaryStage) {
        System.out.println("Ship's center is (" + myShip.getCenterX() + ", " + myShip.getCenterY() + ")");

        EventHandler fireOrMove = new EventHandler() {
            @Override
            public void handle(MouseEvent event) {
                mousePressPtLabel.setText("Mouse Press PT = (" + event.getX() + ", " + event.getY() + ")");
                if (event.getButton() == MouseButton.PRIMARY) {
                    // Aim
                    myShip.plotCourse(event.getX(), event.getY(), false);
                    // fire
                    Missile m1 = myShip.fire();
                    getSpriteManager().addSprites(m1);
                    getSceneNodes().getChildren().add(0, m1.node);
                } else if (event.getButton() == MouseButton.SECONDARY) {
                    // determine when all atoms are not on the game surface. Ship should be one sprite left.
                    if (getSpriteManager().getAllSprites().size()                         generateManySpheres(30);
                    }

                    // stop ship from moving forward
                    myShip.applyTheBrakes(event.getX(), event.getY());
                    // move forward and rotate ship
                    myShip.plotCourse(event.getX(), event.getY(), true);
                }

            }
        };

        // Initialize input
        primaryStage.getScene().setOnMousePressed(fireOrMove);
        //addEventHandler(MouseEvent.MOUSE_PRESSED, me);

        // set up stats
        EventHandler changeWeapons = new EventHandler() {
            @Override
            public void handle(KeyEvent event) {
                myShip.changeWeapon(event.getCode());
            }
        };
        primaryStage.getScene().setOnKeyPressed(changeWeapons);

        // set up stats
        EventHandler showMouseMove = new EventHandler() {
            @Override
            public void handle(MouseEvent event) {
                mousePtLabel.setText("Mouse PT = (" + event.getX() + ", " + event.getY() + ")");
            }
        };

        primaryStage.getScene().setOnMouseMoved(showMouseMove);
    }

    /**
     * Make some more space spheres (Atomic particles)
     *
     * @param numSpheres The number of random sized, color, and velocity atoms to generate.
     */
    private void generateManySpheres(int numSpheres) {
        Random rnd = new Random();
        Scene gameSurface = getGameSurface();
        for (int i = 0; i < numSpheres; i++) {             Color c = Color.rgb(rnd.nextInt(255), rnd.nextInt(255), rnd.nextInt(255));             Atom b = new Atom(rnd.nextInt(15) + 5, c, true);             Circle circle = b.getAsCircle();             // random 0 to 2 + (.0 to 1) * random (1 or -1)             b.vX = (rnd.nextInt(2) + rnd.nextDouble()) * (rnd.nextBoolean() ? 1 : -1);             b.vY = (rnd.nextInt(2) + rnd.nextDouble()) * (rnd.nextBoolean() ? 1 : -1);             // random x between 0 to width of scene             double newX = rnd.nextInt((int) gameSurface.getWidth());             // check for the right of the width newX is greater than width              // minus radius times 2(width of sprite)             if (newX > (gameSurface.getWidth() - (circle.getRadius() * 2))) {
                newX = gameSurface.getWidth() - (circle.getRadius() * 2);
            }

            // check for the bottom of screen the height newY is greater than height
            // minus radius times 2(height of sprite)
            double newY = rnd.nextInt((int) gameSurface.getHeight());
            if (newY > (gameSurface.getHeight() - (circle.getRadius() * 2))) {
                newY = gameSurface.getHeight() - (circle.getRadius() * 2);
            }

            circle.setTranslateX(newX);
            circle.setTranslateY(newY);
            circle.setVisible(true);
            circle.setId(b.toString());
            circle.setCache(true);
            circle.setCacheHint(CacheHint.SPEED);
            circle.setManaged(false);
            // add to actors in play (sprite objects)
            getSpriteManager().addSprites(b);

            // add sprite's
            getSceneNodes().getChildren().add(0, b.node);

        }
    }

    /**
     * Each sprite will update it's velocity and bounce off wall borders.
     *
     * @param sprite - An atomic particle (a sphere).
     */
    @Override
    protected void handleUpdate(Sprite sprite) {
        // advance object
        sprite.update();
        if (sprite instanceof Missile) {
            removeMissiles((Missile) sprite);
        } else {
            bounceOffWalls(sprite);
        }
    }

    /**
     * Change the direction of the moving object when it encounters the walls.
     *
     * @param sprite The sprite to update based on the wall boundaries.
     *               TODO The ship has got issues.
     */
    private void bounceOffWalls(Sprite sprite) {
        // bounce off the walls when outside of boundaries

        Node displayNode;
        if (sprite instanceof Ship) {
            displayNode = sprite.node;//((Ship)sprite).getCurrentShipImage();
        } else {
            displayNode = sprite.node;
        }
        // Get the group node's X and Y but use the ImageView to obtain the width.
        if (sprite.node.getTranslateX() > (getGameSurface().getWidth() - displayNode.getBoundsInParent().getWidth()) ||
                displayNode.getTranslateX() < 0) {             // bounce the opposite direction             sprite.vX = sprite.vX * -1;         }         // Get the group node's X and Y but use the ImageView to obtain the height.         if (sprite.node.getTranslateY() > getGameSurface().getHeight() - displayNode.getBoundsInParent().getHeight() ||
                sprite.node.getTranslateY() < 0) {             sprite.vY = sprite.vY * -1;         }     }     /**      * Remove missiles when they reach the wall boundaries.      *      * @param missile The missile to remove based on the wall boundaries.      */     private void removeMissiles(Missile missile) {         // bounce off the walls when outside of boundaries         if (missile.node.getTranslateX() > (getGameSurface().getWidth() -
                missile.node.getBoundsInParent().getWidth()) ||
                missile.node.getTranslateX() < 0) {             getSpriteManager().addSpritesToBeRemoved(missile);             getSceneNodes().getChildren().remove(missile.node);         }         if (missile.node.getTranslateY() > getGameSurface().getHeight() -
                missile.node.getBoundsInParent().getHeight() ||
                missile.node.getTranslateY() < 0) {

            getSpriteManager().addSpritesToBeRemoved(missile);
            getSceneNodes().getChildren().remove(missile.node);
        }
    }

    /**
     * How to handle the collision of two sprite objects. Stops the particle
     * by zeroing out the velocity if a collision occurred.
     *
     * @param spriteA Sprite from the first list.
     * @param spriteB Sprite from the second list.
     * @return boolean returns a true if the two sprites have collided otherwise false.
     */
    @Override
    protected boolean handleCollision(Sprite spriteA, Sprite spriteB) {
        if (spriteA != spriteB) {
            if (spriteA.collide(spriteB)) {
                if (spriteA instanceof Atom && spriteB instanceof Atom) {

                    ((Atom) spriteA).implode(this); // will remove from the Scene onFinish()
                    ((Atom) spriteB).implode(this);
                    getSpriteManager().addSpritesToBeRemoved(spriteA, spriteB);

                    return true;
                }
            }
        }

        return false;
    }

}

Ship

The Ship class represents our cool looking spaceship. The Ship class inherits from the Sprite class to help us contain velocity information (vector). This class will also contain a doubly linked list containing 32 ImageView (RotatedShipImage) instances which represent each direction to simulate the ship rotating about its center (centroid). At some point I want to change this by making a single SVGPath object to be rotated (I know there are trade offs). For this tutorial I implemented the ship by taking ImageViews objects to be rotated 32 direction evenly from 0 to 360 degrees. Shown below in Figure 3 are all 32 directions using 32 ImageView instances and a single Image object of the image of a spaceship to simulate the rotation about its center (pivot point).

Figure 3: 32 Directions to simulate a rotation

When animating the ship rotating I simply set all but the current image visible using the setVisible(true) method on the ImageView node.

Disclaimer: In gaming you will inevitably encounter math (Trigonometry). If you are interested and want to dig deeper please look at the source code of the TheExpanse class’ initialize() method. At the end of the method uncomment the statement: getSceneNodes().getChildren().add(stats);  . This will display controls which will allow you to use to debug and inspect mouse press coordinates. Also, you can see output in your console (stdout) relating to angles, vectors, etc.

The Ship’s member variables:

  • turnDirection – enum DIRECTION with Clockwise, CounterClockwise, and Neither
  • u – Vec object which contains a vector in relation to the center of the ship coordinates denoting the starting direction the ship begins to rotate
  • directionalShips – list of RotatedShipImage objects each having a previous and next reference to other RotatedShipImage objects. Zero degrees (uIndex=0) is when the spaceship facing east. When rotating a JavaFX nodes going in a counter clockwise direction is positive numbers in degrees
  • uIndex – index of the current RotatedShipImage in the directionalShips list that is to be displayed
  • vIndex – index of the RotatedShipImage in the directionalShips list that is to be displayed at the end of the rotation animation
  • stopArea – a JavaFX Circle with a radius for the ship to know when to stop the ship from moving
  • flipBook – A JavaFX Group containing all RotatedShipImage objects (32). The group is rendered on the Scene. Like a flip book in animation each RotatedShipImage will be determined to be displayed based on uIndex and vIndex
  • keyCode – a JavaFX KeyCode will help determine if a key press to help change your weapon (character ‘2’)

The Ship’s member functions:

  • update() – Updates the ships velocity and direction. Also will determine when to stop moving.
  • getCurrentShipImage() – Based on the uIndex it returns the ImageView that is the current ship direction image that is being displayed
  • getCenterX() – returns the screen’s X coordinate of the center of the ship
  • getCenterY() – returns the screen’s X coordinate of the center of the ship
  • plotCourse(double screenX, double screenY, boolean thrust) – After the user clicks their mouse on the screen this method will calculate the angle to rotate the ship and change the velocity to thrust toward the coordinates onto the destination point. When using the Vec object the screen coordinates will be converted to Cartesian coordinates for determining the angle between two vectors (U and V).
  • turnShip() – The plotCourse() method calls turnShip() method to perform the actual animation of the rotation of the ship
  • applyTheBrakes(double screenX, double screenY) – After the user has chosen (right mouse click) where the ship will navigate to applyTheBrakes() method simply sets up the stopArea (Circle) object to let the ship know when to stop
  • fire() – Returns a Missile (Sprite) object for the game engine to put into the scene. Each missile contains the same direction of the ship with a scaled up velocity (increase speed). Should be faster than the ship can fly.
  •  changeWeapon(KeyCode keyCode) – After the user (player) hit the key stroke of a ‘2’ the weapon will change to create a larger missile projectile but slightly slower. Any other key press will be the default weapon that creates small missile projectiles which are faster moving.
Shown below is figure 4 of a class diagram displaying the members of the class Ship.

Ship Class Diagram

Figure 4: Ship class diagram

Shown below is the source code of the Ship class. Click to expand.

package carlfx.demos.navigateship;

import carlfx.gameengine.Sprite;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.List;

/**
 * A space ship with 32 directions
 * When two atoms collide each will fade and become removed from the scene. The
 * method called implode() implements a fade transition effect.
 *
 * @author cdea
 */
public class Ship extends Sprite {

    /**
     * 360 degree turn
     */
    private final static int TWO_PI_DEGREES = 360;

    /**
     * Number of ship frames and directions the ship is pointing nose
     */
    private final static int NUM_DIRECTIONS = 32;

    /**
     * The angle of one direction (adjacent directions) (11.25 degrees)
     */
    private final static float UNIT_ANGLE_PER_FRAME = ((float) TWO_PI_DEGREES / NUM_DIRECTIONS);

    /**
     * Amount of time it takes the ship to move 180 degrees in milliseconds.
     */
    private final static int MILLIS_TURN_SHIP_180_DEGREES = 300;

    /**
     * When the ship turns on each direction one amount of time for one frame or turn of the ship. (18.75 milliseconds)
     */
    private final static float MILLIS_PER_FRAME = (float) MILLIS_TURN_SHIP_180_DEGREES / (NUM_DIRECTIONS / 2);

    /**
     * All possible turn directions Clockwise, Counter Clockwise, or Neither when the user clicks mouse around ship
     */
    private enum DIRECTION {
        CLOCKWISE, COUNTER_CLOCKWISE, NEITHER
    }

    /**
     * Velocity amount used vector when ship moves forward. scale vector of ship. See flipBook translateX and Y.
     */
    private final static float THRUST_AMOUNT = 3.3f;

    /***/
    private final static float MISSILE_THRUST_AMOUNT = 6.3F;

    /**
     * Angle in degrees to rotate ship.
     */

    /**
     * Current turning direction. default is NEITHER. Clockwise and Counter Clockwise.
     */
    private DIRECTION turnDirection = DIRECTION.NEITHER;

    /**
     * The current starting position of the vector or coordinate where the nose of the ship is pointing towards.
     */
    private Vec u; // current or start vector

    /**
     * All ImageViews of all the possible image frames for each direction the ship is pointing. ie: 32 directions.
     */
    private final List directionalShips = new ArrayList<>();

    /**
     * The Timeline instance to animate the ship rotating using images. This is an optical illusion similar to page
     * flipping as each frame is displayed the previous visible attribute is set to false. No rotation is happening.
     */
    private Timeline rotateShipTimeline;

    /**
     * The current index into the list of ImageViews representing each direction of the ship. Zero is the ship
     * pointing to the right or zero degrees.
     */
    private int uIndex = 0;

    /**
     * The end index into the list of ImageViews representing each direction of the ship. Zero is the ship
     * pointing to the right or zero degrees.
     */
    private int vIndex = 0;

    /**
     * The spot where the user has right clicked letting the engine check the ship's center is in this area.
     */
    private final Circle stopArea = new Circle();

    /**
     * A group contain all of the ship image view nodes.
     */
    private final Group flipBook = new Group();

    /**
     * A key code will be used for weapon selection.
     */
    private KeyCode keyCode;

    public Ship() {

        // Load one image.
        Image shipImage = new Image(getClass().getClassLoader().getResource("ship.png").toExternalForm(), true);
        stopArea.setRadius(40);
        RotatedShipImage prev = null;

        // create all the number of directions based on a unit angle. 360 divided by NUM_DIRECTIONS
        for (int i = 0; i < NUM_DIRECTIONS; i++) {
            RotatedShipImage imageView = new RotatedShipImage();
            imageView.setImage(shipImage);
            imageView.setRotate(-1 * i * UNIT_ANGLE_PER_FRAME);
            imageView.setCache(true);
            imageView.setCacheHint(CacheHint.SPEED);
            imageView.setManaged(false);
            imageView.prev = prev;
            imageView.setVisible(false);
            directionalShips.add(imageView);
            if (prev != null) {
                prev.next = imageView;
            }
            prev = imageView;
            flipBook.getChildren().add(imageView);
        }
        RotatedShipImage firstShip = directionalShips.get(0);
        firstShip.prev = prev;
        prev.next = firstShip;
        // set javafx node to an image
        firstShip.setVisible(true);
        node = flipBook;
        flipBook.setTranslateX(200);
        flipBook.setTranslateY(300);

    }

    /**
     * Change the velocity of the atom particle.
     */
    @Override
    public void update() {
        flipBook.setTranslateX(flipBook.getTranslateX() + vX);
        flipBook.setTranslateY(flipBook.getTranslateY() + vY);

        if (stopArea.contains(getCenterX(), getCenterY())) {
            vX = 0;
            vY = 0;
        }

    }

    private RotatedShipImage getCurrentShipImage() {
        return directionalShips.get(uIndex);
    }

    /**
     * The center X coordinate of the current visible image. See <code>getCurrentShipImage()</code> method.
     *
     * @return The scene or screen X coordinate.
     */
    public double getCenterX() {
        RotatedShipImage shipImage = getCurrentShipImage();
        return node.getTranslateX() + (shipImage.getBoundsInLocal().getWidth() / 2);
    }

    /**
     * The center Y coordinate of the current visible image. See <code>getCurrentShipImage()</code> method.
     *
     * @return The scene or screen Y coordinate.
     */
    public double getCenterY() {
        RotatedShipImage shipImage = getCurrentShipImage();
        return node.getTranslateY() + (shipImage.getBoundsInLocal().getHeight() / 2);
    }

    /**
     * Determines the angle between it's starting position and ending position (Similar to a clock's second hand).
     * When the user is shooting the ship nose will point in the direction of the mouse press using the primary button.
     * When the user is thrusting to a location on the screen the right click mouse will pass true to the thrust
     * parameter.
     *
     * @param screenX The mouse press' screen x coordinate.
     * @param screenY The mouse press' screen ycoordinate.
     * @param thrust  Thrust ship forward or not. True move forward otherwise false.
     */
    public void plotCourse(double screenX, double screenY, boolean thrust) {
        // get center of ship
        double sx = getCenterX();
        double sy = getCenterY();

        // get user's new turn position based on mouse click
        Vec v = new Vec(screenX, screenY, sx, sy);
        if (u == null) {
            u = new Vec(1, 0);
        }

        double atan2RadiansU = Math.atan2(u.y, u.x);
        double atan2DegreesU = Math.toDegrees(atan2RadiansU);

        double atan2RadiansV = Math.atan2(v.y, v.x);
        double atan2DegreesV = Math.toDegrees(atan2RadiansV);

        double angleBetweenUAndV = atan2DegreesV - atan2DegreesU;

        // if abs value is greater than 180 move counter clockwise
        //(or opposite of what is determined)
        double absAngleBetweenUAndV = Math.abs(angleBetweenUAndV);
        boolean goOtherWay = false;
        if (absAngleBetweenUAndV > 180) {
            if (angleBetweenUAndV < 0) {                 turnDirection = DIRECTION.COUNTER_CLOCKWISE;                 goOtherWay = true;             } else if (angleBetweenUAndV > 0) {
                turnDirection = DIRECTION.CLOCKWISE;
                goOtherWay = true;
            } else {
                turnDirection = Ship.DIRECTION.NEITHER;
            }
        } else {
            if (angleBetweenUAndV < 0) {                 turnDirection = Ship.DIRECTION.CLOCKWISE;             } else if (angleBetweenUAndV > 0) {
                turnDirection = Ship.DIRECTION.COUNTER_CLOCKWISE;
            } else {
                turnDirection = Ship.DIRECTION.NEITHER;
            }
        }

        double degreesToMove = absAngleBetweenUAndV;
        if (goOtherWay) {
            degreesToMove = TWO_PI_DEGREES - absAngleBetweenUAndV;
        }

        //int q = v.quadrant();

        uIndex = Math.round((float) (atan2DegreesU / UNIT_ANGLE_PER_FRAME));
        if (uIndex < 0) {
            uIndex = NUM_DIRECTIONS + uIndex;
        }
        vIndex = Math.round((float) (atan2DegreesV / UNIT_ANGLE_PER_FRAME));
        if (vIndex < 0) {             vIndex = NUM_DIRECTIONS + vIndex;         }         String debugMsg = turnDirection +                 " U [m(" + u.mx + ", " + u.my + ")  => c(" + u.x + ", " + u.y + ")] " +
                " V [m(" + v.mx + ", " + v.my + ")  => c(" + v.x + ", " + v.y + ")] " +
                " start angle: " + atan2DegreesU +
                " end angle:" + atan2DegreesV +
                " Angle between: " + degreesToMove +
                " Start index: " + uIndex +
                " End index: " + vIndex;

        System.out.println(debugMsg);

        if (thrust) {
            vX = Math.cos(atan2RadiansV) * THRUST_AMOUNT;
            vY = -Math.sin(atan2RadiansV) * THRUST_AMOUNT;
        }
        turnShip();

        u = v;
    }

    private void turnShip() {

        final Duration oneFrameAmt = Duration.millis(MILLIS_PER_FRAME);
        RotatedShipImage startImage = directionalShips.get(uIndex);
        RotatedShipImage endImage = directionalShips.get(vIndex);
        List frames = new ArrayList<>();

        RotatedShipImage currImage = startImage;

        int i = 1;
        while (true) {

            final Node displayNode = currImage;

            KeyFrame oneFrame = new KeyFrame(oneFrameAmt.multiply(i),
                    new EventHandler() {

                        @Override
                        public void handle(javafx.event.ActionEvent event) {
                            // make all ship images invisible
                            for (RotatedShipImage shipImg : directionalShips) {
                                shipImg.setVisible(false);
                            }
                            // make current ship image visible
                            displayNode.setVisible(true);

                            // update the current index
                            //uIndex = directionalShips.indexOf(displayNode);
                        }
                    }); // oneFrame

            frames.add(oneFrame);

            if (currImage == endImage) {
                break;
            }
            if (turnDirection == DIRECTION.CLOCKWISE) {
                currImage = currImage.prev;
            }
            if (turnDirection == DIRECTION.COUNTER_CLOCKWISE) {
                currImage = currImage.next;
            }
            i++;
        }

        if (rotateShipTimeline != null) {
            rotateShipTimeline.stop();
            rotateShipTimeline.getKeyFrames().clear();
            rotateShipTimeline.getKeyFrames().addAll(frames);
        } else {
            // sets the game world's game loop (Timeline)
            rotateShipTimeline = TimelineBuilder.create()
                    .keyFrames(frames)
                    .build();

        }

        rotateShipTimeline.playFromStart();

    }

    /**
     * Stops the ship from thrusting forward.
     *
     * @param screenX the screen's X coordinate to stop the ship.
     * @param screenY the screen's Y coordinate to stop the ship.
     */
    public void applyTheBrakes(double screenX, double screenY) {
        stopArea.setCenterX(screenX);
        stopArea.setCenterY(screenY);
    }

    public Missile fire() {
        Missile m1;

        float slowDownAmt = 0;
        if (KeyCode.DIGIT2 == keyCode) {
            m1 = new Missile(10, Color.BLUE);
            slowDownAmt = 2.3f;
        } else {
            m1 = new Missile(Color.RED);
        }
        // velocity vector of the missile
        m1.vX = Math.cos(Math.toRadians(uIndex * UNIT_ANGLE_PER_FRAME)) * (MISSILE_THRUST_AMOUNT - slowDownAmt);
        m1.vY = -Math.sin(Math.toRadians(uIndex * UNIT_ANGLE_PER_FRAME)) * (MISSILE_THRUST_AMOUNT - slowDownAmt);

        // make the missile launch in the direction of the current direction of the ship nose. based on the
        // current frame (uIndex) into the list of image view nodes.
        RotatedShipImage shipImage = directionalShips.get(uIndex);

        // start to appear in the center of the ship to come out the direction of the nose of the ship.
        double offsetX = (shipImage.getBoundsInLocal().getWidth() - m1.node.getBoundsInLocal().getWidth()) / 2;
        double offsetY = (shipImage.getBoundsInLocal().getHeight() - m1.node.getBoundsInLocal().getHeight()) / 2;

        // initial launch of the missile
        m1.node.setTranslateX(node.getTranslateX() + offsetX + m1.vX);
        m1.node.setTranslateY(node.getTranslateY() + offsetY + m1.vY);
        return m1;
    }

    public void changeWeapon(KeyCode keyCode) {
        this.keyCode = keyCode;
    }

}

Vec

The Vec class is a simple helper container class to assist in holding a mouse click’s screen coordinates and converting them to Cartesian coordinates based on the center of a sprite, image, or shape. This class is used to help determine the angle between two vectors [Math.atan2(y,x)]. By determining the angle the ship is able to perform the rotation animation of the sprite image.

Shown below is the source code of the Vec class. Click to expand.

package carlfx.demos.navigateship;

/**
 * This class represents a container class to hold a Vector in space and direction
 * the ship will move. Assuming the center of the ship is the origin the angles can
 * be determined by a unit circle via Cartesian coordinates.
 * When the user clicks on the screen the mouse coordinates or screen coordinates
 * will be stored into the mx and my instance variables.
 * The x and y data members are converted to cartesian coordinates before storing.
 *
 * I purposefully left out getters and setters. In gaming just keep things minimalistic.
 * @author cdea
 */
public class Vec {
    public double mx;
    public double my;

    public double x;
    public double y;

    /**
     * This is a default constructor which will take a Cartesian coordinate.
     * @param x  X coordinate of a point on a Cartesian system.
     * @param y  Y coordinate of a point on a Cartesian system.
     */
    public Vec(float x, float y) {
        this.x = x;
        this.y = y;
    }

    /**
     * Constructor will convert mouse click points into Cartesian coordinates based on the sprite's center point as
     * the origin.
     * @param mx  Mouse press' screen X coordinate.
     * @param my  Mouse press' screen Y coordinate.
     * @param centerX Screen X coordinate of the center of the ship sprite.
     * @param centerY Screen Y coordinate of the center of the ship sprite.
     */
    public Vec(double mx, double my, double centerX, double centerY) {
        this.x = convertX(mx, centerX);
        this.y = convertY(my, centerY);
        this.mx = mx;
        this.my = my;
    }

    /**
     * Returns a Cartesian coordinate system's quadrant from 1 to 4.
*
     *     first quadrant - 1 upper right
     *     second quadrant - 2 upper left
     *     third quadrant - 3 lower left
     *     fourth quadrant - 4 lower right
     *
     * @return int quadrant number 1 through 4
     */
    public int quadrant() {
        int q = 0;
        if (x > 0 && y > 0) {
            q =1;
        } else if (x < 0 && y > 0) {
            q = 2;
        } else if (x < 0 && y < 0) {             q = 3;         } else if (x > 0 && y < 0) {
            q = 4;
        }
        return q;
    }
    @Override
    public String toString(){
        return "(" + x + "," + y + ") quadrant=" + quadrant();
    }
    /**
     * Converts point's X screen coordinate into a Cartesian system.
     * @param mouseX Converts the mouse X coordinate into Cartesian system based on the ship center X (originX).
     * @param originX The ship center point's X coordinate.
     * @return  double value of a Cartesian system X coordinate based on the origin X.
     */
    static double convertX(double mouseX, double originX) {
        return mouseX - originX;
    }

    /**
     * Converts point's Y screen coordinate into a Cartesian system.
     * @param mouseY  Converts the mouse Y coordinate into Cartesian system based on the ship center Y (originY).
     * @param originY The ship center point's Y coordinate.
     * @return  double value of a Cartesian system Y coordinate based on the origin Y.
     */
    static double convertY(double mouseY, double originY) {
        return originY - mouseY;
    }

}

RotatedShipImage

A RotatedShipImage class inherits from a JavaFX’s ImageView class, but also contains references to a previous and a next RotatedShipImage instances making up a doubly linked list. Figure 3 depicts 32 images of the of the “ship.png” rendered in each RotatedShipImage which are all placed in a JavaFX Group node. When the ship appears to rotate, one image is displayed at a time.

Shown below is the source code of the RotatedShipImage class. Click to expand.

package carlfx.demos.navigateship;

import javafx.scene.image.ImageView;

/**
 * Represents a double link list to assist in the rotation of the ship.
 * This helps to move clockwise and counter clockwise.
 */
public class RotatedShipImage extends ImageView {
    public RotatedShipImage next;
    public RotatedShipImage prev;
}

Missile

The Missile class inherits from the Atom class. The Missile acts as a marker class to differentiate between spheres (asteroids) and missiles. When missiles are created, they will contain the same direction of the ship (where the ship’s nose is pointing) with a larger velocity.

Shown below is the source code of the Missile class. Click to expand.

package carlfx.demos.navigateship;

import javafx.scene.paint.Color;

/**
 * A missile projectile without the radial gradient.
 */
public class Missile extends Atom {
    public Missile(Color fill) {
        super(5, fill, false);
    }

    public Missile(int radius, Color fill) {
        super(radius, fill, true);
    }
}

Conclusion

Input is so vital to any game play that it is often hard to get right. Older game engines will poll during a game loop. When using JavaFX 2.x’s event handling, you  implement the type of event to be added to the scene graph or to an individual Node object. Hopefully in the future, we will see more ingenious input devices used in gaming (see Oracle’s Java Technology Evangelist Simon Ritter).  Keep your eyes open for Part 4 which deals with collision detection. So, stay tuned and feel free to comment.

References:

7-Eleven: http://www.7-eleven.com

Playing Asteroids: http://www.play.vg/games/4-Asteroids.html

Asteroids: http://en.wikipedia.org/wiki/Asteroids_(video_game)

Scott Safran: http://en.wikipedia.org/wiki/Scott_Safran

Arcade in Backyard: http://www.themysteryworld.com/2011/02/guy-builds-video-arcade-in-his-back.html

StarWars downsized: http://techland.time.com/2012/04/26/man-builds-16-scale-star-wars-arcade-game/

Trigonometry: http://en.wikipedia.org/wiki/Trigonometry

JavaFX Node API: http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html

JavaFX Scene API: http://docs.oracle.com/javafx/2/api/javafx/scene/Scene.html

JavaFX SVGPath API: http://docs.oracle.com/javafx/2/api/javafx/scene/shape/SVGPath.html

Multi-Touch and Gestures Support: http://www.oracle.com/technetwork/java/javafx/overview/roadmap-1446331.html

Pro JavaFX 2 Apress publishing – pg. 62 chapter 2 section on “Handling Input Events” . http://www.apress.com/9781430268727

Java 7 Recipes Apress publishing- pg. 602 chapter 16 Recipe 16-3 “Animating Shapes Along a Path”. http://www.apress.com/9781430240563

Video Game arcade cabinet: http://en.wikipedia.org/wiki/Video_game_arcade_cabinet

Raster Graphics: http://en.wikipedia.org/wiki/Raster_graphics

Part 3 source code at the GitHub: https://github.com/carldea/JFXGen/tree/master/demos/navigateship

JavaFX Canvas Node: http://mail.openjdk.java.net/pipermail/openjfx-dev/2012-April/001210.html

JavaFX- Optimizing Performance for JavaFX Applications: http://www.parleys.com/#st=5&id=2738&sl=0

Oracle’s Java Technology Evangelist Simon Ritter: https://blogs.oracle.com/javaone/entry/interfacing_with_the_interface_javafx

Video Game High School episode 1: http://www.rocketjump.com/?video=vghs-episode-1

Video Game High School episode 2: http://www.rocketjump.com/?video=vghs-episode-2-5

JavaFX to JavaScript and Back Part 2

Tons of Bouncy Balls

Tons of Bouncy Balls

Introduction

In the second installment of the series “JavaFX to JavaScript and Back” we will look at code! If you remember what we discussed in my previous post of Part 1 showing a demo of many bouncy balls. If you want to skip to the source code go to demo and click on download project.

Newcomers

If you are new and just getting started with JavaFX I strongly suggest JavaFX.com or Java.sun.com. If you are like me, one who knows enough to be dangerous please skip to Getting Started. Newcomers should take a look at books in the References section at the end of this article. Another awesome resource is the “Free” 15-Week JavaFX Programming (with Passion!) Online Course w/ Sang Shin & James Weaver.

Assumptions

  • NetBeans 6.5.1 is installed with JavaFX 1.2 pluggin.
  • Java SDK 1.6 update 14
  • Know basic JavaScript
  • Know basic HTML

Getting started

To give you a quick recap of the scenarios:

  1. Create Bouncy Balls (JavaScript to JavaFX)
  2. Remove Bouncy Balls (JavaFX to JavaScript)

Create a JavaFX Applet

Step 1: Create a Ball (Custom Node)
First create a class called Ball that extends a CustomNode. The key is overriding the create() method of the CustomNode class. We first create a Circle with a RadialGradient fill. Next we create a Group containing the circle in its content attribute. Then add the onMousePressed function to the group to detect a mouse pressed event which will remove this ball’s instance from the Scene (Game Area) and decrements the numBalls variable which updates the Web Page’s form element Number of Balls. Finally we return the group (ball variable).

class Ball extends CustomNode {
    public var velocity:Velocity;
    public var centerX:Integer=100;
    public var centerY:Integer=100;
    public var radius:Integer;
    public var fill:Color;
    protected override function create(): Node {
        var circle:Circle = Circle {
                centerX: centerX, centerY: centerY
                radius: radius
                fill: RadialGradient {
                        centerX: centerX - radius / 3
                        centerY: centerY - radius / 3

                        radius: radius
                        proportional: false
                        stops: [
                            Stop {
                                offset: 0.0
                                color: getRndColor()
                            },
                            Stop {
                                offset: 1.0
                                color: Color.BLACK
                            }
                        ] // stops
                } // RadialGradient
        } // circle
        var ball = Group{
            content:[circle];
            // remove ball
            onMousePressed: function( e: MouseEvent ):Void {
                delete this from gameArea;
                numBalls--;
            }
        } // Group

        return ball;
    } // create()
} // Ball

Step 2: Create initial ball to be put in Scene

You should notice I’ve created a few script level functions which conveniently return random values to create a Ball node. I won’t list those function for brevity. The random generated attributes are: velocity, centerX, centerY, radius, circle.fill = (RadialGradient 1st stop Color [Step 1 – Line 19])

// create an initial ball to float around.
var initialBallRadius = getRndRadius();
var rndCenter:Point = getRndCenter(500, 400, initialBallRadius);
var firstBall = Ball{
    velocity:getRndVelocity()
    centerX: rndCenter.x
    centerY: rndCenter.y
    radius: initialBallRadius
};

Step 3: Create the Scene to display Balls
The variable gameArea is a sequence of Nodes. You will notice the ball instance from Step 2 is the first node in the sequence. The scene’s content attribute is bound to the gameArea variable. During run time as nodes are added and removed from the game area the scene will dynamically update the visible nodes.

var gameArea:Node[] = [firstBall, toggleAnimationButton, mainRectRegion];
var scene:Scene = Scene {
        content: bind gameArea
        fill:Color.TRANSPARENT
};

Step 4: Create the Stage
This is the top level container which holds the scene.

Stage {
    title: "Application title"
    width: 500
    height: 400
    opacity: bind mainScreenOpacity;
    scene: scene
}

Step 5: Create a game or animation loop using the Timeline class
Here we create a Timeline instance that will run indefinitely (Timeline.INDEFINITE) with a single key frame (KeyFrame) which periodically updates each bouncy ball’s attributes which eventually renders each ball node with a new x and y position based on the velocity. Also the Timeline (gameLoop) is started immediately using the play() function.

var gameLoop:Timeline = Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames : [
        KeyFrame {
            time: 1s / 50
            canSkip : true
            action: function() {
                for (node:Node in gameArea) {
                    if (not (node instanceof Ball)){
                        continue; // don't update non balls
                    }
                    var ball = node as Ball;
                    var xMin = ball.boundsInParent.minX;
                    var yMin = ball.boundsInParent.minY;
                    var xMax = ball.boundsInParent.maxX;
                    var yMax = ball.boundsInParent.maxY;

                    // Collision - boundaries
                    if (xMin  scene.width){
                       ball.velocity.xVelocity = ball.velocity.xVelocity * -1;
                    }
                    if (yMin  scene.height){
                       ball.velocity.yVelocity = ball.velocity.yVelocity * -1;
                    }

                    ball.translateX = ball.translateX + ball.velocity.xVelocity;
                    ball.translateY = ball.translateY + ball.velocity.yVelocity;
                }
            } // action
        } //
    ]
};
gameLoop.play();

Step 6: Create the AppletStageExtension
This is the reference to the JavaFX applet which would assist with browser functionality such as the evaluation of JavaScript code.

var applet: AppletStageExtension;

Step 7: Create the numBalls variable keeping track of adds and removes.
The numBalls variable has a trigger (on replace) that updates the HTML page’s input text field “numBalls“. You should notice the id attribute of the tag is name “numBalls“. An id is a way to uniquely locate the form element in the HTML DOM (in this case a reference to the input field. When numBalls variable changes the trigger will evaluate the JavaScript code “document.getElementById(‘numBalls’).value = {numBalls}”. What this does is populate the form element field’s ‘id’ called “numBalls” whenever the JavaFX variable numBalls is updated.

var numBalls:Integer = 1 on replace {
    applet.eval("document.getElementById('numBalls').value = {numBalls}");
};

Step 8: Create a JavaFX function to add lots of balls

The HTML Web page will have a JavaScript function that would call the JavaFX function below when the user presses the Add Ball(s) button.  javafxtojavascriptandback8You will notice the FX.deferAction() call, which ensures updates to JavaFX classes are happening on the JavaFX main processing thread. This function will instantiate num balls inserts into the game area (Scene content) and increments the numBalls (trigger will update Web page’s form element).

// This function is called from the JavaScript button and numBallsToAdd field
function addBalls(num:Integer) : Void {
    FX.deferAction(function() : Void {
        for( i in [1..num]){
            var curBallRadius = getRndRadius();
            var rndCenter:Point = getRndCenter(500, 400, curBallRadius);
            var b = Ball{
                velocity:getRndVelocity()
                centerX: rndCenter.x
                centerY: rndCenter.y
                radius: curBallRadius
                fill: getRndColor()
            };
            insert b into gameArea;
            numBalls++;
        }
    });
}

Create a HTML Web page containing the JavaFX applet

Step 9: Create HTML Web page to display JavaFX applet

Run in Browser

Run in Browser

After creating the project in NetBeans select project / properties / Run (categories) / Application Execution Model – Run in Browser.

Step 10: Build Project

Building the project which will create files in the {PROJECT_HOME}/dist directory.

Step 11: Copy originally NetBeans created HTML file of the dist directory to another name

Use the copied and renamed HTML file and not the original because when the build process occurs it will overwrite the original HTML generated file.

Step 12: Modify HTML code to contain JavaScript to call JavaFX applet.

    javafx(
        {
              archive: "JavaFXtoJavaScript.jar",
              draggable: true,
              width: 500,
              height: 400,
              code: "javafxtojavascript.Main",
              name: "JavaFXtoJavaScript",
			  id: "ballApp"
        },
		{ isApplet: "true" }
    );

function addBalls() {
	document.ballApp.script.addBalls(document.getElementById('numBallsToAdd').value);
}

Step 13: Modify HTML to contain Form Elements.

<form>
<table>
<tr><th>Add balls to Scene :</th>
<td><input id=”numBallsToAdd” name=”numBallsToAdd” value=”1″/></td>
<td><input onClick=”addBalls();” type=”button” name=”addButton” value=”Add Ball(s)”/></td>
</tr>
<tr>
<th>Number of balls: </th>
<td><input id=”numBalls” name=”numBalls” READONLY/></td>
</tr>
</table>
<br/>
</form>

Running Applet in Browser through NetBeans

Step 14: Run application or hit F6 key

This will launch the JavaFX applet into the browser from the original HTML page created from the build. Go to the address and modify the URL (Just the file name) to use the modified copy which would run the JavaFX applet along with the form elements added from step 12 – 13.

Conclusion

Although the example only used simple attributes of the AppletStageExtension there are other properties and events to explore such as onAppletRestored, showDocument(), etc.  Hopefully we can get away from HTML and just have rich client applications, however I don’t think the browser is going away anytime soon. So we can happily go from JavaFX to JavaScript and Back!

Enjoy. Let me know what you think.

References:

Pro JavaFX Platform by Jim Weaver, Weiqi Gao, Stephen Chin, and Dean Iverson
JavaFX: Developing Rich Internet Applications by Jim Clarke, Jim Connors and Eric J. Bruno
Essential JavaFX by Gail Anderson and Paul Anderson

numBalls

JavaFX to JavaScript and Back Part 1

Introduction

One of the most impressive features that I’ve seen relating to JavaFX, is the ability of an Applet to interact with the browser via JavaScript and HTML. The browser can also interact back with JavaFX code. There are plenty of examples out there but I’d like to add one more to the pile of cool examples. It may be cool using AJAX concepts with JavaScript & HTML, but using the concept with JavaFX is just amazing. The possibilities are endless. “JavaFX to JavaScript and Back Part 1” will demonstrate what the application will do and “JavaFX to JavaScript and Back Part 2” will be on steps on how-to do it.

While I was a kid growing up, I would go to the bowling alley to get a 25 cent(US) bouncy ball from the vending machine. I’ve lost many a bouncy ball in the parking lot from bouncing them as hard as I could. In this example I will create a 500 by 400 pixel sized bouncy ball containment field in JavaFX. This is to prevent them from ever leaving me like they did when I was a kid. 😦

Note: Thanks to Stephen Chin an author of the book Pro JavaFX Platform and WidgetFX for allowing me to host The Bouncy Ball Containment Field Click Here to run!

(Tip: If in Firefox just middle mouse click link to launch in another tab)

Initial Ball added to ball world

Here is an example of an HTML page with an embedded JavaFX applet with HTML form elements below for data input to create bouncy balls. The demo allows a user to specify the number of balls to add to the scene called the “Add balls to Scene” field.  When the “Add Ball(s)” button is pressed the JavaScript code will pass the number of balls to the JavaFX public script level function called addBalls() which will create bouncy balls. When balls are added the JavaFX Script code will talk back to the HTML read-only field called “Number of balls” to show the current number of balls in the containment field (Scene). On the JavaFX side of things the location, velocity, size and color of the balls will be generated randomly and rendered on the scene.  You should notice the new JavaFX 1.2 Toggle Button component on the upper left. It is used to stop and  start the animation of the bouncing of the balls (Timeline pause & play to be exact). The first use case scenario is add balls which depicts the JavaScript to JavaFX data flow. The second use case scenario is display the current number of bouncy balls in the containment field which depicts the JavaFX to JavaScript data flow.

Balls addBalls(lots of balls)

This shows when the user specifies a whole bunch of balls to add.

Button to use JavaScript function addBalls()
Button to use JavaScript function addBalls()

A ball can be removed from the scene by just clicking an individual ball.  The field “Number of balls” input field is really a read-only field that is updated from the JavaFX side whenever balls are added and removed from the bouncy ball containment field.

More Bouncy Balls

By adding many balls I wanted to see how well it would perform.

Tons of Bouncy Balls

Here I add balls, lots of balls! The bouncy ball containment field now holds 400 bouncy balls. Although I might not be doing things efficiently I noticed my CPU was performing adequately. The utilization was between 41% to 46% (older Dell hyper-threading Inspiron XPS laptop Pentium(R) 4 CPU 3.40 GHz) using Windows XP SP 3. It was a smooth animation with no flicker. The Ball class is a Custom Node which contains a Circle with an effect called Radial Gradient to give it a 3D sphere look.

Dragging JavaFX Applet out of Web Page

By using the Alt key along with mouse drag the JavaFX applet will appear to be pulled out of the browser’s Web page and out onto the desktop.

JavaFX Applet living outside of browser

By default, when you drag the applet out of the browser onto your desktop a small “close button” appears on the upper right corner of your application. When the user clicks the “close button” box the JavaFX applet will snap right back into the browser. Another thing to note is that when the JavaFX applet is running outside the browser and the user subsequently closes the browser the applet will remain running independent of the browser.   This introduces an interesting ability that adds a new dimension to usability on the desktop.  As the user becomes drawn into the application experience the need to be attached to the Web page will be obsolete. Another cool effect that I have added to the Application is transparency or opacity level when the mouse cursor is exiting or entering the scene area. When the user moves the mouse away from the application the screen will fade to a subtle transparency. When the user moves the mouse cursor into the application screen area the background becomes fully opaque (white in this case).

Fully Opaque when mouse moves into application

Fully Opaque when mouse moves into application

This fading effect helps the user transition from one application to another.

The last, item of the desktop experience to mention is the Java JNLP mechanism that asks the user if he or she wants to create a short-cut icon for the application to launch at a later time.

Create short-cut dialog

Create short-cut dialog

To reproduce the dialog asking for a shortcut, you would drag the applet out of browser, then close tab or browser application and then a dialog will appear asking to create the short-cut.

Conclusion

Building the bouncy ball containment field was extremely fun. I learned a lot about boundsInParent which taught me that it is the rectangular region after all transformations to the node has been applied when adding balls to the Scene. As the future of the Web 2.0 apps grow users will demand richer interfaces and better content. I believe JavaFX has made great strides in desktop development and also increasing the usability factor of desktop applications by leaps and bounds. In Part 2 of “JavaFX to JavaScript and Back” I will detail a step-by-step tutorial on how to build the JavaFX applet you see here. Let me know what you think. Feedback is always welcome!