Archive for the ‘Software’ Category

JSF Error – Target Unreachable, identifier ‘MyBacking’ resolved to null

Friday, July 16th, 2010

My JSF application was throwing the following error message:


Target Unreachable, identifier 'MyBacking' resolved to null

All of my other backing beans were working so It was kind of perplexing and I could not immediately pinpoint the error source.
To cut a long story short, it turns out that the ManagedBean annotation requires the name attribute like so:


@ManagedBean(name = "MyBacking")
@RequestScoped
public class MyBacking {
[...]

This is kind of odd since the class name and the defined attribute name are equal.

My environment:

Netbeans 6.9
Mojarra 2.0.2
EclipseLink, version: Eclipse Persistence Services – 2.0.0.v20091127-r5931
GlassFish Server 3

How to record / create a Macro in Netbeans

Friday, May 28th, 2010

One of the great features of the Netbeans IDE is the ability to create a macro to simplify repetitive and tedious tasks. In my case I had a long list of country names that needed to be placed in a Java property file. I.e. turning the following:


...
Armenia
Australia
Austria
Belgium
Bolivia
...

into the following key-value pair:


...
Armenia=Armenia
Australia=Australia
Austria=Austria
Belgium=Belgium
Bolivia=Bolivia
...

This of course is a real nuisance but with the help of a little macro the work can get done in no time.

My first attempt to create such a macro is in using the macro recorder. I placed the curser right after the word “Armenia” and pressed the round red “Start Macro Recording” button at the top of the editor.
I then moved the cursor holding the shift button (select) and using the arrow buttons to the beginning of the line. Then I pressed Ctrl-C (copy) and then moved the cursor via the mouse back to the end of the word “Armenia”. I entered a “=” symbol and pressed Ctrl-V. I then stopped recording the macro. Finally I assigned the previously unused keyboard shortcut CTRL-L to my new macro.

As could be expected the macro recorder has no notion of the beginning of the word or the beginning of the line. The recorder counted my backward selection steps and consequently the macro works only for countries with the same amount of letters as “Armenia” i.e. 7.

I opened the Macro Editor Tools -> Options -> Editor -> Macros and found the following code for my macro:


selection-backward
selection-backward
selection-backward
selection-backward
selection-backward
selection-backward
selection-backward
copy-to-clipboard
caret-forward
"="
paste-from-clipboard

What I need was a command that would take me to the beginning of the line. (The beginning of the word is not enough since some country names like for example “United States” consist of two words). In one of the previously defined macros I found the command “caret-begin-word” and I modified my macro as follows:


selection-begin-line
copy-to-clipboard
 caret-forward
 "="
 paste-from-clipboard

And sure enough when placing the cursor at the end of the country name and pressing CTRL-L you get the desired completion of the line.

Definitely not an advanced Netbeans topic, but getting the hang of this can be a great time saver.

How to register a ServletContextListener in JSF 2.0

Thursday, May 27th, 2010

In pre JSF 2.0 you were forced to use the WEB-INF/web.xml file to register your ServletContextListener



<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <listener>
        <listener-class>com.mycompany.myproject.MyServletContextListener</listener-class>
    </listener>
...
</web-app>

While you can still do this, in JSF 2.0 it is now possible to register your ServletContextListener or rather any of the following Listeners:

ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener, HttpSessionListener, HttpSessionAttributeListener

by using the @WebListener annotation.

So your listener implementation would look like the following:


@WebListener
public class MyServletContextListener implements ServletContextListener {
...
}

When registering my ServletContextListener I ran into the following error messages:


SCHWERWIEGEND: PWC1306: Startup of context /MyApp failed due to previous errors
SCHWERWIEGEND: PWC1305: Exception during cleanup after start failed
org.apache.catalina.LifecycleException: PWC2769: Manager has not yet been started
        at org.apache.catalina.session.StandardManager.stop(StandardManager.java:892)
        at org.apache.catalina.core.StandardContext.stop(StandardContext.java:5383)
        ...

SCHWERWIEGEND: ContainerBase.addChild: start:
org.apache.catalina.LifecycleException: java.lang.NullPointerException
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:5216)
        at com.sun.enterprise.web.WebModule.start(WebModule.java:499)
        ...

WARNUNG: java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: java.lang.NullPointerException
java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: java.lang.NullPointerException
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:932)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:912)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:694)
        ...

SCHWERWIEGEND: Exception while invoking class com.sun.enterprise.web.WebApplication start method
java.lang.Exception: java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: java.lang.NullPointerException
        at com.sun.enterprise.web.WebApplication.start(WebApplication.java:117)
        at org.glassfish.internal.data.EngineRef.start(EngineRef.java:126)
        at org.glassfish.internal.data.ModuleInfo.start(ModuleInfo.java:241)
        at org.glassfish.internal.data.ApplicationInfo.start(ApplicationInfo.java:236)

SCHWERWIEGEND: Exception while loading the app
java.lang.Exception: java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: java.lang.NullPointerException
        at com.sun.enterprise.web.WebApplication.start(WebApplication.java:117)
        at org.glassfish.internal.data.EngineRef.start(EngineRef.java:126)
        at org.glassfish.internal.data.ModuleInfo.start(ModuleInfo.java:241)

This was a simple oversight on my part. In my Listener I wanted to use an EJB to add some data to the database using JPA. I forgot to use the @EJB annotation when defining the EJB attribute:


@EJB
private MyFacade myfacade;
				

Processing GET request parameters in JSF 2.0

Friday, April 23rd, 2010

In JSF 2.0 <h:link> tags were introduced that allow users to navigate to a JSF page without using a postback request.

For example:


<h:link value="3 Month Subscription" outcome="subscribe" />

would create an HTML link similar to:


<a href="/MyApp/faces/subscribe.xhtml">3 Month Subscription</a>

The value of the “outcome” parameter utilizes JSF 2.0 implicit navigation rules that save you the trouble of defining navigation cases in the faces-config.xml file.

Furthermore query parameters may be added to the links:


<h:link value="3 Month Subscription" outcome="subscribe">
    <f:param name="subscriptionType" value="threeMonth" />
</h:link>

<h:link value="6 Month Subscription" outcome="subscribe">
    <f:param name="subscriptionType" value="sixMonth" />
</h:link>

The links would look like this:


<a href="/MyApp/faces/subscribe.xhtml?subscriptionType=threeMonth">3 Month Subscription</a>
<a href="/MyApp/faces/subscribe.xhtml?subscriptionType=sixMonth">6 Month Subscription</a>

This way we are able to preset the subscription type field on the subscribe.xhtml page. In subscribe.xhtml there might be the following Radio Buttons:


<h:selectOneRadio label="Subscription Type" value="#{BackingBean.subscriptionType}" id="subscriptionType">
    <f:selectItem itemLabel="3 Month Subscription" itemValue="threeMonth" />
    <f:selectItem itemLabel="6 Month Subscription" itemValue="sixMonth" />
</h:selectOneRadio>

Instead of representing the subscription type value as radio buttons you could use any other input component including for example a hidden field.

In order for the radio button to be selected upon loading the subscribe.xhtml page you must add the following annotation to the subscriptionType field in the corresponding JSF managed bean:


[...]
@ManagedProperty(value="#{param.subscriptionType}")
private String subscriptionType;
[...]

Differences between TopLink and EclipseLink

Monday, March 22nd, 2010

The default persistence manager in Netbeans Release 6.8 has changed from TopLink to EclipseLink and I will list error messages and differences that I find as I go along.

1.
ErrorMessage

Exception [EclipseLink-8034] (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.JPQLException
Exception Description: Error compiling the query [findUserByEmail: SELECT u FROM theuser u WHERE u.email = :email]. Unknown entity type [theuser].
at org.eclipse.persistence.exceptions.JPQLException.entityTypeNotFound(JPQLException.java:483)

Solution
EclipseLink is case sensitive. If your entity is named “TheUser” (yes, it is a dumm name for an entity) your named query should be:
@NamedQueries({
@NamedQuery(
name="findUserByEmail",
query="SELECT u FROM TheUser u WHERE u.email = :email"
)
})

and not ...SELECT u FROM theuser....

Preview Release of JavaFX Composer

Thursday, December 17th, 2009

Just this week marked the release of Netbeans 6.8. Another release that has been worth waiting for with many great new features including full support of Java EE 6.

One of the things that followed after the release was the announcement in Netbeans newsletter Issue # 420 – Dec 15, 2009 that a preview release of JavaFX Composer was made. The release has been made very silently since none of the other major JavaFX blogs we are following has picked up the news. JavaFX Composer is a visual design tool or RAD (rapid application development) tool that has been much anticipated and will make the development of “business” applications using JavaFX more compelling for businesses.

We are very excited about the release and our first impression is very good. We hope to give the composer a more thorough test ride soon.

The JavaFX Composer is a Netbeans plugin and you can get it like this:

“The JavaFX Composer is a preview release and is available for NetBeans IDE 6.8 as a plugin from the NetBeans Update Center. (From the NetBeans IDE, go to Tools –> Plugins –> Available Plugins –> JavaFX.) ” (source Netbeans newsletter issue #420)

More information can be found here.

The other tool Sun is working on is the JavaFX Authoring Tool where no release date has yet been made public.

How to use Inkscape’s new JavaFX export functionality

Wednesday, December 16th, 2009

With the release of Inkscape 0.47, Inkscape now offers its users to save their work as JavaFX *.fx files. This is how it works for you.
Create a graphic in Inkscape for example:

img in inkscape

Choose File -> Save As… -> Select file type as JavaFX *.fx

The file name you have chosen for your JavaFX file is the name of the JavaFX class. We named our file MyShape.fx
So in a JavaFX Project you can call new MyShape and your graphic should appear.

img in javafx

Unfortunately there were three bugs that we encountered in this simple example.
Number one, the text of our Inkscape drawing does not show up in the JavaFX export (Inkscape Bug 489364).

Number two, in the create Method of the JavaFX export file a method named path4117-5() is called (Inkscape Bug 439270). This is not only incorrect syntax but the method does not exist. It was called path4117_5() and changes had to be made to the JavaFX file.

And number three, as you can see the stroke width of the line is not the same. Going into the code you can see that the width is set to 0.0 in Inkscape’s JavaFX file, when in fact it should be set to 6.0. (I filed bug 497416).

If you would like to avoid names such as path4117 etc. in your JavaFX files, select any one of your objects in Inkscape, press the right mouse button and select “object properties”. Here you can change the Id to something that is easier to remember.

There are of course more things to discover in collaboratively working with JavaFX and Inkscape, some of which we might focus on in the near future.

While these three bugs are unfortunate, they will probably be easy to resolve. Let us also not forget, that JavaFX support is new to Inkscape. What counts for now is that it is great that Inkscape offers an open source alternative to creating graphics for JavaFX.

Creating a Simple Game in JavaFX (Part 7)

Tuesday, October 27th, 2009


Final Thoughts

We have created a simple game in JavaFX and my personal experience with JavaFX is very positive. I find it very intuitive to create graphical objects and timeline objects for animation. This is a great time saver and leads to better structured code and better looking graphics when comparing the code to the initial JavaSE implementation (please see Part 1).

Of course this game is only somewhat fun to play. Things that I would like to change are:

  • Introduce Levels: Each Level would speed up the balls or add new balls
  • Introduce bonus objects: Extra lives, things that make your ball or the enemy balls smaller or larger, etc.
  • Tidy up the graphics: Think of a real game theme, instead of playing with balls
  • Introduce sound effects
  • Persist Highscore beyond the application life cycle
  • …and many other things

If someone reading this article is interested in further developing this game, leave me a comment and I could check the sources into a public repository like kenai or sourceforge. Any other comments would also be appreciated.

Game Applet


You can drag the game onto your desktop by pressing the “Alt” key while you are dragging the game with your mouse.

And here is the webstart version.

Complete Tutorial Code

Main.fx


/*
 * 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;

import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.layout.Tile;
import javafx.geometry.HPos;

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

def MODE_GAME_OVER: String = "GAME_OVER";
def MODE_RUNNING:   String = "RUNNING";

var mode:String = MODE_RUNNING;

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

var scoreTime = 0;
var scoreMoves = 0;
var scoreBonus = 0;
var scoreTotal = 0;
var scoreHigh = 0;

var scoreArea: Group = Group {

    content : [
        Rectangle {
            arcWidth: 20  arcHeight: 20
            x: 290 y: 0
            width: 120, height: 200
            fill: Color.LIGHTGOLDENRODYELLOW
            stroke: Color.DARKORANGE
            strokeWidth: 3
        },
        Text {
            font : Font {
                size: 24
            }
            layoutX: 320 layoutY: 30
            content: "Score"
        },
        Tile {
            columns: 2
            rows: 5
            hgap: 5
            vgap: 5
            tileWidth: 50
            hpos: HPos.LEADING
            layoutX: 290 layoutY: 50
            content: [
                Text {
                    font : Font {
                        size: 12
                    }
                    content: "Time:"
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: bind scoreTime.toString()
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: "Moves:"
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: bind scoreMoves.toString()
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: "Bonus:"
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: bind scoreBonus.toString()
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: "Total:"
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: bind scoreTotal.toString()
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: "High:"
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: bind scoreHigh.toString()
                }
            ]
        }
    ]
}

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

var gameOverScreen: Group = Group{
    visible:false
    var r:Rectangle = Rectangle {
        arcWidth: playingField.arcWidth  arcHeight: playingField.arcHeight
        width: playingField.width, height: playingField.height
        fill: Color.ROSYBROWN
        stroke: Color.DARKRED
        strokeWidth: playingField.strokeWidth
    }
    content: [
        r,
        Text {
            font : Font {
                size: 24
            }
            x: 10, y: 50
            content: "Game Over"
        },
        Text {
            font : Font {
                size: 20
            }
            x: 10, y: 80
            content: "(press Enter to restart)"
        }
   ]
}

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

var enemies : Enemy[] = for (i in [0..3]) {
    var enemy:Enemy = Enemy {
        translateX : randomEnemyInitPosition(), translateY:randomEnemyInitPosition()
    }
    enemy
};

var bonusTimeline: Timeline = Timeline {
    repeatCount: 1
    keyFrames : [
        KeyFrame {
            time : 3s
            canSkip: false
        }
    ]
}

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

                calculateBonusEnemy();

                // Enemy
                for (enemy in enemies)
                    enemy.calcPosition(playingField.boundsInLocal.minX,playingField.boundsInLocal.maxX,playingField.boundsInLocal.minY,playingField.boundsInLocal.maxY);

                // Player
                if (moveRight) {
                    var newX = player.translateX + PLAYER_BALL_STEP;
                    if (newX < playingField.boundsInLocal.maxX - player.radius - playingField.strokeWidth) {
                        playerMoved = true;
                        player.translateX = newX
                    }
                }
                if (moveLeft) {
                    var newX = player.translateX - PLAYER_BALL_STEP;
                    if (newX > playingField.boundsInLocal.minX + player.radius + playingField.strokeWidth) {
                        playerMoved = true;
                        player.translateX = newX
                    }
                }
                if (moveUp) {
                    var newY= player.translateY - PLAYER_BALL_STEP;
                    if (newY > playingField.boundsInLocal.minY + player.radius + playingField.strokeWidth) {
                        playerMoved = true;
                        player.translateY = newY
                    }
                }
                if (moveDown) {
                    var newY= player.translateY + PLAYER_BALL_STEP;
                    if (newY < playingField.boundsInLocal.maxY - player.radius - playingField.strokeWidth) {
                        playerMoved = true;
                        player.translateY = newY
                    }
                }
                // checkCollision
                for (enemy in enemies) {
                    if (checkCircleCollision(player.translateX,player.translateY,player.radius,enemy.translateX,enemy.translateY,10.0)) {
                        if (not enemy.bonusMode) {
                            gameOver();
                        } else {
                            calculateNewScore(playerMoved,true);
                        }
                    } else {
                        calculateNewScore(playerMoved);
                    }
                }
            }
        }
    ]
}

function calculateNewScore(addMoveScore:Boolean,addBonusScore:Boolean) {
    scoreBonus += 1000;
    calculateNewScore(addMoveScore);
}

function calculateNewScore(addMoveScore:Boolean) {
    scoreTime += 5;
    scoreMoves = if (addMoveScore) scoreMoves+5 else scoreMoves-10;
    scoreTotal = scoreTime + scoreMoves + scoreBonus;

}

function calculateBonusEnemy() {
    var bEnemy:Enemy = null;
    for (enemy in enemies) {
        if (enemy.bonusMode) {
            bEnemy = enemy;
        }
    }

    // None Bonus
    if (bEnemy==null) {
        if (javafx.util.Math.random()>0.95) {
            // Change one to bonus
            var i = (javafx.util.Math.ceil( enemies.size()*javafx.util.Math.random() )).intValue();
            println("changing to bonus {i}");
            enemies[i].fill(Color.CYAN);
            enemies[i].bonusMode = true;
            bonusTimeline.playFromStart();
        }
    } else {
        // One Bonus
        if (not bonusTimeline.running) { // min 3s in Bonus
            if (javafx.util.Math.random()>0.5) { // Turn off bonus
                bEnemy.fill(Enemy.DEFAULT_FILL);
                bEnemy.bonusMode = false;
                if (not bEnemy.bonusWasHit) {
                    scoreBonus -= 5000
                }
                bEnemy.bonusWasHit = false
            } else {
                // prolong bonus
                bonusTimeline.playFromStart();
            }

       }
    }
}

// Function called after collision
function gameOver():Void {
    timeline.stop();
    mode = MODE_GAME_OVER;
    gameOverScreen.visible = true;
    if (scoreTotal > scoreHigh)
        scoreHigh = scoreTotal;
}

function initGame():Void {
    initActors();
    initScore();
    mode = MODE_RUNNING;
    gameOverScreen.visible = false
}

function initScore() {
    scoreBonus = scoreMoves = scoreTime = scoreTotal = 0
}

// Function is used to initialize the actors i.e. player and enemies
// on the playing field
function initActors() {
    player.translateX = 250;
    player.translateY = 250;
    for (enemy in enemies) {
        enemy.translateX = randomEnemyInitPosition();
        enemy.translateY = randomEnemyInitPosition();
    }
}
// http://gpwiki.org/index.php/C:Collision_detection_between_two_circles
function checkCircleCollision(c1X:Number,c1Y:Number,c1R:Number,c2X:Number,c2Y:Number,c2R:Number):Boolean {
    var distanceSquared = ((c1X - c2X) * (c1X - c2X)) +  ((c1Y - c2Y) * (c1Y - c2Y));
    var radiiSquared = (c1R + c2R) * (c1R + c2R);

    if (radiiSquared > distanceSquared) {
        return true
    }

    return false
}

function randomEnemyInitPosition():Integer {
    var r = javafx.util.Math.random();
    var r2 = javafx.util.Math.random();
    ((r  * 100)+(r2*100)) as Integer
}

Stage {
    title: "Ballgame"
    scene: Scene {
        fill: Color.CHOCOLATE;
        width: 430, height: 400
        content: Group {
            focusTraversable: true
            translateX: 10, translateY:10
            content: [
                playingField,
                player,
                enemies,
                gameOverScreen,
                scoreArea
            ]
            onKeyPressed : function (e: KeyEvent){
              if (mode==MODE_GAME_OVER) {
                if (e.code == KeyCode.VK_ENTER) {
                    initGame();
                    timeline.playFromStart();
                }
              }
              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();

Enemy.fx


/*
 * Enemy.fx
 *
 * Created on 03.10.2009, 13:59:33
 */

package myballgame;

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

/**
 * @author Alexander Gnodtke
 */

public static def DEFAULT_FILL = Color.CRIMSON;

public class Enemy extends CustomNode {

    public var bonusMode = false;
    public var bonusWasHit = false; // Player hit in Bonus Mode

    var radius = 10;
    var moveUp = false;
    var moveLeft = false;
    var speedX = 0;
    var speedY = 0;

    var enemy:Circle;

    override function create():Node {

        // Enemy Speed
        speedX = calcSpeed();
        speedY = calcSpeed();

        // Enemy initial Direction
        moveUp = calcRandomBoolean();
        moveLeft = calcRandomBoolean();

        // Enemy figure
        enemy = Circle {
            radius: radius
            fill: Color.CRIMSON
        }
        return enemy
    }

    public function fill(color:Color) {
        enemy.fill = color;
    }

    function calcSpeed():Integer {
       var r = javafx.util.Math.random();
       if (r>0.66){
            return 3
        } else if (r<0.33) {
            return 2
        } else {
            return 1
        }
    }

    function calcRandomBoolean():Boolean {
        if (javafx.util.Math.random()>0.5)
            return true
        else
            return false
    }

    public function calcPosition(xMin:Integer,xMax:Integer,yMin:Integer,yMax:Integer) {
        if (moveUp) {
            var newY= translateY - speedY;
            if (newY > yMin + radius)
                translateY = newY
            else
                moveUp = false
        } else {
            var newY= translateY + speedY;
            if (newY < yMax - radius)
                translateY = newY
            else
                moveUp = true
        }
        if (moveLeft) {
            var newX = translateX - speedX;
            if (newX > xMin + radius)
                translateX = newX
            else
                moveLeft = false
        } else {
            var newX = translateX + speedX;
            if (newX < xMax - radius)
                translateX = newX
            else
                moveLeft=true
        }
    }
}

Creating a Simple Game in JavaFX (Part 6)

Tuesday, October 27th, 2009

So far we have a working game but we are still missing an essential ingredient: score keeping.

Displaying the Score


...
var scoreTime = 0;
var scoreMoves = 0;
var scoreBonus = 0;
var scoreTotal = 0;
var scoreHigh = 0;

var scoreArea: Group = Group {

    content : [
        Rectangle {
            arcWidth: 20  arcHeight: 20
            x: 290 y: 0
            width: 120, height: 200
            fill: Color.LIGHTGOLDENRODYELLOW
            stroke: Color.DARKORANGE
            strokeWidth: 3
        },
        Text {
            font : Font {
                size: 24
            }
            layoutX: 320 layoutY: 30
            content: "Score"
        },
        Tile {
            columns: 2
            rows: 5
            hgap: 5
            vgap: 5
            tileWidth: 50
            hpos: HPos.LEADING
            layoutX: 290 layoutY: 50
            content: [
                Text {
                    font : Font {
                        size: 12
                    }
                    content: "Time:"
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: bind scoreTime.toString()
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: "Moves:"
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: bind scoreMoves.toString()
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: "Bonus:"
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: bind scoreBonus.toString()
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: "Total:"
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: bind scoreTotal.toString()
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: "High:"
                },
                Text {
                    font : Font {
                        size: 12
                    }
                    content: bind scoreHigh.toString()
                }
            ]
        }
    ]
}

Stage {
    ...
    scene: Scene {
        ...
        content: Group {
            ...
            content: [
                playingField,
                player,
                enemies,
                gameOverScreen,
                scoreArea
            ]
        }
    }
}

At this point you should be comfortable with the new code above. Nonethless there are two new aspects that we have not covered earlier. I will discuss them as we go through the code.
At the top I have added different variables for the different types of score keeping. ScoreTime keeps the score collected for running of the game. With each second passed the player automatically receives points. ScoreMoves collects the score when the player is moving. When the player is not moving, points will be deducted from scoreMoves. ScoreBonus is the score received when a player touches an enemy in bonus mode. Not touching this enemy will result in points being deducted from the scoreBonus score. Finally the total score adds up all the scores mentioned above and the high score persists the highest achieved score for the duration of the applications life. This means if you close the game, the high score is lost. This behaviour is of course not desirable, and would need fixing in future versions of the game.

The scoreArea is a familiar Group object that encompasses a Rectangle which is the background of the score area, a title object and then a new type of object called a Tile. A tile is one of several layout objects added to JavaFX in version 1.2 and is basically a grid in our case of two columns and 5 rows. All tiles or cells are of the same size. The objects are added in the content[] sequence, with the sequence position determining which position in the grid the object will have.

All we are adding to the Tile object are Text objects. The first Text object of each row is the label and the second Text object is the corresponding score. Now for the second new aspect. The value of the score is defined as bind scoreTotal.toString(). What this does is bind the value of the Text object to the value of the scoreTotal variable (as String (.toString())). This is an incredibly simple way of having your score Text object updated each time the score changes. No need for extra code, extra listeners etc. etc.

Don’t forget to add the new scoreArea to the main Group.

Our game screen now looks like this:

img 5

Adding Time Score

Now that we have the graphics in place lets calculate the time score.


                ...
                // checkCollision
                for (enemy in enemies) {
                    if (checkCircleCollision(player.translateX,player.translateY,player.radius,enemy.translateX,enemy.translateY,10.0)) {
                        gameOver();
                    } else {
                        calculateNewScore();
                    }

                }
            }
        }
    ]
}

function calculateNewScore() {
    scoreTime += 5;
    scoreTotal = scoreTime + scoreMoves + scoreBonus;

}

As always it is in our game play Timeline object that score calculations take place. After we have checked that no collision with enemy players has taken place we call the method calculateNewScore(). Notice that we do not only update the scoreTime variable with 5 points every cycle but that we are already calculating the total score which is made up of scoreTime, scoreMoves, scoreBonus.
Also notice that just as we have pointed out before, through the binding mechanism, once we update the scoreTime and totalScore variables, the corresponding text objects are also updated.

There is still a little bug in the above code. Each time you reach game over, the score is not reset. Lets make the necessary changes to start each game with a fresh new score:


function initGame():Void {
    initActors();
    initScore();
    mode = MODE_RUNNING;
    gameOverScreen.visible = false
}

function initScore() {
    scoreBonus = scoreMoves = scoreTime = scoreTotal = 0
}

All it took was a small change in initGame() and a new function called initScore().

Adding Move Score


var timeline: Timeline = Timeline {
    ...
    keyFrames : [
        KeyFrame {
            ...
            action: function() {
                var playerMoved = false;
                ...
                // Player
                if (moveRight) {
                    var newX = player.translateX + PLAYER_BALL_STEP;
                    if (newX < playingField.boundsInLocal.maxX - player.radius - playingField.strokeWidth) {
                        playerMoved = true;
                        player.translateX = newX
                    }
                }
                if (moveLeft) {
                    var newX = player.translateX - PLAYER_BALL_STEP;
                    if (newX > playingField.boundsInLocal.minX + player.radius + playingField.strokeWidth) {
                        playerMoved = true;
                        player.translateX = newX
                    }
                }
                if (moveUp) {
                    var newY= player.translateY - PLAYER_BALL_STEP;
                    if (newY > playingField.boundsInLocal.minY + player.radius + playingField.strokeWidth) {
                        playerMoved = true;
                        player.translateY = newY
                    }
                }
                if (moveDown) {
                    var newY= player.translateY + PLAYER_BALL_STEP;
                    if (newY < playingField.boundsInLocal.maxY - player.radius - playingField.strokeWidth) {
                        playerMoved = true;
                        player.translateY = newY
                    }
                }
                // checkCollision
                for (enemy in enemies) {
                    if (checkCircleCollision(player.translateX,player.translateY,player.radius,enemy.translateX,enemy.translateY,10.0)) {
                        gameOver();
                    } else {
                        calculateNewScore(playerMoved);
                    }

                }
            }
        }
    ]
}
...
function calculateNewScore(addMoveScore:Boolean) {
    scoreTime += 5;
    scoreMoves = if (addMoveScore) scoreMoves+5 else scoreMoves-10;
    scoreTotal = scoreTime + scoreMoves + scoreBonus;

}

Calculating a move score only requires very subtle changes. In the main loop we introduce a variable called playerMoved of type Boolean. If a new position is calculated for our player object, then playerMoved is set to true.
We have also updated the function calculateNewScore to accept a parameter indicating whether the player has moved or not. If the player has moved we add 5 points to the move score otherwise we subtract 10 points.

Adding Bonus Score

Adding the bonus score is a bit trickier. Here are the code changes in Main.fx:


var bonusTimeline: Timeline = Timeline {
    repeatCount: 1
    keyFrames : [
        KeyFrame {
            time : 3s
            canSkip: false
        }
    ]
}
...
var timeline: Timeline = Timeline {
    ...
    keyFrames : [
        KeyFrame {
            ...
            action: function() {
                var playerMoved = false;

                calculateBonusEnemy();
                for (enemy in enemies) {
                    if (checkCircleCollision(player.translateX,player.translateY,player.radius,enemy.translateX,enemy.translateY,10.0)) {
                        if (not enemy.bonusMode) {
                            gameOver();
                        } else {
                            calculateNewScore(playerMoved,true);
                        }
                    } else {
                        calculateNewScore(playerMoved);
                    }
                }
            }
        }
    ]
}
function calculateNewScore(addMoveScore:Boolean,addBonusScore:Boolean) {
    scoreBonus += 1000;
    calculateNewScore(addMoveScore);
}
function calculateBonusEnemy() {
    var bEnemy:Enemy = null;
    for (enemy in enemies) {
        if (enemy.bonusMode) {
            bEnemy = enemy;
        }
    }

    // None Bonus
    if (bEnemy==null) {
        if (javafx.util.Math.random()>0.95) {
            // Change one to bonus
            var i = (javafx.util.Math.ceil( enemies.size()*javafx.util.Math.random() )).intValue();
            println("changing to bonus {i}");
            enemies[i].fill(Color.CYAN);
            enemies[i].bonusMode = true;
            bonusTimeline.playFromStart();
        }
    } else {
        // One Bonus
        if (not bonusTimeline.running) { // min 3s in Bonus
            if (javafx.util.Math.random()>0.5) { // Turn off bonus
                bEnemy.fill(Enemy.DEFAULT_FILL);
                bEnemy.bonusMode = false;
                if (not bEnemy.bonusWasHit) {
                    scoreBonus -= 5000
                }
                bEnemy.bonusWasHit = false
            } else {
                // prolong bonus
                bonusTimeline.playFromStart();
            }

       }
    }
}
...

And the code changes in Enemy.fx:


...
public var bonusMode = false;
public var bonusWasHit = false; // Player hit in Bonus Mode
...
public function fill(color:Color) {
    enemy.fill = color;
}

In Enemy.fx all we do is make our Enemy objects aware that they could be in bonus mode (bonusMode) and whether they were hit in bonus mode (bonusWasHit). Furthermore we add fill function that allows us to change the fill color of the enemy objects.

The bigger changes are in Main.fx. In each cycle we now have a function calculateBonusEnemy() being called. This method first checks if an enemy is in bonus mode. If no enemy is in bonus mode it randomly decides to change one enemy to bonus mode (if random number > 0.95). Which enemy is set to bonus mode is also randomly determined. The bonus enemy gets its color changed and the bonusMode variable set to true. Furthermore a bonusTimeline is played which serves the sole purpose of running for three seconds.

If no enemy is in bonus mode (not bonusTimeline.running) there is a 50/50 chance that the bonus mode will be turned off or that the bonus mode will be prolonged. When turning of the bonus mode we also check if the enemy was hit during its bonus time and if not, 5000 points are subtracted from the bonus score. It pays to hit enemies in bonus mode.

The other changes in Main.fx are quite simple. If a player collides with a bonus enemy, 1000 points are added to the players score for each cycle.

Adding High Score


// Function called after collision
function gameOver():Void {
    timeline.stop();
    mode = MODE_GAME_OVER;
    gameOverScreen.visible = true;
    if (scoreTotal > scoreHigh)
        scoreHigh = scoreTotal;
}

Adding the High Score functionality is as simple as checking if the current total score when the gameOver() function is called is higher than the previous High Score.

We're done! We now have the final version of our game. Please check part 7 for some final thoughts, a functioning version of our game and the complete tutorial code.

Creating a Simple Game in JavaFX (Part 5)

Thursday, October 22nd, 2009


We now have a player object we can control and we have enemy objects moving around the playing field. What we are still missing is code to deal with enemy player collisions and score keeping. This article will deal with calculating collisions.

Calculating Collisions

Once again it is in the Timeline responsible for managing our play cycle where we need to check for collisions.


var timeline: Timeline = Timeline {
    ...
    keyFrames : [
        KeyFrame {
            ...
            action: function() {
                // move enemy and player to new positions
                ...
                // checkCollision
                for (enemy in enemies) {
                    if (checkCircleCollision(player.translateX,player.translateY,player.radius,enemy.translateX,enemy.translateY,10.0)) {
                        gameOver();
                    }
                }
            }
        }
    ]
}

// Function called after collision
function gameOver():Void {
    timeline.stop();
}

function checkCircleCollision(c1X:Number,c1Y:Number,c1R:Number,c2X:Number,c2Y:Number,c2R:Number):Boolean {
    var distanceSquared = ((c1X - c2X) * (c1X - c2X)) +  ((c1Y - c2Y) * (c1Y - c2Y));
    var radiiSquared = (c1R + c2R) * (c1R + c2R);

    if (radiiSquared > distanceSquared) {
        return true
    }

    return false
}

After we have moved our enemies and player to new positions we need to check in each game play cycle, if they have collided. This is done in the function checkCircleCollision(…). The parameters of this function are the x and y coordinates and the radius of the player and of one enemy circle. More details about the mathematics of calculating such a collision can be found here: http://gpwiki.org/index.php/C:Collision_detection_between_two_circles.

If the player collides with any of the enemy objects the method gameOver() is called and gameplay is stopped by halting the Timeline object responsible for our game play.

Restarting the Game after Game Over

Of course we would like to let players of our game restart the game without reloading or restarting our JavaFX application. These are the necessary code changes:



def MODE_GAME_OVER: String = "GAME_OVER";
def MODE_RUNNING:   String = "RUNNING";

var mode:String = MODE_RUNNING;

var gameOverScreen: Group = Group{
    visible:false
    var r:Rectangle = Rectangle {
        arcWidth: playingField.arcWidth  arcHeight: playingField.arcHeight
        width: playingField.width, height: playingField.height
        fill: Color.ROSYBROWN
        stroke: Color.DARKRED
        strokeWidth: playingField.strokeWidth
    }
    content: [
        r,
        Text {
            font : Font {
                size: 24
            }
            x: 10, y: 50
            content: "Game Over"
        },
        Text {
            font : Font {
                size: 20
            }
            x: 10, y: 80
            content: "(press Enter to restart)"
        }
   ]
}

Stage {
    ...
    scene: Scene {
        ...
        content: Group {
            ...
            content: [
                playingField,
                player,
                enemies,
                gameOverScreen
            ]
            onKeyPressed : function (e: KeyEvent){
              if (mode==MODE_GAME_OVER) {
                if (e.code == KeyCode.VK_ENTER) {
                    initGame();
                    timeline.playFromStart();
                }
              }
           }
...
}
// Function called after collision
function gameOver():Void {
    timeline.stop();
    mode = MODE_GAME_OVER;
    gameOverScreen.visible = true;
}

function initGame():Void {
    initActors();
    mode = MODE_RUNNING;
    gameOverScreen.visible = false
}

// Function is used to initialize the actors i.e. player and enemies
// on the playing field
function initActors() {
    player.translateX = 250;
    player.translateY = 250;
    for (enemy in enemies) {
        enemy.translateX = randomEnemyInitPosition();
        enemy.translateY = randomEnemyInitPosition();
    }
}

This code is easy to understand. We introduce a variable called “mode” and define two constants MODE_GAME_OVER and MODE_RUNNING. These are the two states that are possible in our game.
We then define a Game Over screen which is a simple rectangle which is the same size as our playing field and whose visibility property is initially set to “false”. Remember that the game starts running, so we do not want to see the game over screen.
We then add the gameOverScreen object to the visible elements of our Scene. Notice how it gets added as the last object. This is necessary because once the gameOverScreen becomes visible it should appear on top of everything else.
Notice how in the gameOver() function we now set the mode to MODE_GAME_OVER.
In our KeyListener we now check if the mode of our game is MODE_GAME_OVER if so and the ENTER key has been pressed we reinitialize the game by calling the init() function. The init() function resets the player and enemy positions, changes the state or mode of our game to MODE_RUNNING and hides the game over screen. Finally our main game loop is restarted by calling timeline.playFromStart().

That concludes this part of our tutorial. In the next part we will add all of the scoring functionality. Below is a listing of the complete code so far:

Main.fx


/*
 * 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;

import javafx.scene.text.Font;
import javafx.scene.text.Text;

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

def MODE_GAME_OVER: String = "GAME_OVER";
def MODE_RUNNING:   String = "RUNNING";

var mode:String = MODE_RUNNING;

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 gameOverScreen: Group = Group{
    visible:false
    var r:Rectangle = Rectangle {
        arcWidth: playingField.arcWidth  arcHeight: playingField.arcHeight
        width: playingField.width, height: playingField.height
        fill: Color.ROSYBROWN
        stroke: Color.DARKRED
        strokeWidth: playingField.strokeWidth
    }
    content: [
        r,
        Text {
            font : Font {
                size: 24
            }
            x: 10, y: 50
            content: "Game Over"
        },
        Text {
            font : Font {
                size: 20
            }
            x: 10, y: 80
            content: "(press Enter to restart)"
        }
   ]
}

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

var enemies : Enemy[] = for (i in [0..3]) {
    var enemy:Enemy = Enemy {
        translateX : randomEnemyInitPosition(), translateY:randomEnemyInitPosition()
    }
    enemy
};

var timeline: Timeline = Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames : [
        KeyFrame {
            time : 30ms
            canSkip: false
            action: function() {
                // Enemy
                for (enemy in enemies)
                    enemy.calcPosition(playingField.boundsInLocal.minX,playingField.boundsInLocal.maxX,playingField.boundsInLocal.minY,playingField.boundsInLocal.maxY);

                // 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
                    }
                }
                                // checkCollision
                for (enemy in enemies) {
                    if (checkCircleCollision(player.translateX,player.translateY,player.radius,enemy.translateX,enemy.translateY,10.0)) {
                        gameOver();
                    }
                }
            }
        }
    ]
}

// Function called after collision
function gameOver():Void {
    timeline.stop();
    mode = MODE_GAME_OVER;
    gameOverScreen.visible = true;
}

function initGame():Void {
    initActors();
    mode = MODE_RUNNING;
    gameOverScreen.visible = false
}

// Function is used to initialize the actors i.e. player and enemies
// on the playing field
function initActors() {
    player.translateX = 250;
    player.translateY = 250;
    for (enemy in enemies) {
        enemy.translateX = randomEnemyInitPosition();
        enemy.translateY = randomEnemyInitPosition();
    }
}
// http://gpwiki.org/index.php/C:Collision_detection_between_two_circles
function checkCircleCollision(c1X:Number,c1Y:Number,c1R:Number,c2X:Number,c2Y:Number,c2R:Number):Boolean {
    var distanceSquared = ((c1X - c2X) * (c1X - c2X)) +  ((c1Y - c2Y) * (c1Y - c2Y));
    var radiiSquared = (c1R + c2R) * (c1R + c2R);

    if (radiiSquared > distanceSquared) {
        return true
    }

    return false
}

function randomEnemyInitPosition():Integer {
    var r = javafx.util.Math.random();
    var r2 = javafx.util.Math.random();
    ((r  * 100)+(r2*100)) as Integer
}

Stage {
    title: "Ballgame"
    scene: Scene {
        fill: Color.CHOCOLATE;
        width: 430, height: 400
        content: Group {
            focusTraversable: true
            translateX: 10, translateY:10
            content: [
                playingField,
                player,
                enemies,
                gameOverScreen
            ]
            onKeyPressed : function (e: KeyEvent){
              if (mode==MODE_GAME_OVER) {
                if (e.code == KeyCode.VK_ENTER) {
                    initGame();
                    timeline.playFromStart();
                }
              }
              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();

Enemy.fx


/*
 * Enemy.fx
 *
 * Created on 03.10.2009, 13:59:33
 */

package myballgame;

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

/**
 * @author Alexander Gnodtke
 */

public static def DEFAULT_FILL = Color.CRIMSON;

public class Enemy extends CustomNode {

    var radius = 10;
    var moveUp = false;
    var moveLeft = false;
    var speedX = 0;
    var speedY = 0;

    var enemy:Circle;

    override function create():Node {

        // Enemy Speed
        speedX = calcSpeed();
        speedY = calcSpeed();

        // Enemy initial Direction
        moveUp = calcRandomBoolean();
        moveLeft = calcRandomBoolean();

        // Enemy figure
        enemy = Circle {
            radius: radius
            fill: Color.CRIMSON
        }
        return enemy
    }

    function calcSpeed():Integer {
       var r = javafx.util.Math.random();
       if (r>0.66){
            return 3
        } else if (r<0.33) {
            return 2
        } else {
            return 1
        }
    }

    function calcRandomBoolean():Boolean {
        if (javafx.util.Math.random()>0.5)
            return true
        else
            return false
    }

    public function calcPosition(xMin:Integer,xMax:Integer,yMin:Integer,yMax:Integer) {
        if (moveUp) {
            var newY= translateY - speedY;
            if (newY > yMin + radius)
                translateY = newY
            else
                moveUp = false
        } else {
            var newY= translateY + speedY;
            if (newY < yMax - radius)
                translateY = newY
            else
                moveUp = true
        }
        if (moveLeft) {
            var newX = translateX - speedX;
            if (newX > xMin + radius)
                translateX = newX
            else
                moveLeft = false
        } else {
            var newX = translateX + speedX;
            if (newX