The game covers a lot of things from collision detection to visual effects like inner shadows. If you want to play the game: click here. It’s probably a good idea having a look at the game before stepping through the code so you already know what I am talking about.
So let’s step through the code. You can also download the code as Netbeans project.
/*
* Copyright 2011, Benny Gächter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package javafxapp;
import javafx.animation.KeyFrame;
import javafx.scene.paint.Color;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Flow;
import javafx.scene.text.Text;
import javafx.animation.Timeline;
import javafx.scene.Node;
import javafx.scene.CustomNode;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.transform.Translate;
import javafx.scene.text.Font;
import javafx.scene.Group;
import javafx.scene.control.Button;
import javafx.scene.effect.InnerShadow;
import javafx.scene.control.Hyperlink;
import javafx.stage.AppletStageExtension;
// Height and Width of this Application
def APP_WIDTH = 500;
def APP_HEIGHT = APP_WIDTH;
//Variables for mouse coordinates
var mouseX = 0.0;
var mouseY = 0.0;
//We need this to get a random place for the balls
var random = new java.util.Random();
//state of the game 1 is “running” 2 is “game over”
var state = 1;
//not used at the moment
var counter = 0;
//player time
var currentRuntime: Number = 0;
//create a background and track mouse movements on it
def background = Rectangle {
x: 0 y: 0 width: APP_WIDTH
height: APP_HEIGHT
fill: Color.web("#E6E6E6");
onMouseMoved: function(e: MouseEvent) {
mouseX = e.x;
mouseY = e.y;
}
}
//create player
def player = Rectangle {
x: bind mouseX - 10
y: bind mouseY - 10
width: 20
height: 20
//create a nice visual effect
effect: InnerShadow {
choke: 0.2
offsetX: 1
offsetY: 1
radius: 5
color: Color.WHITE
}
}
//create balls
var AIs: Ball[];
for (i in [0..16]) {
insert Ball {
//set balls to a random place
x: random.nextInt(450), y: random.nextInt(450)
//create a nice visual effect for the balls
effect: InnerShadow {
choke: 0.5
offsetX: 1
offsetY: 1
radius: 5
color: Color.BLACK
};
} into AIs;
//let the balls go to random directions
if(random.nextInt(100) mod 2 == 0){
AIs[i].vy = 1
}
if(random.nextInt(100) mod 2 == 0){
AIs[i].vx = -1
}
}
//t1 is for game logic
def t1 = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
KeyFrame {
//17ms is better than 25…
time: 17ms //25ms
values: []
action: function() {
for (AI in AIs) {
//bounce from walls
if (AI.x + AI.vx < AI.radius) { AI.vx = -AI.vx;
}
if (AI.x + AI.vx > APP_WIDTH - 7) { AI.vx = -AI.vx;
}
if (AI.y + AI.vy < AI.radius) { AI.vy = -AI.vy;
}
if (AI.y + AI.vy > APP_WIDTH - 7) { AI.vy = -AI.vy
}
//bounce from other balls
/*
*not yet implemented
*/
//detect collissions
if (AI.x >= mouseX and AI.x <= mouseX + 10) {
if (AI.y + AI.radius >= mouseY - 10 and AI.y - AI.radius <= mouseY) {
AI.vy = -AI.vy;
state = 2;
}
} else if (AI.y >= mouseY - 10 and AI.y <= mouseY) {
if (AI.x + AI.radius >= mouseX and AI.x - AI.radius <= mouseX + 10) {
AI.vx = -AI.vx;
state = 2;
}
}
//calculate new position
AI.x += AI.vx;
AI.y += AI.vy;
//increase speed
AI.vx = AI.vx * 1.002;
AI.vy = AI.vy * 1.002;
}
// not used at the moment
counter++;
}
}
]
};
//t1 observes the game state and reacts on it
def t2 = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
KeyFrame {
time: 25ms
values: []
action: function() {
/* insert Ball {
x: random.nextInt(500), y: random.nextInt(500)
} into AIs;*/
//insert AIs into stage.scene.content;
//stage.scene.impl_setStage(stage);
if (state != 1) {
t1.stop();
tlTimer.stop();
gameOverScreen.visible = true;
}
}
}
]
};
//tlTimer is for displaying the current time
def tlTimer = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
KeyFrame {
time: 100ms
values: []
action: function() {
currentRuntime += 0.1;
}
}
]
};
//start all timers
tlTimer.play();
t1.play();
t2.play();
//create the text object for timer
def timer = Flow {
content: [Text { content: bind currentRuntime.toString()
font: Font { size: 30 } }
];
}
//create the game over screen
var gameOverScreen: Group = Group {
visible: false
var r: Rectangle = Rectangle {
arcWidth: APP_WIDTH arcHeight: APP_WIDTH
width: APP_WIDTH, height: APP_WIDTH
fill: Color.ROSYBROWN
stroke: Color.DARKRED
strokeWidth: APP_WIDTH
}
content: [
r,
Text {
font: Font {
size: 28
}
x: 20, y: 50
content: "Game Over! Time: ";
},
Text {
font: Font {
size: 28
}
x: 20, y: 80
content: bind String.valueOf(currentRuntime).concat(" Seconds")
},
Text {
font: Font {
size: 28
}
x: 20, y: 200
content: "By Benny Gächter ";
},
Hyperlink {
layoutX: 100,
layoutY: 230,
scaleX:3
scaleY:3
text: "www.bgaechter.ch"
action: function(): Void {
AppletStageExtension.showDocument("http://www.bgaechter.ch");
}
}
Button {
layoutX: 250
layoutY: 120
scaleX: 3
scaleY: 3
text: "Restart"
action: function() {
restart();
},
}
]
}
// Display everything
var stage: Stage = Stage {
title: "Test App"
width: 500
height: 500
scene: Scene {
content: [
background,
player,
AIs,
timer,
gameOverScreen
]
}
}
//the ball class derived from CustomNode
class Ball extends CustomNode {
// Ball position
public var x: Number = 100;
public var y: Number = 100;
// Ball radius
public var radius: Number = 10;
// Ball velocity vectors
public var vx: Number = 1.0;
public var vy: Number = -1.0;
public override function create(): Node {
return Circle {
transforms: Translate { x: bind x, y: bind y }
radius: bind radius
fill: Color.RED
}
}
}
//The restart function, may be called after game over
function restart(): Void {
state = 1;
currentRuntime = 0;
t1.playFromStart();
tlTimer.playFromStart();
gameOverScreen.visible = false;
for (AI in AIs) {
AI.vx = 1;
AI.vy = -1;
}
}
You did that in 2011? Sorry to disappoint you, but JavaFX 1.x syntax (JavaFX Script) has been dropped by Oracle months ago, and they released an Early Access to JavaFX 2.0 using Java syntax (or Groovy, or Scala, or... any other JVM language).
ReplyDeleteWell, JavaFX Script is still something fun to learn and hopefully what you learned there will still be useful in 2.0, but I suggest not to spend too much time on the fine details of the language... (also see the Visage project, reusing this syntax/the compiler).
Hi, sorry for this late answer. Yes i knew i am using an outdated technology and i didn't intend to do something serious. It was just playing around with some java related technology.
ReplyDeleteI didn't look at javafx 2.0 but i think that my experience with java 1.x will help a bit.