Creating a Simple Game in JavaFX (Part 3)


So far we have created a simple JavaFX application which sports a rectangle shape which will be our playing field. In this part we will now add the player object and the code that lets the user control its movement.

Creating the Player Object


var player: Circle = Circle {
    translateX: 250, translateY:250
    radius: 10
    fill: Color.BLACK
}
Stage {
...
    scene: Scene {
        ...
        content: Group {
            ...
            content: [
                playingField,
                player
            ]
        }
    }
}

I will not spend a lot of time commenting the code. The player object is a simple Circle shape variable defined like the playing field at script level, i.e. outside the Stage object. The player object is then added to the content of Scene’s Group object’s content after the playing field. Adding it after the playing field makes it appear on top of the playing field otherwise it would not be visible. The result looks like this:

img3

Adding User Interaction
In order to move the player object around we will have to listen to key events. If the user presses the left arrow key, we want the player object to move left, if he presses the right arrow key, the object should move right etc.. But listening to key events is not enough. Later when we have enemy ball objects and our playing field has bounds that cannot be crossed, several actions need to be synchronized at a certain interval level. We will call this a cycle in the game play. Creating such a cycle that checks everything that has happened (for example our Key Events) and recalculates all other events (for example collisions) is very easy with a JavaFX timeline.


var timeline: Timeline = Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames : [
        KeyFrame {
            time : 30ms
            canSkip: false
            action: function() {
            }
        }
    ]
}

Stage {
...
}

// Start game
timeline.playFromStart();

We define a timeline object that runs indefinately. Our cycle is 30ms long, so every 30ms all the game’s calculations are made. Do not forget to start the timeline at the end of our script. The function defined as the keyframe’s action is where these calculations take place as we will see in a minute.

Now for capturing the key events:


...
var moveUp = false;
var moveDown = false;
var moveLeft = false;
var moveRight = false;
...
Stage {
    ...
    scene: Scene {
        ...
        content: Group {
            focusTraversable: true
            ...
            content: [
                playingField,
                player
            ]
            onKeyPressed : function (e: KeyEvent){
              if (e.code == KeyCode.VK_LEFT) {
                    moveLeft = true;
              }
              if (e.code == KeyCode.VK_RIGHT) {
                    moveRight = true;
              }
              if (e.code == KeyCode.VK_UP) {
                    moveUp = true;
              }
              if (e.code == KeyCode.VK_DOWN) {
                    moveDown = true;
              }
            }
            onKeyReleased : function (e: KeyEvent){
              if (e.code == KeyCode.VK_LEFT) {
                    moveLeft = false;
              }
              if (e.code == KeyCode.VK_RIGHT) {
                    moveRight = false;
              }
              if (e.code == KeyCode.VK_UP) {
                    moveUp = false;
              }
              if (e.code == KeyCode.VK_DOWN) {
                    moveDown = false;
              }
            }
        }
    }
}

There are several things to notice in the new code. First of all the main Group object is set to focusTraversable=true. Otherwise it would not capture any key events. Capturing key events is pretty self explanatory except for the fact that we are not moving anything but merely remembering that the ball should move. If we press the left arrow key. The variable moveLeft is set to true. In our cycle we will have to process this information as we will see in a minute. If we release the key the variable moveLeft is set to false and the player object should not move. In theory if you were fast enough at pressing and releasing the key, a cycle could miss a movement, but this practically not happen and is not relevant. Now lets process the movement in our timeline object.


def PLAYER_BALL_STEP = 4;
...
var timeline: Timeline = Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames : [
        KeyFrame {
            time : 30ms
            canSkip: false
            action: function() {
                // Player
                if (moveRight) {
                    var newX = player.translateX + PLAYER_BALL_STEP;
                    if (newX < playingField.boundsInLocal.maxX - player.radius - playingField.strokeWidth) {
                        player.translateX = newX
                    }
                }
                if (moveLeft) {
                    var newX = player.translateX - PLAYER_BALL_STEP;
                    if (newX > playingField.boundsInLocal.minX + player.radius + playingField.strokeWidth) {
                        player.translateX = newX
                    }
                }
                if (moveUp) {
                    var newY= player.translateY - PLAYER_BALL_STEP;
                    if (newY > playingField.boundsInLocal.minY + player.radius + playingField.strokeWidth) {
                        player.translateY = newY
                    }
                }
                if (moveDown) {
                    var newY= player.translateY + PLAYER_BALL_STEP;
                    if (newY < playingField.boundsInLocal.maxY - player.radius - playingField.strokeWidth) {
                        player.translateY = newY
                    }
                }
            }
        }
    ]
}

At the top of the script we define a constant called PLAYER_BALL_STEP which will tell us, if the player has moved the ball in one direction, how many pixel it should travel each circle. At 30ms cycle time moving just 1 pixel makes the player object very slow. This is where you can speed up and slow down the speed of the player object by adjusting the value. In a later game design you might add a speed up bonus or something similar.
Lets pretend the player has pressed the right arrow key. Variable moveRight is set to true, a new position on the x-axis is calculated for the player object by taking the current x position player.translateX and adding the number of steps the player object should move. We then check if the ball is within the bounds of the playing field, otherwise you could move the ball anywhere you wanted including right out of the window. If it is inbounds we set the X-axis position of our player object to the new value.
One thing to notice is that we are not working with if-else constructs. This is because in theory the user can press up and right at the same time and we then want the ball to move diagonally.
We now have a player object capable of moving and staying in the playing fields bounds. The next step is to add some enemy objects. Below is a complete listing of the code so far, so you don’t get mixed up.


/*
 * Main.fx
 *
 * Created on 15.10.2009, 17:36:19
 */

package myballgame;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.Group;

import javafx.scene.shape.Rectangle;

import javafx.scene.shape.Circle;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;

import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

/**
 * @author Alexander Gnodtke
 */
def PLAYER_BALL_STEP = 4;

var moveUp = false;
var moveDown = false;
var moveLeft = false;
var moveRight = false;

var playingField: Rectangle = Rectangle {
    arcWidth: 20  arcHeight: 20
    width: 280, height: 380
    fill: Color.ANTIQUEWHITE
    stroke: Color.DARKORANGE
    strokeWidth: 3
}

var player: Circle = Circle {
    translateX: 250, translateY:250
    radius: 10
    fill: Color.BLACK
}

var timeline: Timeline = Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames : [
        KeyFrame {
            time : 30ms
            canSkip: false
            action: function() {
                                  // Player
                if (moveRight) {
                    var newX = player.translateX + PLAYER_BALL_STEP;
                    if (newX < playingField.boundsInLocal.maxX - player.radius - playingField.strokeWidth) {
                        player.translateX = newX
                    }
                }
                if (moveLeft) {
                    var newX = player.translateX - PLAYER_BALL_STEP;
                    if (newX > playingField.boundsInLocal.minX + player.radius + playingField.strokeWidth) {
                        player.translateX = newX
                    }
                }
                if (moveUp) {
                    var newY= player.translateY - PLAYER_BALL_STEP;
                    if (newY > playingField.boundsInLocal.minY + player.radius + playingField.strokeWidth) {
                        player.translateY = newY
                    }
                }
                if (moveDown) {
                    var newY= player.translateY + PLAYER_BALL_STEP;
                    if (newY < playingField.boundsInLocal.maxY - player.radius - playingField.strokeWidth) {
                        player.translateY = newY
                    }
                }
            }
        }
    ]
}

Stage {
    title: "Ballgame"
    scene: Scene {
        fill: Color.CHOCOLATE;
        width: 430, height: 400
        content: Group {
            focusTraversable: true
            translateX: 10, translateY:10
            content: [
                playingField,
                player
            ]
            onKeyPressed : function (e: KeyEvent){

              if (e.code == KeyCode.VK_LEFT) {
                    moveLeft = true;
              }
              if (e.code == KeyCode.VK_RIGHT) {
                    moveRight = true;
              }
              if (e.code == KeyCode.VK_UP) {
                    moveUp = true;
              }
              if (e.code == KeyCode.VK_DOWN) {
                    moveDown = true;
              }
            }
            onKeyReleased : function (e: KeyEvent){
              if (e.code == KeyCode.VK_LEFT) {
                    moveLeft = false;
              }
              if (e.code == KeyCode.VK_RIGHT) {
                    moveRight = false;
              }
              if (e.code == KeyCode.VK_UP) {
                    moveUp = false;
              }
              if (e.code == KeyCode.VK_DOWN) {
                    moveDown = false;
              }
            }
        }
    }
}

// Start game
timeline.playFromStart();

Tags: , ,

Leave a Reply