Skip to content

Category: english

JavaFX, Side Scrolling Gaming

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.

Question about JavaFX SDK Preview?

This month, August 2008, from days 18 to 22 you have a opportunity to ask question about the JavaFX SDK Preview and get answers from experts on that topics. The Ask the Experts program requires no login, and allows you to submit questions at a time convenient to you.

These three experts Software Engineers from Sun Microsystems and related with JavaFX project will be answering questions about JavaFX:

It’s a good event to get answers for your JavaFX questions.

JavaFX, Draggable Nodes

One thing that I like a lot to do with JavaFX is draggable objects. Due to some recent changes in the JavaFX syntax my old codes for that are no longer working. Joshua Marinacci from Sun’s JavaFX engineering team and other guys from the JavaFX community gave me some tips. Here some strategies I’m using for making draggable nodes in JavaFX.

In this first example, a simple draggable ellipse.


video url: http://www.youtube.com/watch?v=pAJHH-mPLaQ

import javafx.application.*;
import javafx.scene.paint.*;
import javafx.scene.geometry.*;
import javafx.input.*;
 
Frame {
    width: 300, height: 300, visible: true
    stage: Stage {
        content: [
            Ellipse {
                var endX = 0.0; var endY = 0.0
                var startX = 0.0; var startY = 0.0
                centerX: 150, centerY: 150
                radiusX: 80, radiusY: 40
                fill: Color.ORANGE
                translateX: bind endX
                translateY: bind endY
                onMousePressed: function(e:MouseEvent):Void {
                    startX = e.getDragX()-endX;
                    startY = e.getDragY()-endY;
                }
                onMouseDragged: function(e:MouseEvent):Void {
                    endX = e.getDragX()-startX;
                    endY = e.getDragY()-startY;
                }
            }
        ]
 
    }
}

When you need to create a group of draggable objects, you can try thie approach of a draggable group like this. Inside on it, you can put whatever you want.


Video url: http://www.youtube.com/watch?v=mHOcPRrgQCQ

import javafx.application.*;
import javafx.scene.paint.*;
import javafx.scene.geometry.*;
import javafx.input.*;
import javafx.scene.*;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.animation.*;
 
// a graggable group
public class DragGroup extends CustomNode{
    public attribute content: Node[];
 
    private attribute endX = 0.0;
    private attribute endY = 0.0;
 
    private attribute startX = 0.0;
    private attribute startY = 0.0;
 
    public function create(): Node {
        return Group{
            translateX: bind endX
            translateY: bind endY
            content: bind content
        }
    }
 
    override attribute onMousePressed = function(e:MouseEvent):Void {
        startX = e.getDragX()-endX;
        startY = e.getDragY()-endY;
    }
 
    override attribute onMouseDragged = function(e:MouseEvent):Void {
        endX = e.getDragX()-startX;
        endY = e.getDragY()-startY;
    }
}
 
// angle animation, cycles between 0 to 360 in 36 seconds
var angle = 0.0;
var angleAnimation = Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames : [
        KeyFrame {
            time: 0s
            values: 
                    angle => 0.0
        },
        KeyFrame{
            time: 36s
            values :  
                    angle => 360.0 tween Interpolator.LINEAR
        }
    ]
}
 
// some pictures from my Flickr albums 
var me    = "http://farm4.static.flickr.com/3042/2746737338_aa3041f283_m.jpg";
 
 
 
var dog   = "http://farm4.static.flickr.com/3184/2717290793_ec14c26a85_m.jpg";
var plant = "http://farm4.static.flickr.com/3014/2731177705_bed6d6b8fa_m.jpg";
var bird  = "http://farm4.static.flickr.com/3190/2734919599_a0110e7ce0_m.jpg";
var me_89  = "http://farm3.static.flickr.com/2138/2308085138_7b296cc5d0_m.jpg";
 
 
Frame {    
    width: 640, height: 480, visible: true
    stage: Stage {
        fill: Color.BLACK
        content: [
            DragGroup{
                content: ImageView {
                    anchorX: 120, anchorY: 90
                    rotate: bind 30 + angle
                    image: Image { backgroundLoading: true, url: me }
                }
            },
            DragGroup {
                translateX: 300, translateY: 50
                content: ImageView {
                    anchorX: 120, anchorY: 90
                    rotate: bind -30 + angle
                    image: Image { backgroundLoading: true, url: dog }
                }
            },
            DragGroup {
                translateX: 300, translateY: 300
                content: ImageView {
                    anchorX: 120, anchorY: 90
                    rotate: bind 90 + angle
                    image: Image { backgroundLoading: true, url: plant }
                }                
            },
            DragGroup {
                translateX: 200
                translateY: 200
                content: ImageView {
                    anchorX: 120, anchorY: 90
                    rotate: bind 90 + angle
                    image: Image { backgroundLoading: true, url: bird }
                }                
            },
            DragGroup {
                translateX: 30
                translateY: 200
                content: ImageView {
                    anchorX: 85, anchorY: 120
                    rotate: bind angle + 180
                    image: Image { backgroundLoading: true, url: me_89 }
                }                
            },
        ]
 
    }
 
    closeAction: function() { 
        java.lang.System.exit( 0 ); 
    }
}
 
angleAnimation.start();

One more example, using the same class DragGroup, we can put multiple nodes using lists.


Video url: http://www.youtube.com/watch?v=gJqy7EdtEqs

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.paint.*;
import javafx.input.*;
import javafx.animation.*;
import java.lang.Math;
 
// Class to create a draggable group of objects
public class DragGroup extends CustomNode{
    public attribute content: Node[];
 
    private attribute endX = 0.0;
    private attribute endY = 0.0;
 
    private attribute startX = 0.0;
    private attribute startY = 0.0;
 
    override attribute onMousePressed = function(e:MouseEvent):Void {
        startX = e.getDragX()-endX;
        startY = e.getDragY()-endY;
    }
 
    override attribute onMouseDragged = function(e:MouseEvent):Void {
        endX = e.getDragX()-startX;
        endY = e.getDragY()-startY;
    }
 
    public function create(): Node {
        return Group{
            translateX: bind endX
            translateY: bind endY
            content: bind content
        }
    }
}
 
// angle animation, cycles between 0 to 360 in 10 seconds
var angle = 0.0;
var angleAnimation = Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames : [
        KeyFrame {
            time: 0s
            values: angle => 0.0
        },
        KeyFrame{
            time: 10s
            values :  angle => 360.0 tween Interpolator.LINEAR
        }
    ]
}
 
// breath animation, go and back from 0.0 to 10.0 in 2 seconds
var breath = 0.0;
var breathAnimation = Timeline {
    repeatCount: Timeline.INDEFINITE
    autoReverse: true
    keyFrames : [
        KeyFrame {
            time: 0s
            values: breath => 0.0
        },
        KeyFrame{
            time: 1s
            values :  breath => 10.0 tween Interpolator.LINEAR
        }
    ]
}
 
// Creates n multi colored floating circles around a bigger circle
var n = 12;
var colors = [
    Color.BLUE, Color.AQUA, Color.MAGENTA, Color.RED,
    Color.YELLOW, Color.ORANGE, Color.HOTPINK, Color.LIME
];
var chosen = Color.YELLOW;
var floatingCircles = Group{
    rotate: bind angle
    content: for (i in [1..n]) 
    Circle {
        fill: colors[i mod sizeof colors]
        radius: 10
        centerX: Math.cos(i * 2 * Math.PI/n) * 70
        centerY: Math.sin(i * 2 * Math.PI/n) * 70
        onMouseClicked: function( e: MouseEvent ):Void {
            chosen = colors[i mod sizeof colors];
        }
    }
}
var circle = Circle{
    radius: bind 50 + breath
    fill: bind chosen
}
 
 
Frame {
    width: 400, height: 400, visible: true
    stage: Stage {
        fill: Color.BLACK
        content: [
            DragGroup{
                translateX: 200, translateY: 200
                content: [circle, floatingCircles]
            }
        ]
    }
 
    closeAction: function() { 
        java.lang.System.exit( 0 ); 
    }
}
 
// starts all animations
angleAnimation.start();
breathAnimation.start();