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:
[youtube]Xv5z-9LGuOc[/youtube]
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.
[youtube]k-MHh6irvwE[/youtube]
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
It’s great you resumed your bloging on more “real” JavaFX apps !
Keep them going…. All the demos so far are JUST GREAT examples of JavaFX power …!
It seams there are some issues with the applet (can’t load at least here …and stay in a “continuous applet loading” animation)… all the other JavaFX samples (from javafx.com) works just fine …
Just an idea:
Woldn’t be nice (and a challenge !) to create a gallery of JavaFX equivalent samples for the ones (Flash & Silverlight) found here:
Flash vs Silverlight Gallery
http://www.shinedraw.com/flash-vs-silverlight-gallery/
The applet version doesn’t work
java.io.FileNotFoundException: JNLP not available: Nerdy_browser.jnlp
at sun.plugin2.applet.JNLP2Manager.loadJarFiles(JNLP2Manager.java:387)
at sun.plugin2.applet.Plugin2Manager$AppletExecutionRunnable.run(Plugin2Manager.java:1332)
at java.lang.Thread.run(Thread.java:619)
Exception: java.io.FileNotFoundException: JNLP not available: Nerdy_browse
Muito bom! 😀
Não consegui rodar nem via JWS nem applet:
#### Unable to load resource: file:/home/silveira/Documentos/jfx/nerdy/dist/Nerdy_browser.jnlp
Please please fix it soon.
Great job guy!
Very interesting
Really great.
Exactly that what I was looking for.
Thanks alot
The funnies and coolest tutorial I’ve read in a long time.
Man… this is just very cool! your blog rocks!!! I’m writing a thesis something about JavaFX games. This blog will help me a lot…
Silveira voce nao sabe como eu estou depois de ver esse projeto
d rpg eu não tenho muito conhescimento na area mas estou com um projeto de um jogo p celular e se vc vc postar um exemplo não só eu como muitas pessoas te amariam eternameste
rsrsrsrs please answer
c u
Para quem tentou compilar o código usando Javafx 1.2 e o sprite não saiu do lugar:
Node não pode receber foco por padrão e o key handler não é exigido por Node para ficar focado havendo duas excessões: Control e SwingComponent são focusable por default. A nova variável focusTraversable foi incluida e é um boolean. A variável focusTraversable especifica quando o Node deve fazer parte de um “ciclo transversal de foco” (focus traversal cycle).
[http://javafx.com/docs/articles/javafx1-2.jsp#keyboard]
Simplificando: é só adicionar focusTraversable:true na var player.
E para quem já conhece javaFx 1.0 ou 1.1 vale a pena olhar o artigo acima.
Bons estudos.
Obrigado pela ótima dica Diogo! o/
[…] 8th, 2008JavaFX, how to create a rpg like game Posted by Silveira in english JavaFX 1.0 is out and there are tons of new cool features, […]
I made a game with another background and the stormtrooper.. It was cool..
But I would really like if you made a part 2 with some new functions and maps 🙂
– CJ
i downloaded your source code for RPG like game, but unfortunately, it doesn’t work. Although i have a the latest update of netbeans and javafx (netbeans 6.9.1 and javafx 1.3.1) . Do i need to do anything special because of the newer version of javafx and netbeans.
Thanks
Hi, Congratuliations for your work. I think the javafx was development to make the things easier; I know the code is the base of this, but you can save many time using the tools of javafx to draw and manage sprites like you can do it in macromedia flash for example. ¿Why dont make a tutorial but using this tools and less code (I know is imposible do it without any code )? Ive said again, your work is great you are the expert, Im a begginer, but I think that is the porpouse of the javafx. ¿What do you think? Thanks for all
Miss this part of the code “focusTraversable:true” like this way you can move the Nerdy, without this you cant.
You need to write it right here:
var player = ImageView{
focusTraversable:true
………and the rest code th same
Greetings
PORQUE O CHAR NÃO QUER ANDAR EU FIZ TUDO CERTO?
Great tutorial!! I did a little clone, but my player don’t walk 🙁 don’t know what to do!
Olá Mr. Silveira! ^^
Cara, eu estou querendo fazer exatamente a mesma coisa que você fez so que em Java SE.
Viajei legal no Java FX, mto diferente esse negocio de linguagem declarativa.
Vc poderia me dar uns toques de como fazer algo similar?
Obrigado.
Aproposito, mto bom o seu post! haha serio msm ^^
fun flash games…
[…]JavaFX, how to create a rpg like game « Silveira Neto[…]…
Hello, just wanted to say what a great post this is.
I’m looking for inspiration and ideas on a project I’m doing as part of my degree. I’ve got to create an educational JavaFX game. I was wondering if you would mind if I used some of the concepts you’ve employed here(obviously not blatantly rip off your example). But the top down view and a similar art style?
I’ll be following this blog from now on!
Hi Alex, all my content is under Creative Creative Commons Attribution-ShareAlike 2.5 License, unless explicitly stated. Yes you can copy content, just take a minute to read and understand the license.
Can I compilate this code in java 1.8 (last version) ?
No.