the world is a pixel
Posts tagged Java Web Start
JavaFX, how to create a rpg like game
Dec 8th
JavaFX 1.0 is out and there are tons of new cool features, specially for game development.trans
I’ll show in this tutorial how to create a very simple demo that shows how to load imtrages, handle sprites, collisions and keyboard events that you can use to create a game with a old school rpg like vision.
For the background scenario I’m using the house that I drew and we’ll call as house.png.
That we load as a Image and place into a ImageView.
ImageView{ image: Image {url: "{__DIR__}house.png"} }
For the character I’m using the last character I drew, the nerdy guy.

To make the animation easier, I spited it into 9 pieces:
down0.png, down1.png and down2.png
left0.png, left1.png and left2.png
right0.png, right1.png and righ2.png
up0.png, up1.png and up2.png
All images I’m using should be in the same directory of source code.
Let’s start loading the scenario and a single character sprite.
import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.image.*; Stage { title: "RPG-like demo", width: 424, height: 412 visible: true scene: Scene{ content: [ ImageView{ image: Image {url: "{__DIR__}house.png"} }, ImageView{ x: 320 y: 80 image: Image {url: "{__DIR__}down1.png"} } ] } }
Saved as Game.fx you can compile and run with in your terminal:
$ javafxc Game.fx
$ javafx Game
Hint: You can use NetBeans 6.5 JavaFX plugin to easier the JavaFX development.
To put animation on the character we load all sprites into four lists. Each list for each direction.
// sprites def up = for(i in [0..2]) { Image {url: "{__DIR__}up{i}.png" } } def right = for(i in [0..2]) { Image {url: "{__DIR__}right{i}.png" } } def down = for(i in [0..2]) { Image {url: "{__DIR__}down{i}.png" } } def left = for(i in [0..2]) { Image {url: "{__DIR__}left{i}.png" } }
And create vars to store the character position and frame of animation.
var frame = 0; var posx = 320; var posy = 80;
Also store the house background.
// house background
def house = ImageView{ image: Image {url: "{__DIR__}house.png"} };
I create booleans to store some key states and at each interval of time I see how they are and do something about. You can handle keyboard event with less code but I like this way because keep visual and game logics a little bit more separated.
// keyboard var upkey = false; var rightkey = false; var downkey = false; var leftkey = false; // player var player = ImageView{ x: bind posx y: bind posy image: Image {url: "{__DIR__}down1.png"} onKeyPressed: function(e:KeyEvent){ if (e.code == KeyCode.VK_DOWN) { downkey = true; } else if (e.code == KeyCode.VK_UP) { upkey = true; }else if (e.code == KeyCode.VK_LEFT) { leftkey = true; }else if (e.code == KeyCode.VK_RIGHT) { rightkey = true; } } // onKeyPressed onKeyReleased: function(e: KeyEvent){ if (e.code == KeyCode.VK_DOWN) { downkey = false; } else if (e.code == KeyCode.VK_UP) { upkey = false; }else if (e.code == KeyCode.VK_LEFT) { leftkey = false; }else if (e.code == KeyCode.VK_RIGHT) { rightkey = false; } } // onKeyReleased }
See a video of the game working so far:
Now we will add collisions. In a previous post I showed some math behind bounding box game collisions. The good news are that you no longer need to worry about that. There are a lot of API improvements in JavaFX 1.0 that do all the hard work for you, specially the new classes on javafx.geometry package, Rectangle2D and Point2D.
We create rectangles that represent the obstacles in the house.
// collidable obstacles def obstacles = [ Rectangle { x: 0 y: 0 width: 32 height: 382 stroke: Color.RED }, Rectangle { x: 0 y: 0 width: 414 height: 64 stroke: Color.RED }, Rectangle { x: 384 y: 0 width: 32 height: 382 stroke: Color.RED }, Rectangle { x: 0 y: 192 width: 128 height: 64 stroke: Color.RED }, Rectangle { x: 192 y: 192 width: 64 height: 64 stroke: Color.RED }, Rectangle { x: 224 y: 0 width: 32 height: 288 stroke: Color.RED }, Rectangle { x: 288 y: 128 width: 96 height: 64 stroke: Color.RED }, Rectangle { x: 0 y: 352 width: 128 height: 32 stroke: Color.RED }, Rectangle { x: 192 y: 352 width: 192 height: 32 stroke: Color.RED }, Rectangle { x: 224 y: 320 width: 32 height: 32 stroke: Color.RED }, Rectangle { x: 32 y: 64 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 64 y: 64 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 96 y: 64 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 128 y: 64 width: 64 height: 32 stroke: Color.YELLOW }, Rectangle { x: 192 y: 32 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 64 y: 128 width: 64 height: 32 stroke: Color.YELLOW }, Rectangle { x: 32 y: 250 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 64 y: 250 width: 64 height: 32 stroke: Color.YELLOW }, Rectangle { x: 200 y: 255 width: 20 height: 20 stroke: Color.YELLOW }, Rectangle { x: 200 y: 170 width: 20 height: 20 stroke: Color.YELLOW }, Rectangle { x: 257 y: 32 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 288 y: 32 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 320 y: 192 width: 64 height: 64 stroke: Color.YELLOW }, Rectangle { x: 352 y: 295 width: 32 height: 60 stroke: Color.YELLOW }, Rectangle { x: 32 y: 327 width: 64 height: 23 stroke: Color.YELLOW }, ];

We just have to change a little bit the game logics in order to handle collisions.

We define a bounding box around the player, it’s a rectangle from (4, 25) at the player coordinates system and with width 19 and height 10. The idea is to prospect where the player will be in the next step, see if it’s bouding box don’t collide with any obstacle and so pass it to the real game position.
// game logics var gamelogics = Timeline { repeatCount: Timeline.INDEFINITE keyFrames: KeyFrame { time : 1s/8 action: function() { var nextposx = posx; var nextposy = posy; if(downkey) { nextposy += 5; player.image = down[++frame mod 3]; } if(upkey) { nextposy -= 5; player.image = up[++frame mod 3]; } if(rightkey) { nextposx += 5; player.image = right[++frame mod 3]; } if(leftkey) { nextposx -= 5; player.image = left[++frame mod 3]; } for(obst in obstacles) { if(obst.boundsInLocal.intersects(nextposx + 4, nextposy + 25, 19, 10)) { return; } } posx = nextposx; posy = nextposy; } } }
This is enough to do the trick but I also added a way to smoothly show the obstacles when pressing the space key.
Here is the complete source code.
package Game; import javafx.stage.Stage; import javafx.scene.*; import javafx.scene.image.*; import javafx.scene.input.*; import javafx.scene.paint.*; import javafx.scene.shape.*; import javafx.animation.*; var frame = 0; var posx = 320; var posy = 80; // sprites def up = for(i in [0..2]) { Image {url: "{__DIR__}up{i}.png" } } def right = for(i in [0..2]) { Image {url: "{__DIR__}right{i}.png" } } def down = for(i in [0..2]) { Image {url: "{__DIR__}down{i}.png" } } def left = for(i in [0..2]) { Image {url: "{__DIR__}left{i}.png" } } // house background def house = ImageView{ image: Image {url: "{__DIR__}house.png"} }; // keyboard var upkey = false; var rightkey = false; var downkey = false; var leftkey = false; // player var player = ImageView{ x: bind posx y: bind posy image: down[1] onKeyPressed: function(e:KeyEvent){ if (e.code == KeyCode.VK_DOWN) { downkey = true; } else if (e.code == KeyCode.VK_UP) { upkey = true; }else if (e.code == KeyCode.VK_LEFT) { leftkey = true; }else if (e.code == KeyCode.VK_RIGHT) { rightkey = true; } if(e.code == KeyCode.VK_SPACE){ if(fade==0.0){ fadein.playFromStart(); } if(fade==1.0){ fadeout.playFromStart(); } } } // onKeyPressed onKeyReleased: function(e: KeyEvent){ if (e.code == KeyCode.VK_DOWN) { downkey = false; } else if (e.code == KeyCode.VK_UP) { upkey = false; }else if (e.code == KeyCode.VK_LEFT) { leftkey = false; }else if (e.code == KeyCode.VK_RIGHT) { rightkey = false; } } // onKeyReleased } // collidable obstacles def obstacles = [ Rectangle { x: 0 y: 0 width: 32 height: 382 stroke: Color.RED }, Rectangle { x: 0 y: 0 width: 414 height: 64 stroke: Color.RED }, Rectangle { x: 384 y: 0 width: 32 height: 382 stroke: Color.RED }, Rectangle { x: 0 y: 192 width: 128 height: 64 stroke: Color.RED }, Rectangle { x: 192 y: 192 width: 64 height: 64 stroke: Color.RED }, Rectangle { x: 224 y: 0 width: 32 height: 288 stroke: Color.RED }, Rectangle { x: 288 y: 128 width: 96 height: 64 stroke: Color.RED }, Rectangle { x: 0 y: 352 width: 128 height: 32 stroke: Color.RED }, Rectangle { x: 192 y: 352 width: 192 height: 32 stroke: Color.RED }, Rectangle { x: 224 y: 320 width: 32 height: 32 stroke: Color.RED }, Rectangle { x: 32 y: 64 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 64 y: 64 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 96 y: 64 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 128 y: 64 width: 64 height: 32 stroke: Color.YELLOW }, Rectangle { x: 192 y: 32 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 64 y: 128 width: 64 height: 32 stroke: Color.YELLOW }, Rectangle { x: 32 y: 250 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 64 y: 250 width: 64 height: 32 stroke: Color.YELLOW }, Rectangle { x: 200 y: 255 width: 20 height: 20 stroke: Color.YELLOW }, Rectangle { x: 200 y: 170 width: 20 height: 20 stroke: Color.YELLOW }, Rectangle { x: 257 y: 32 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 288 y: 32 width: 32 height: 32 stroke: Color.YELLOW }, Rectangle { x: 320 y: 192 width: 64 height: 64 stroke: Color.YELLOW }, Rectangle { x: 352 y: 295 width: 32 height: 60 stroke: Color.YELLOW }, Rectangle { x: 32 y: 327 width: 64 height: 23 stroke: Color.YELLOW }, ]; // game logics var gamelogics = Timeline { repeatCount: Timeline.INDEFINITE keyFrames: KeyFrame { time : 1s/8 action: function() { var nextposx = posx; var nextposy = posy; if(downkey) { nextposy += 5; player.image = down[++frame mod 3]; } if(upkey) { nextposy -= 5; player.image = up[++frame mod 3]; } if(rightkey) { nextposx += 5; player.image = right[++frame mod 3]; } if(leftkey) { nextposx -= 5; player.image = left[++frame mod 3]; } for(obst in obstacles) { if(obst.boundsInLocal.intersects(nextposx + 4, nextposy + 25, 19, 10)) { return; } } posx = nextposx; posy = nextposy; } } } gamelogics.play(); // obstacles view var fade = 0.0; var obstacleslayer = Group { opacity: bind fade content: [ Rectangle { x:0 y:0 width:500 height: 500 fill: Color.BLACK }, obstacles, Rectangle { x: bind posx + 4 y: bind posy + 25 width: 19 height: 10 fill: Color.LIME } ] } var fadein = Timeline { keyFrames: [ at (0s) {fade => 0.0} at (1s) {fade => 1.0} ] } var fadeout = Timeline { keyFrames: [ at (0s) {fade => 1.0} at (1s) {fade => 0.0} ] } // game stage Stage { title: "RPG-like demo", width: 424, height: 412 visible: true scene: Scene{ fill: Color.BLACK content: [house, player, obstacleslayer] } }
Play Through Java Web Start
or click here to play via applet, inside your browser.
update: The applet version and Java Web Start versions should be working now. The applet version on Linux seems to be having problems with the keyboard handling, use the Java Web Start version while I’m trying to fix it.
Downloads:
- Source, images and jars, nerdy.zip
- All content
- First video, javafx_nerdy_without_collision.ogv
- Second video, javafx_nerdy.ogv
JavaFX, Defuse the Bomb
Nov 6th
I continue to develop simple games demos to feel better the strengths and weakness of JavaFX for game development.
Preview:
Click to play via Java Web Start:
There’s a little JavaFX game demo where you have to transport a bomb to a defuse point without touching in the walls. I’m using the collision detection methods I described early in this post to detect when the bomb hits a wall and then explode or when a bomb is inside the defuse point and the game ends. As it’s only a demo, it’s just one single level, but adding more levels would be easy.
Basically we have this four images:
The code is petty simple. A little bit more than 300 lines with even with all comments and declarations. I transform the bomb image into a draggable node, create a list of collidable nodes and a especial node, the goal. I check the collisions when the bomb is dragged by mouse, if it hits something, it blows up.
I use extensively the TimeLine class from the animation framework (javafx.animation) to create chained animations and even to control some game logic.
As I focused in the simplicity, I don’t declared any classes to after instantiate their objects. I just was using common classes from JavaFX and putting logic on ir throught event and binding to external variables.
import javafx.application.Frame; import javafx.application.Stage; import javafx.animation.Timeline; import javafx.animation.KeyFrame; import javafx.animation.Interpolator; import javafx.scene.image.ImageView; import javafx.scene.image.Image; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.geometry.Circle; import javafx.scene.geometry.Rectangle; import javafx.scene.geometry.Shape; import javafx.scene.text.Text; import javafx.scene.Font; import javafx.scene.FontStyle; import javafx.input.MouseEvent; /* Fade variable modified in some animations and used in the fadescreen */ var fade = 0.0; /* The Bomb */ var lock = false; var tx = 0.0; var ty = 0.0; var bomb:Node = Group{ opacity: bind bombfade; content: [ ImageView { image: Image { url: "{__DIR__}/bomb.png" } }, Circle { centerX: 45, centerY: 21, radius: 7, fill: Color.LIME opacity: bind led }, Circle { centerX: 30, centerY: 30, fill: Color.WHITE radius: bind fireradius }, ], var startX = 0.0; var startY = 0.0; translateX: bind tx translateY: bind ty onMousePressed: function( e: MouseEvent ):Void { if (lock) {return;} startX = e.getDragX() - tx; startY = e.getDragY() - ty; } onMouseDragged: function(e:MouseEvent):Void { if (lock) {return;} tx = e.getDragX() - startX; ty = e.getDragY() - startY; checkcollissions(); } } /* Big rectangle that covers all the screen (bomb explosion or game end) */ var fadescreen = Rectangle { x: 0, y: 0, width: 640, height: 480, fill: Color.WHITE opacity: bind fade } /* The wood floor image for the scenario. */ var floor = ImageView { image: Image { url: "{__DIR__}/floor.png" } } /* The goal image where the bomb should be placed. */ var goal = ImageView { x: 470, y: 360 image: Image { url: "{__DIR__}/goal.png" } } /* List of obstacles nodes that the bomb can collide with. */ var obstacles = [ Rectangle { x: 120, y: 0, width: 100, height: 300, fill: Color.BLACK}, Rectangle { x: 350, y: 200, width: 100, height: 300, fill: Color.BLACK}, Rectangle { x: 370, y: 50, width: 50, height: 50, fill: Color.BLACK}, Rectangle { x: 250, y: 120, translateX: bind move, width: 100, height: 50 fill: Color.BLACK }, ]; /* Visible representations of obstacles */ var wallimage = Image { url: "{__DIR__}/wall.png" } var walls = for(obs in obstacles){ ImageView { x: obs.x, y: obs.y, translateX: bind obs.translateX clip: obs, image: wallimage } } /* Animation for a blinking green led */ var led = 0.0; var bombclock = Timeline { repeatCount: Timeline.INDEFINITE autoReverse: true keyFrames : [ KeyFrame { time : 0s values : led => 0.0 tween Interpolator.LINEAR }, KeyFrame { time : 1s values : led => 1.0 tween Interpolator.LINEAR } ] } /* Animation for the bomb explosion and game reset */ var fireradius = 0.0; var explosion:Timeline = Timeline { repeatCount: 1 keyFrames : [ KeyFrame { time : 0s values : [ fireradius => 0.0, fade => 0.0 ] }, KeyFrame { time : 2s values : [ fireradius => 200.0 tween Interpolator.LINEAR, fade => 1.0 tween Interpolator.LINEAR ] action: gamereset }, KeyFrame { time : 3s values: fade => 0.0 tween Interpolator.LINEAR }, ] } /* Reset variables for initial values */ function gamereset(){ lock = false; fireradius = 0.0; tx = 0.0; ty = 0.0; bombfade = 1.0; moveblock.start(); specialcollison.start(); bombclock.start(); } /* Animation when the bomb reaches the goal. Bomb disapear. */ var bombfade = 1.0; var bomdisapear = Timeline { repeatCount: 1 keyFrames : [ KeyFrame { time : 1s values: [ bombfade => 0.0 tween Interpolator.EASEBOTH, fade => 0.0 ] }, KeyFrame { time : 2s values: fade => 1.0 tween Interpolator.LINEAR; action: gamereset }, KeyFrame { time : 3s values: fade => 0.0 tween Interpolator.LINEAR; }, ] } /* Animation for a moving block. */ var move = 0.0; var moveblock = Timeline { repeatCount: Timeline.INDEFINITE autoReverse: true keyFrames : [ KeyFrame { time : 0s values : move => 0.0 }, KeyFrame { time : 3s values : move => 200.0 tween Interpolator.EASEBOTH }, ] } /* Check and handle possible collisions. */ function checkcollissions(): Void { if(checkobstacles()){ lock = true; specialcollison.stop(); moveblock.stop(); explosion.start(); } if (insidenode(bomb,goal)) { lock = true; moveblock.stop(); bomdisapear.start(); } } /* There was a bug, when the bomb is stopped, not been gragged, in front of the moving block, it could pass through it because checkcollissions() was only called on mouse moving. This make sure checking this special case. */ var specialcollison:Timeline = Timeline { repeatCount: Timeline.INDEFINITE keyFrames : [ KeyFrame { time : 1s/5 action: function(){ if(hitnode(obstacles[sizeof obstacles-1], bomb)){ lock = true; moveblock.stop(); explosion.start(); specialcollison.stop(); } } } ] } /* * The next four functions are for collision detection. * @See http://silveiraneto.net/2008/10/30/javafx-rectangular-collision-detection/ */ /* * Check collision between two rectangles. */ function collission(ax, ay, bx, by, cx, cy, dx, dy): Boolean { return not ((ax > dx)or(bx < cx)or(ay > dy)or(by < cy)); } /* * Check if the first rectangle are inside the second. */ function inside (ax, ay, bx, by, cx, cy, dx, dy):Boolean{ return ((ax > cx) and (bx < dx) and (ay > cy) and (by < dy)); } function hitnode(a: Node, b:Node): Boolean { return (collission( a.getBoundsX(), a.getBoundsY(), a.getBoundsX() + a.getWidth(), a.getBoundsY() + a.getHeight(), b.getBoundsX(), b.getBoundsY(), b.getBoundsX() + b.getWidth(), b.getBoundsY() + b.getHeight() )); } function insidenode(a:Node,b:Node):Boolean { return (inside( a.getBoundsX(), a.getBoundsY(), a.getBoundsX() + a.getWidth(), a.getBoundsY() + a.getHeight(), b.getBoundsX(), b.getBoundsY(), b.getBoundsX() + b.getWidth(), b.getBoundsY() + b.getHeight() )); } /* * Check collision of bomb against each obstacle. */ function checkobstacles(): Boolean{ for(obst in obstacles){ if (hitnode(obst, bomb)){ return true; } } return false; } /* Pack visual game elements in a Frame's Stage, unresizable. */ Frame { title: "Defuse the Bomb" width: 640 height: 480 resizable: false closeAction: function() { java.lang.System.exit( 0 ); } visible: true stage: Stage { content: bind [floor, goal, walls, bomb, fadescreen] } } /* Call gamereset to set initial values and start animations */ gamereset();
Downloads:
- High definition video, javafx_defuse.ogv.
- Entire project with sources and resources for NetBeans 6.1 with JavaFX pluggin, defuse.tar.bz2.
Apache and JNLP files
Nov 6th
To Apache Web Server correctly handles yours JNLP (Java Network Launch Protocol) files, modify or create a .htaccess file in the top directory of your web site and add the following:
AddType application/x-java-jnlp-file .jnlp AddType application/x-java-archive-diff .jardiff
Without these MIME-types, the user would see the xml jnlp file as a plain text in the browser. After that you can link to yours Java Web Start applications with a icon like this one:
JavaFX, game demo
Aug 24th
A simple demo using some techniques described in the post JavaFX side scrolling gaming.
Original ogg video: blackdot.ogg
Source (NetBeans project): BlackDot.tar.gz
Source (one file only): blackdot.fx
Java Web Start: launch.jnlp
















