Programming Games in JavaFX - Part 4

In this lesson we'll give our ship some fire power to launch bullets. We'll create a new class called Bullet.fx and it will have some of the same properties of the Ship class, although it is actually much simpler.  One difference between the Ship and Bullet classes is that Ship displays an image, while Bullet just displays a tiny rectangle that is filled in with blue.  As you create your own games, you'll probablly create many classes that have the same properties as Ship and Bullet, such as posX, posY, moveAngle, velocityX, velocityY, etc.  There is one property in Bullet.fx that Ship.fx does not have, and this is 'active'.  We'll use this boolean flag to determine if a bullet is on the screen, if so we'll need to handle its position during the game loop and, in a later lesson, we'll need to check all 'active' bullets in our collision detection code.

 

We'll start with the additions to our Config.fx class:

 

package blasteroids;

public def SCREEN_HEIGHT:Integer = 800;
public def SCREEN_WIDTH:Integer = 1200;
public def SHIP_ROTATION_VELOCITY = 10;
public def REFRESH_RATE = .04s;
public def SHIP_ACCELERATION = 1;
public def BULLET_VELOCITY=15;
public def BULLET_COUNT=10;

 

BULLET_VELOCITY will be fixed at 15 and BULLET_COUNT will be used to limit the number of bullets that can be on the screen at any given time.

 

Here's our new Bullet.fx class, go ahead and add the Bullet class to the project and copy this code into it:

 

package blasteroids;

import javafx.scene.CustomNode;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

public class Bullet extends CustomNode {

    public var posX: Number;
    public var posY: Number;
    public var offsetX: Number = bind posX - rectangle.width / 2;
    public var offsetY: Number = bind posY - rectangle.height / 2;
    public var active: Boolean = false;
    public var moveAngle: Number;
    public var velocityX: Number;
    public var velocityY: Number;

    var rectangle:Rectangle = Rectangle{
        width: 3
        height: 3
        fill: Color.BLUE;
        translateX: bind offsetX;
        translateY: bind offsetY;
    }

    override public function create():Node{
        return rectangle;
    }

}

 

Finally, we'll need to update our game engine, Container.fx, to control all activities associated with bullets.  We'll add a sequence of Bullet objects and this sequence will get initialized by a function called initializeBullets(), which gets called from within the startGame() function. Since we only have 10 bullets, we'll need to recycle them; so we'll use a variable called currentBullet to keep track of how many have been fired. Once this variable reaches the max, we'll start recycling them. Of course, if you want to allow more bullets you can simply increase the BULLET_COUNT constant in Config.fx.  We'll also add code to launch a bullet when the space bar is pressed.  Finally we'll have to add code that handles the repositioning of all active bullets when the game loop cycles.  By now, you may have already noticed that as the gameLoop Timeline cycles, it calls the function gameUpdate().  This function is used to call the functions that control the movement of all the objects on the game board.  This is where we'll add the call to a new function called updateBullets(). Here's Container.fx with the new code higlighted in bold:

 

package blasteroids;

import blasteroids.Config;
import blasteroids.Ship;
import java.lang.Math;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;

public class Container extends CustomNode {
    public var turnLeft:Boolean = false;
    public var turnRight:Boolean = false;
    public var ship: Ship = Ship{
        posX: Config.SCREEN_WIDTH / 2
        posY: Config.SCREEN_HEIGHT /2
    }
    var bullets: Bullet[];
    var currentBullet:Integer = 0;
    function initializeBullets():Void{
        //make sure the bullets sequence is empty before reloading...
        delete bullets;
        for(i in [1..Config.BULLET_COUNT]){
            var b=Bullet{};

            b.active = false;
            b.visible = false;
            //put the bullet onto the gameBoard,
            //even though it's not active yet...
            insert b into gameBoard.content;
            //put the bullet into the bullets sequence...
            insert b into bullets;
        }
    }

    public var txtInfo:Text = Text{
        x:10
        y:20
        wrappingWidth: Config.SCREEN_WIDTH - 20
        font: Font { size: 18 }
        fill: Color.WHITE
        //Just changing the text that was set in the last lesson...
        content:"Use the left and right arrow keys to turn the ship."
        "\nUse the up and down arrow keys to move the ship "
        "forward or reverse it."
        "\nPress the space key to fire bullets."
        "\nPress ENTER to start..."
    }
    public var gameBoard: Group = Group{
        content: [
            //backgroud rectangle...
            Rectangle{
                width: Config.SCREEN_WIDTH;
                height: Config.SCREEN_HEIGHT;
                fill: Color.BLACK
            }
            //add the info text to the scene...
            txtInfo,
            //add the ship to the scene...
            ship
        ]
        focusTraversable:true
        //setting focusTraversable to true allows us to
        //listen for keyboard events
        onKeyPressed: function(e:KeyEvent):Void{
                if(e.code == KeyCode.VK_ENTER){
                    if(not gameLoop.running or gameLoop.paused){
                        startGame();
                    }
                }
                if(e.code == KeyCode.VK_LEFT){
                    turnLeft = true;
                }
                if(e.code == KeyCode.VK_RIGHT){
                    turnRight = true;
                }
                if(e.code == KeyCode.VK_UP){
                    ship.moveAngle = ship.faceAngle;
                    ship.velocityX += Math.sin(Math.toRadians(ship.moveAngle)) * Config.SHIP_ACCELERATION;
                    ship.velocityY += -Math .cos(Math.toRadians(ship.moveAngle)) * Config.SHIP_ACCELERATION;
                }
                if(e.code == KeyCode.VK_DOWN){
                    ship.moveAngle = ship.faceAngle;
                    ship.velocityX += Math.sin(Math.toRadians(ship.moveAngle)) * -Config.SHIP_ACCELERATION;
                    ship.velocityY += -Math .cos(Math.toRadians(ship.moveAngle)) * -Config.SHIP_ACCELERATION;
                }
                if(e.code == KeyCode.VK_SPACE){
                    //launch a bullet...
                    currentBullet++;
                    //if we reach the end of the bullets sequence, then
                    //we start recycling bullets...
                    if(currentBullet > Config.BULLET_COUNT - 1){
                        currentBullet = 0
                    }
                    //get a handle on the current bullet...
                    var b = bullets[currentBullet];
                    //make the bullet active...
                    b.active = true;

                   //make sure the bullet is visible...
                    b.visible = true;
                    //set the bullet's position to the position
                    //of the ship...
                    b.posX = ship.posX;
                    b.posY = ship.posY;
                    //set the bullet's move angle equal to the face angle
                    //of the ship...
                    b.moveAngle = ship.faceAngle;
                    //set the velocity of the bullet...
                    b.velocityX = Math.sin(Math.toRadians(b.moveAngle))
                        * Config.BULLET_VELOCITY;
                    b.velocityY = -Math .cos(Math.toRadians(b.moveAngle))
                        * Config.BULLET_VELOCITY;
                }

            }
            onKeyReleased:function(e:KeyEvent):Void{
               if(e.code == KeyCode.VK_LEFT){
                    turnLeft = false;
                }
                if(e.code == KeyCode.VK_RIGHT){
                    turnRight = false;
                }
            }
    }
    override public function create():Node{
        return gameBoard;
    }
    def gameLoop:Timeline = Timeline{
        repeatCount: Timeline.INDEFINITE
        keyFrames: [
            KeyFrame{
                time: Config.REFRESH_RATE
                action: function(){
                    gameUpdate();
                }
            }
        ]
    }
    public function startGame():Void{
        txtInfo.visible = false;
        initializeBullets();
        gameLoop.play();
    }
    public function gameUpdate():Void{
        updateShip();
        updateBullets();
    }
    public function updateBullets():Void{
        for(b in bullets){
            if(b.active){
                //update the active bullet's position...
                b.posX += b.velocityX;
                b.posY += b.velocityY;
                //if the bullet goes off the screen, set it to inactive...
                if(b.posX < 0 or b.posY > Config.SCREEN_WIDTH){
                    b.active = false;
                }
                if(b.posY < 0 or b.posY > Config.SCREEN_HEIGHT){
                    b.active = false;
                }
            }
        }
    }

    public function updateShip():Void{
        if(turnLeft){
            ship.faceAngle -=Config.SHIP_ROTATION_VELOCITY;
        }
        if(turnRight){
            ship.faceAngle +=Config.SHIP_ROTATION_VELOCITY;
        }
        //update the ship's position...
        ship.posX += ship.velocityX;
        ship.posY += ship.velocityY;

        //handle wrapping for when ship goes out of bounds...
        if(ship.posX < 0 - ship.width/2 ) {
            //ship has moved off the left side,
            //so make it wrap to the right side...
            ship.posX = Config.SCREEN_WIDTH;
        }
        if(ship.posX > Config.SCREEN_WIDTH + ship.width/2) {
            //ship has moved off the right side,
            //so make it wrap to the left side...
            ship.posX = 0;
        }
        if(ship.posY < 0 - ship.height/2){
            //ship has moved off the bottom of the scene,
            //so make it wrap to the top...
            ship.posY = Config.SCREEN_HEIGHT;
        }
        if(ship.posY > Config.SCREEN_HEIGHT + ship.height/2 )
        {
            //ship has moved off the top of the scene,
            //so make it wrap to the bottom...
            ship.posY = 0;
        }
    }
}

 

That should do it for this lesson. In the next lesson we'll give our ship some targets by adding a sequence of astroids. To continue with the tutorial, go to Programming Games in JavaFX - Part 5.

 

 


RECENT ARTICLES