Now is possible to create a tiny mall. With a few more ambients will be possible to create a entire little town.
And here the last entire tile set.
Booth, made with Gimp.
Now is possible to create a tiny mall. With a few more ambients will be possible to create a entire little town.
And here the last entire tile set.
Booth, made with Gimp.
Changelog:
Tile sets are a very simple way to draw scenarios with repeated elements. From simple to complex ones using a very low footprint.
First step, load the png file that stores the tileset into a Image. The file tiles.png shoud be in the same directory of the source code. I adjusted some tiles from those tile set I’ve blogged here before into a grid of 10×10 tiles.
var tileset = Image {
url: "{__DIR__}tiles.png"
}
Notice that each tile have 32 of height and 32 of width. We will assume this and use theses numbers when performing calculations to find a single tile in our tile set.
def w = 32;
def h = 32;
To display a Image in the screen we use a ImageView node. A ImageView can have a viewport property to create crop or zoom effect. A viewport is just a Rectangle2D, a object with position (minX and minY), height and width. If we want to display the first tile in the tileset we do
ImageView {
image: tileset
viewport: Rectangle2D{
minX: 0, minY: 0, height: 32, width: 32
}
}
Notice that the minX determines the column and minY the row in the tileset. The first row is 0*32, the second row is 1*32 and so on. If we want to display the tile at the second line and third column of the tileset we do
ImageView {
image: tileset
viewport: Rectangle2D{
minX: 2 * 32 , minY: 1*32, height: 32, width: 32
}
}
Those properties in a Rectangle2D are for init and read only. So I created a list with all Rectangles I can need for use as a viewport.
def viewports = for (row in [0..9]) {
for (col in [0..9]) {
Rectangle2D{
minX: col * w, minY: row * h, height: w, width: h
}
}
}
The scenario map is stored in another list. The first element of the list is 7, that is, the first tile in the scenario is the 7th tile from the tile set.
var map = [
7, 3, 3, 3, 3, 3, 3, 3, 3, 8,
19, 26, 40, 41, 24, 13, 13, 23, 24, 19,
19, 36, 50, 51, 34, 2, 2, 2, 34, 19,
19, 2, 2, 2, 2, 2, 2, 2, 25, 19,
19, 57, 58, 44, 45, 46, 2, 2, 35, 19,
27, 3, 3, 6, 55, 56, 5, 3, 3, 38,
19, 60, 13, 16, 47, 48, 15, 13, 61, 19,
19, 70, 1, 33, 1, 1, 1, 1, 71, 19,
19, 1, 1, 1, 1, 1, 1, 1, 49, 19,
17, 9, 9, 9, 9, 9, 9, 9, 9, 18,
];
Finally to create a scenario with 100 tiles, 10 per row and with 10 rows, in a list called tiles. Each iteration of this loop creates a ImageView. Each ImageView will store a single tile. We get the tile number in the map list and so use it to index the viewports list.
var tiles = for (row in [0..9]) {
for (col in [0..9]) {
ImageView {
x: col * w, y: row * h,
viewport: bind viewports[map[row * 10 + col]]
image: tileset
}
}
}
Additionally I added two things to transform this program also in a (extremely)Â simple map editor. At each ImageView I added a callback for onMouseClicked event. When you click on a tile, it changes its map position, ie, the tile. The next tile for the left button and the last tile for any other button.
onMouseClicked: function( e: MouseEvent ):Void {
var amount = if(e.button == MouseButton.PRIMARY) { 1 } else { -1 };
map[row * 10 + col] = (map[row * 10 + col] + amount) mod 100;
}
The other thing is to print the map list when the program is over. There is the full program:
package tileeditor;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.geometry.Rectangle2D;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.MouseButton;
def w = 32;
def h = 32;
var map = [
7, 3, 3, 3, 3, 3, 3, 3, 3, 8,
19, 26, 40, 41, 24, 13, 13, 23, 24, 19,
19, 36, 50, 51, 34, 2, 2, 2, 34, 19,
19, 2, 2, 2, 2, 2, 2, 2, 25, 19,
19, 57, 58, 44, 45, 46, 2, 2, 35, 19,
27, 3, 3, 6, 55, 56, 5, 3, 3, 38,
19, 60, 13, 16, 47, 48, 15, 13, 61, 19,
19, 70, 1, 33, 1, 1, 1, 1, 71, 19,
19, 1, 1, 1, 1, 1, 1, 1, 49, 19,
17, 9, 9, 9, 9, 9, 9, 9, 9, 18,
];
var tileset = Image {
url: "{__DIR__}tiles.png"
}
def viewports = for (row in [0..9]) {
for (col in [0..9]) {
Rectangle2D{
minX: col * w, minY: row * h, height: w, width: h
}
}
}
var tiles = for (row in [0..9]) {
for (col in [0..9]) {
ImageView {
x: col * w, y: row * h,
viewport: bind viewports[map[row * 10 + col]]
image: tileset
onMouseClicked: function( e: MouseEvent ):Void {
var amount = if(e.button == MouseButton.PRIMARY) { 1 } else { -1 };
map[row * 10 + col] = (map[row * 10 + col] + amount) mod 100;
}
}
}
}
Stage {
title: "JavaFX Simple Tile Editor"
scene: Scene {
content: [ tiles ]
}
onClose: function() {
println(map);
}
}
Here is the result for that map
And you can try it yourself in your browser. Play it online now.
Here is a video of it working
[youtube]lxuBEoItB5E[/youtube]
Downloads:
Possibilities
We are using just a image that can handle 100 tiles, tiles.png with less than 30Kb. The map is also composed with 100 tiles. Each tile we can choose between 100 different tiles, so we can compose 10100 different maps (one googol10 ). Most of them are useless and without any sense, but some are cool. 🙂
Myself in a 8bits pixelart style.
2x zoom
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:
Using the plain char from My Free Charset I drew this nerdy guy.
With 2x zoom:
The plain character I’m using to create new characters:
Changelog since the last version:
I’m trying to find a good style for outside building for this set. I tried some kinds of roofs but they are not good yet.
In a game I wrote some years ago we handled simple rectangular collisions. Given the points:
We did:
// returning 0 means collision
int collision(int ax, int ay, int bx, int by, int cx, int cy, int dx, int dy){
return ((ax > dx)||(bx < cx)||(ay > dy)||(by < cy));
}
I'll show here a little demo about how implement simple rectangular collisions on JavaFX.
First I created a movable rectangle using the same idea of draggable nodes I already had posted before.
import javafx.input.MouseEvent;
import javafx.scene.geometry.Rectangle;
public class MovableRectangle extends Rectangle {
private attribute startX = 0.0;
private attribute startY = 0.0;
public attribute onMove = function(e:MouseEvent):Void {}
override attribute onMousePressed = function(e:MouseEvent):Void {
startX = e.getDragX()-translateX;
startY = e.getDragY()-translateY;
onMove(e);
}
override attribute onMouseDragged = function(e:MouseEvent):Void {
translateX = e.getDragX()-startX;
translateY = e.getDragY()-startY;
onMove(e);
}
}
In the main code I some important things:
Here is the main code:
import javafx.application.Frame;
import javafx.application.Stage;
import javafx.scene.geometry.Rectangle;
import javafx.scene.paint.Color;
import javafx.input.MouseEvent;
var colide = Color.WHITE;
function checkcollision():Void {
if (
(rec1.getBoundsX() > rec2.getBoundsX() + rec2.getWidth()) or
(rec1.getBoundsX() + rec1.getWidth() < rec2.getBoundsX()) or
(rec1.getBoundsY() > rec2.getBoundsY() + rec2.getHeight()) or
(rec1.getBoundsY() + rec1.getHeight() < rec2.getBoundsY())
) {
colide = Color.WHITE
} else {
colide = Color.LIGHTGRAY
}
}
var rec1: MovableRectangle = MovableRectangle {
x: 10, y: 10, width: 50, height: 60, fill: Color.RED
onMove: function(e:MouseEvent):Void {
checkcollision()
}
}
var rec2: MovableRectangle = MovableRectangle {
x: 100, y: 100, width: 70, height: 30, fill: Color.BLUE
onMove: function(MouseEvent):Void {
checkcollision()
}
}
Frame {
title: "Rectangular Collisions", width: 300, height: 300
closeAction: function() {
java.lang.System.exit( 0 );
}
visible: true
stage: Stage {
fill: bind colide
content: [rec1, rec2]
}
}
Try it via Java Web Start:
Some considerations:
More generally, we can code:
function collission(ax, ay, bx, by, cx, cy, dx, dy): Boolean {
return not ((ax > dx)or(bx < cx)or(ay > dy)or(by < cy));
}
function hitnode(a: Node, b:Node): Boolean{
return (collission(
a.getBoundsX(), a.getBoundsY(),
a.getBoundsX() + a.getWidth(), a.getBoundsY() + a.getHeight(),
b.getX(), b.getY(),
b.getX() + b.getWidth(), b.getY() + b.getHeight()
));
}
This way we can pass just two bounding boxes to hitnode and easily check collision of a node against a list of bounding boxes nodes.
Using the same approach I also wrote this function to test if a Node is inside another Node:
function inside (ax, ay, bx, by, cx, cy, dx, dy):Boolean{
return ((ax > cx) and (bx < dx) and (ay > cy) and (by < dy));
}
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()
));
}
Soon I'll post game examples showing how to use this method and others collission detection methods.
Downloads:
Second version of the my free tileset. Now is possible to build a entire house. 🙂
Here is the tileset.
There’s a nice map editor with several features called Tiled. If you are edditing game maps, take a look.
I started to make several small JavaFX game demos. I’m doing that to fell where JavaFX is good to make this sort of game and what patterns would be frequently needed to implement, where I will place a little framework for fast development of simple casual games. What I’m calling now just ‘GameFX’. My first experiment was to creating a side scrolling animation that’s is usefull to create the parallax effect in side scrolling games. For that I created the class Slidding. You create an Slidding with a set of nodes and they will slide from right to left and when a node reaches the left side it back to the right side.
Example:
Slidding {
content: [
Circle {
centerX: 100, centerY: 100, radius: 40
fill: Color.RED
},
Circle {
centerX: 300, centerY: 100, radius: 40
fill: Color.BLUE
}
]
clock: 0.05s
}
That produces:
You create a Slidding with a list of Nodes at content, a clock (that will determine the speed of that animation) and a width. If you don’t provide a width, the slidding will do the best effort to determine one. You can use this approach to create more complex scenarios, using more Slidding groups.
This is a example of that:
import javafx.application.*;
import javafx.animation.*;
import javafx.scene.geometry.*;
import javafx.scene.paint.*;
import javafx.scene.*;
import gamefx.Slidding;
var SCREENW = 500;
var SCREENH = 400;
/* the sky is a light blue rectangle */
var sky = Rectangle {
width: SCREENW, height: SCREENH
fill: LinearGradient {
startX: 0.0 , startY: 0.0
endX: 0.0, endY: 1.0
proportional: true
stops: [
Stop { offset: 0.0 color: Color.LIGHTBLUE },
Stop { offset: 0.7 color: Color.LIGHTYELLOW },
Stop { offset: 1.0 color: Color.YELLOW }
]
}
}
/* the ground is a olive rectangle */
var ground = Rectangle {
translateY: 300
width: 500, height: 100
fill: LinearGradient {
startX: 0.0 , startY: 0.0
endX: 0.0, endY: 1.0
proportional: true
stops: [
Stop { offset: 0.2 color: Color.OLIVE },
Stop { offset: 1.0 color: Color.DARKOLIVEGREEN }
]
}
}
/* a clod cloud is like an ellipse */
class Cloud extends Ellipse {
override attribute radiusX = 50;
override attribute radiusY = 25;
override attribute fill = Color.WHITESMOKE;
override attribute opacity = 0.5;
}
/* we create a slidding of clouds */
var clouds = Slidding {
content: [
Cloud{centerX: 100, centerY: 100},
Cloud{centerX: 150, centerY: 20},
Cloud{centerX: 220, centerY: 150},
Cloud{centerX: 260, centerY: 200},
Cloud{centerX: 310, centerY: 40},
Cloud{centerX: 390, centerY: 150},
Cloud{centerX: 450, centerY: 30},
Cloud{centerX: 550, centerY: 100},
]
clock: 0.2s
}
var SUNX = 100;
var SUNY = 300;
var rotation = 0;
/* the sun, with it's corona */
var sun = Group {
rotate: bind rotation
anchorX: SUNX, anchorY: SUNY
content: [
for (i in [0..11]) {
Arc {
centerX: SUNX, centerY: SUNY
radiusX: 500, radiusY: 500
startAngle: 2 * i * (360 / 24), length: 360 / 24
type: ArcType.ROUND
fill: Color.YELLOW
opacity: 0.3
}
},
Circle {
centerX: SUNX, centerY: SUNY, radius: 60
fill: Color.YELLOW
},
]
}
/* animate the corona changing the it rotation angle */
var anim = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames : [
KeyFrame {
time : 0s
values: rotation => 0.0 tween Interpolator.LINEAR
},
KeyFrame {
time : 2s
values: rotation => (360.0/12) tween Interpolator.LINEAR
},
]
}
anim.start();
/* a tree is a simple polygon */
class Tree extends Polygon{
public attribute x = 0;
public attribute y = 0;
override attribute points = [0,0, 10,30, -10,30];
override attribute fill = Color.DARKOLIVEGREEN;
init{
translateX = x;
translateY = y;
}
}
/* a forest is a lot of trees */
var forest = Slidding{
content: [
Tree{x: 20, y: 320}, Tree{x: 80, y: 280}, Tree{x:120, y: 330},
Tree{x:140, y: 280}, Tree{x:180, y: 310}, Tree{x:220, y: 320},
Tree{x:260, y: 280}, Tree{x:280, y: 320}, Tree{x:300, y: 300},
Tree{x:400, y: 320}, Tree{x:500, y: 280}, Tree{x:500, y: 320}
]
clock: 0.1s
width: SCREENW
}
Frame {
title: "Side Scrolling"
width: SCREENW
height: SCREENH
closeAction: function() {
java.lang.System.exit( 0 );
}
visible: true
stage: Stage {
content: [sky, sun, clouds, ground, forest]
}
}
Producing:
If you want to try these examples, place this Slidding implementation as Slidding.fx in a directory named gamefx, or grab here the NetBeans project.
package gamefx;
import javafx.scene.CustomNode;
import javafx.scene.Node;
import javafx.scene.Group;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
/*
* The slidding group of nodes for side scrolling animations.
*
* @example
* Slidding {
* width: 300
* content: [
* Circle { centerX: 100, centerY: 100, radius: 40, fill: Color.RED },
* Circle { centerX: 200, centerY: 100, radius: 40, fill: Color.BLUE },
* ]
* clock: 0.05s
* }
*/
public class Slidding extends CustomNode {
public attribute content: Node[];
public attribute clock = 0.1s;
public attribute width: Number;
public attribute autostart = true;
public attribute cycle = true;
public attribute anim = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames : [
KeyFrame {
time : clock
action: function() {
for(node in content){
node.translateX--;
if (node.getX() + node.translateX + node.getWidth() <= 0){
if(cycle){
node.translateX = width - node.getX();
} else {
delete node from content;
}
}
}
} // action
} // keyframe
]
} // timeline
public function create(): Node {
// if width is not setted, we try to figure out
if(width == 0) {
for(node in content) {
if(node.getX() + node.getWidth() > width) {
width = node.getX() + node.getWidth();
}
}
}
// normaly the slidding will start automaticaly
if(autostart){
anim.start();
}
// just a Group of Nodes
return Group {
content: content
};
}
}
Is not the final implementation but it’s a idea. Soon I’ll show a demo game I did using theses codes.