/** * A simple JavaFX game demo. * You control a black sphere to clean the smoke clouds. * by J. M. Silveira Neto , http://silveiraneto.net * This code is under GPLv3. */ package game; import javafx.application.*; import javafx.scene.paint.*; import javafx.scene.geometry.*; import javafx.scene.text.*; import javafx.scene.effect.*; import javafx.scene.*; import javafx.input.*; import javafx.animation.*; import javafx.scene.image.*; import java.lang.*; var SCREENW = 500; var SCREENH = 400; var isgameover = false; /* * The slidding * * @example * Slidding { * width: 300 * content: [ * Circle { centerX: 100, centerY: 100, radius: 40, fill: Color.BLACK }, * Circle { centerX: 200, centerY: 100, radius: 40, fill: Color.BLACK }, * ] * clock: 0.05s * } */ public class Slidding extends CustomNode { public attribute content: Node[]; public attribute cycle = true; public attribute clock = 0.1s; private attribute width: Number; 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 { for(node in content) { if(node.getBoundsWidth() > width) { width = node.getBoundsWidth(); } } anim.start(); return Group { content: content }; } } // a clod is a ellipse class Cloud extends Ellipse { override attribute radiusX = 50; override attribute radiusY = 25; override attribute fill = Color.WHITESMOKE; override attribute opacity = 0.5; } // clouds is a lot slidding clouds var clouds = Slidding { width: SCREENW 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}, Cloud{centerX: 600, centerY: 150}, ] } public class Keyboard extends CustomNode { public attribute down = false; public attribute up = false; public attribute left = false; public attribute right = false; override attribute onKeyPressed = function ( e: KeyEvent ):Void { if (e.getKeyCode() == KeyCode.VK_DOWN) { down = true; } else if (e.getKeyCode() == KeyCode.VK_UP) { up = true; }else if (e.getKeyCode() == KeyCode.VK_LEFT) { left = true; }else if (e.getKeyCode() == KeyCode.VK_RIGHT) { right = true; } } override attribute onKeyReleased = function( e: KeyEvent ):Void { if (e.getKeyCode() == KeyCode.VK_DOWN) { down = false; } else if (e.getKeyCode() == KeyCode.VK_UP) { up = false; }else if (e.getKeyCode() == KeyCode.VK_LEFT) { left = false; }else if (e.getKeyCode() == KeyCode.VK_RIGHT) { right = false; } } public function create(): Node { return Text { content: "Keyboard" }; } } var kb = Keyboard{} var smokeList = [ Smoke{ centerX: 100, centerY: 50 }, Smoke{ centerX: 100, centerY: 150 }, Smoke{ centerX: 100, centerY: 200 }, Smoke{ centerX: 200, centerY: 100 }, Smoke{ centerX: 400, centerY: 50 }, Smoke{ centerX: 500, centerY: 150 }, Smoke{ centerX: 600, centerY: 200 }, Smoke{ centerX: 700, centerY: 100 }, Smoke{ centerX: 800, centerY: 220 }, Smoke{ centerX: 900, centerY: 50 }, Smoke{ centerX: 1000, centerY: 80 }, Smoke{ centerX: 1100, centerY:180 }, Smoke{ centerX: 1200, centerY: 50 }, Smoke{ centerX: 1200, centerY:220 }, Smoke{ centerX: 1400, centerY: 50 }, ]; var smokes = Slidding { width: SCREENW * 2 content: smokeList clock: 0.05s } var fadein = 0.0; var gameovertxt = Group { visible: bind isgameover opacity: bind fadein content: [ Text { font: Font { size: 80 style: FontStyle.BOLD } fill: Color.BLACK x: 10, y: 200 content: "Demo Over" }, Text { font: Font { size: 16 style: FontStyle.PLAIN } fill: Color.BLACK x: 20, y: 230 content: "Thanks for playing." }, Text { font: Font { size: 16 style: FontStyle.PLAIN } fill: Color.BLACK x: 20, y: 250 content: "http://silveiraneto.net" }, ] } var gameoveranimation = Timeline { repeatCount: 1 keyFrames : [ KeyFrame { time : 0s values : [ fadein => 0.0 tween Interpolator.LINEAR, ] }, KeyFrame { time : 1s values : [ fadein => 1.0 tween Interpolator.LINEAR, ] }, ] } var smokesclean = 0; public class Aircraft extends CustomNode { public attribute upperBound = 0.0; public attribute lowerBound = 300.0; public attribute leftBound = 0.0; public attribute rightBound = 500.0; public attribute keyboard: Keyboard; public attribute smokes: Smoke[]; private attribute altitude = 200.0; private attribute distance = 10.0; private attribute scale = 2.0; private attribute body = Circle { fill: Color.BLACK centerX: 20, centerY: 20 radius: 20 }; private attribute shine = Ellipse { centerX: 20, centerY: 11 radiusX: 12, radiusY: 8 fill: LinearGradient { startX: 0.0 , startY: 0.0 endX: 0.0, endY: 1.0 proportional: true stops: [ Stop { offset: 0.0 color: Color.WHITE }, Stop { offset: 0.5 color: Color.TRANSPARENT }, ] } }; private attribute mouth = Ellipse { fill: Color.GRAY centerX: 32, centerY: 20 radiusX: 5, radiusY: 8 }; private attribute lips = ShapeSubtract { fill: Color.DARKGRAY a: Ellipse { centerX: 32, centerY: 20 radiusX: 5, radiusY: 8 } b: Ellipse { centerX: 30, centerY: 20 radiusX: 5, radiusY: 8 } }; // TODO replace for trigger private function changeAltitude(change) { if((altitude + body.getHeight() + change < lowerBound) and (altitude + change > upperBound)){ altitude += change; } } // TODO replace for trigger private function changeDistance(change){ if((distance + body.getWidth() + change < rightBound) and(distance + change > leftBound)){ distance += change; } } private attribute checking = Timeline { repeatCount: Timeline.INDEFINITE keyFrames : [ KeyFrame { time : 0.02s action: function() { if( keyboard.up){ changeAltitude(-1); } if( keyboard.down){ changeAltitude(1); } if( keyboard.left){ changeDistance(-1); } if( keyboard.right){ changeDistance(1); } if(not isgameover){ var centerMouthX = distance + mouth.getX() + mouth.getWidth() / 2; var centerMouthY = altitude + mouth.getY() + mouth.getHeight() / 2; for(smoke in smokes){ if(smoke.contains(centerMouthX, centerMouthY)) { smoke.startCleaning(); } } if (smokesclean == sizeof smokes) { isgameover = true; gameoveranimation.start(); } } } } ] } public function goToCenter(): Void { checking.pause(); var centralize = Timeline { repeatCount: 1 keyFrames : [ KeyFrame { time : 0s values: [ translateX => translateX tween Interpolator.EASEBOTH, ] }, KeyFrame{ time: 3s values: [ translateX => (leftBound + rightBound) / 2 tween Interpolator.EASEBOTH, ] } ] } centralize.start() } public function create(): Node { checking.start(); return Group { translateX: bind distance translateY: bind altitude content: [body, shine, mouth, lips] }; // group } // create } // 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 } ] } } // ground is a green 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 } ] } } // smoke class Smoke extends Ellipse{ override attribute radiusX = 30; override attribute radiusY = 20; override attribute opacity = 0.5; override attribute fill = Color.DARKGRAY; readonly attribute clean = false; private attribute shake = Timeline { repeatCount: Timeline.INDEFINITE autoReverse: true keyFrames : [ KeyFrame { time : 0s values: radiusY => radiusY }, KeyFrame{ time: 2s values: radiusY => (radiusY + radiusX) / 2 tween Interpolator.LINEAR } ] } private attribute cleanAnimation = Timeline { repeatCount: 1 keyFrames : [ KeyFrame { time : 0s values : [ radiusX => radiusX, radiusY => radiusY, fill => Color.DARKGRAY tween Interpolator.EASEBOTH ] }, KeyFrame { time : 1s values : [ radiusX => 10 tween Interpolator.EASEIN, radiusY => 10 tween Interpolator.EASEIN, fill => Color.BLACK tween Interpolator.EASEBOTH ] }, KeyFrame { time : 1.5s values : [ fill => Color.DARKGRAY tween Interpolator.EASEBOTH ] action: function() { clean = true; smokesclean++; } }, KeyFrame { time : 2.0s values : [ radiusX => 60 tween Interpolator.EASEIN, radiusY => 30 tween Interpolator.EASEIN, fill => Color.WHITESMOKE tween Interpolator.EASEBOTH ] } ] } public attribute startCleaning = function() { if(not clean){ shake.stop(); if(not cleanAnimation.running){ cleanAnimation.start(); } } } init{ //shake.start(); } } // aircraft var aircraft = Aircraft{ upperBound: 10; lowerBound: 300; rightBound: SCREENW keyboard: kb; smokes: smokeList } // tree 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; } } // forest var forest = Slidding{ width: SCREENW 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 } // game stage var stage = Stage { content: [kb, sky, clouds, ground, aircraft, smokes, forest, gameovertxt] } Frame { title: "Game" width: SCREENW height: SCREENH closeAction: function() { System.exit( 0 ); } visible: true stage: stage }