├── jfxgenproj ├── settings.gradle ├── build.gradle └── src │ └── main │ └── java │ └── carlfx │ └── gameengine │ ├── SoundManager.java │ ├── Sprite.java │ ├── SpriteManager.java │ └── GameWorld.java ├── settings.gradle ├── demos ├── navigateship │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── ship.png │ │ │ └── laser_2.mp3 │ │ │ └── java │ │ │ └── carlfx │ │ │ └── demos │ │ │ └── navigateship │ │ │ ├── RotatedShipImage.java │ │ │ ├── Missile.java │ │ │ ├── InputPart3.java │ │ │ ├── Part3_4_5.java │ │ │ ├── Vec.java │ │ │ ├── Atom.java │ │ │ ├── TheExpanse.java │ │ │ └── Ship.java │ └── build.gradle └── atomsmasher │ ├── notes.txt │ ├── src │ └── main │ │ └── java │ │ └── carlfx │ │ └── demo │ │ └── atomsmasher │ │ ├── GameLoopPart2.java │ │ ├── Atom.java │ │ └── AtomSmasher.java │ └── build.gradle ├── .gitignore └── README.md /jfxgenproj/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jfxgenproj' 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'jfxgenproj', 'demos:atomsmasher' 2 | -------------------------------------------------------------------------------- /demos/navigateship/src/main/resources/ship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carldea/JFXGen/HEAD/demos/navigateship/src/main/resources/ship.png -------------------------------------------------------------------------------- /demos/navigateship/src/main/resources/laser_2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carldea/JFXGen/HEAD/demos/navigateship/src/main/resources/laser_2.mp3 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build.gradle.swp 2 | .gradle 3 | build 4 | _build 5 | .classpath 6 | .project 7 | .settings 8 | bin 9 | *.iml 10 | *.ipr 11 | *.iws 12 | out/ 13 | .idea/ 14 | /nbproject/private/ 15 | /nbbuild/ 16 | /dist/ 17 | nbproject 18 | 19 | -------------------------------------------------------------------------------- /demos/navigateship/src/main/java/carlfx/demos/navigateship/RotatedShipImage.java: -------------------------------------------------------------------------------- 1 | package carlfx.demos.navigateship; 2 | 3 | import javafx.scene.image.ImageView; 4 | 5 | /** 6 | * Represents a double link list to assist in the rotation of the ship. 7 | * This helps to move clockwise and counter clockwise. 8 | */ 9 | public class RotatedShipImage extends ImageView { 10 | public RotatedShipImage next; 11 | public RotatedShipImage prev; 12 | } 13 | -------------------------------------------------------------------------------- /demos/navigateship/src/main/java/carlfx/demos/navigateship/Missile.java: -------------------------------------------------------------------------------- 1 | package carlfx.demos.navigateship; 2 | 3 | import javafx.scene.paint.Color; 4 | 5 | /** 6 | * A missile projectile without the radial gradient. 7 | */ 8 | public class Missile extends Atom { 9 | public Missile(Color fill) { 10 | super(5, fill, false); 11 | } 12 | 13 | public Missile(int radius, Color fill) { 14 | super(radius, fill, true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demos/atomsmasher/notes.txt: -------------------------------------------------------------------------------- 1 | gradle clean build 2 | 3 | cd build 4 | 5 | run below 6 | 7 | javafxpackager -createjar -appclass carlfx.demos.atomsmasher.GameLoopPart2 -srcdir classes/main -outdir out -outfile atomsmasher-0.1.jar -v -manifestAttrs "JavaFX-Version=2.1,implementation-title=AtomSmasher,implementation-version=0.1,JavaFX-Application-Class=carlfx.demos.atomsmasher.GameLoopPart2,JavaFX-Class-Path=jfxgenproj-0.1.jar" 8 | 9 | 10 | copy jfxgenproj-0.1.jar colocated to atomsmasher-0.1.jar 11 | 12 | double click atomsmasher-0.1.jar 13 | 14 | -------------------------------------------------------------------------------- /jfxgenproj/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'idea' 3 | 4 | project.ext.group = 'carlfx' 5 | project.ext.jdkVersion = 1.7 6 | project.ext.javafxHome = System.getenv('JAVAFX_HOME') 7 | 8 | group = 'carlfx' 9 | version = 0.3 10 | 11 | sourceCompatibility = jdkVersion 12 | targetCompatibility = jdkVersion 13 | 14 | jar { 15 | manifest { 16 | attributes 'Implementation-Title': 'JavaFX 2 Game Engine', 'Implementation-Version': project.version 17 | } 18 | } 19 | 20 | repositories { 21 | mavenCentral() 22 | } 23 | 24 | 25 | dependencies { 26 | compile files("${project.ext.javafxHome}/rt/lib/jfxrt.jar") 27 | testCompile 'junit:junit:4.10' 28 | } 29 | defaultTasks 'clean', 'build' 30 | -------------------------------------------------------------------------------- /demos/navigateship/src/main/java/carlfx/demos/navigateship/InputPart3.java: -------------------------------------------------------------------------------- 1 | package carlfx.demos.navigateship; 2 | 3 | import carlfx.gameengine.GameWorld; 4 | import javafx.application.Application; 5 | import javafx.stage.Stage; 6 | 7 | /** 8 | * The main driver of the game. 9 | * @author cdea 10 | */ 11 | public class InputPart3 extends Application { 12 | 13 | GameWorld gameWorld = new TheExpanse(59, "JavaFX 2 GameTutorial Part 3 - Input"); 14 | /** 15 | * @param args the command line arguments 16 | */ 17 | public static void main(String[] args) { 18 | launch(args); 19 | } 20 | 21 | @Override 22 | public void start(Stage primaryStage) { 23 | // setup title, scene, stats, controls, and actors. 24 | gameWorld.initialize(primaryStage); 25 | 26 | // kick off the game loop 27 | gameWorld.beginGameLoop(); 28 | 29 | // display window 30 | primaryStage.show(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /demos/atomsmasher/src/main/java/carlfx/demo/atomsmasher/GameLoopPart2.java: -------------------------------------------------------------------------------- 1 | package carlfx.demos.atomsmasher; 2 | 3 | import carlfx.gameengine.GameWorld; 4 | import javafx.application.Application; 5 | import javafx.stage.Stage; 6 | 7 | /** 8 | * The main driver of the game. 9 | * @author cdea 10 | */ 11 | public class GameLoopPart2 extends Application { 12 | 13 | GameWorld gameWorld = new AtomSmasher(60, "JavaFX 2 GameTutorial Part 2 - Game Loop"); 14 | /** 15 | * @param args the command line arguments 16 | */ 17 | public static void main(String[] args) { 18 | launch(args); 19 | } 20 | 21 | @Override 22 | public void start(Stage primaryStage) { 23 | // setup title, scene, stats, controls, and actors. 24 | gameWorld.initialize(primaryStage); 25 | 26 | // kick off the game loop 27 | gameWorld.beginGameLoop(); 28 | 29 | // display window 30 | primaryStage.show(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /demos/navigateship/src/main/java/carlfx/demos/navigateship/Part3_4_5.java: -------------------------------------------------------------------------------- 1 | package carlfx.demos.navigateship; 2 | 3 | import carlfx.gameengine.GameWorld; 4 | import javafx.application.Application; 5 | import javafx.application.Platform; 6 | import javafx.stage.Stage; 7 | 8 | /** 9 | * The main driver of the game. 10 | * 11 | * @author cdea 12 | */ 13 | public class Part3_4_5 extends Application { 14 | 15 | GameWorld gameWorld = new TheExpanse(59, "JavaFX 2 GameTutorial Part 3, 4, and 5"); 16 | 17 | /** 18 | * @param args the command line arguments 19 | */ 20 | public static void main(String[] args) { 21 | launch(args); 22 | } 23 | 24 | @Override 25 | public void start(Stage primaryStage) { 26 | // setup title, scene, stats, controls, and actors. 27 | gameWorld.initialize(primaryStage); 28 | 29 | // kick off the game loop 30 | gameWorld.beginGameLoop(); 31 | 32 | // display window 33 | primaryStage.show(); 34 | } 35 | 36 | @Override 37 | public void stop() throws Exception { 38 | Platform.exit(); 39 | gameWorld.shutdown(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /jfxgenproj/src/main/java/carlfx/gameengine/SoundManager.java: -------------------------------------------------------------------------------- 1 | package carlfx.gameengine; 2 | 3 | import javafx.scene.media.AudioClip; 4 | 5 | import java.net.URL; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.concurrent.ExecutorService; 9 | import java.util.concurrent.Executors; 10 | 11 | /** 12 | * Responsible for loading sound media to be played using an id or key. 13 | * Contains all sounds for use later. 14 | *

15 | * User: cdea 16 | */ 17 | public class SoundManager { 18 | ExecutorService soundPool = Executors.newFixedThreadPool(2); 19 | Map soundEffectsMap = new HashMap<>(); 20 | 21 | /** 22 | * Constructor to create a simple thread pool. 23 | * 24 | * @param numberOfThreads - number of threads to use media players in the map. 25 | */ 26 | public SoundManager(int numberOfThreads) { 27 | soundPool = Executors.newFixedThreadPool(numberOfThreads); 28 | } 29 | 30 | /** 31 | * Load a sound into a map to later be played based on the id. 32 | * 33 | * @param id - The identifier for a sound. 34 | * @param url - The url location of the media or audio resource. Usually in src/main/resources directory. 35 | */ 36 | public void loadSoundEffects(String id, URL url) { 37 | AudioClip sound = new AudioClip(url.toExternalForm()); 38 | soundEffectsMap.put(id, sound); 39 | } 40 | 41 | /** 42 | * Lookup a name resource to play sound based on the id. 43 | * 44 | * @param id identifier for a sound to be played. 45 | */ 46 | public void playSound(final String id) { 47 | Runnable soundPlay = new Runnable() { 48 | @Override 49 | public void run() { 50 | soundEffectsMap.get(id).play(); 51 | } 52 | }; 53 | soundPool.execute(soundPlay); 54 | } 55 | 56 | /** 57 | * Stop all threads and media players. 58 | */ 59 | public void shutdown() { 60 | soundPool.shutdown(); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JFXGen 2 | ====== 3 | 4 | A simple JavaFX game engine. 5 | --------------------------------------------------------------- 6 | JFXGen - JavaFX Game Engine 7 | 8 | Welcome to a simple JavaFX game engine. This project consists of the core project called jfxgenproj and sub modules or demos. This document will help you show you how to setup your environment and build executables. 9 | 10 | Software Requirements: 11 | - Java 7 SDK or later 12 | - JavaFX 2.1 SDK or later 13 | - Gradle Gradle 1.0-rc-1 14 | - Git 15 | 16 | When setting your environment make sure you have 'JAVA_HOME', 'JAVAFX_HOME', and 'GRADLE_HOME'. Please refer to install instructions for your platform (iOS, unix, linux, Windows). 17 | 18 | Building the game engine and the demos as Java Webstart applictions. 19 | -------------------------------------------------------------------- 20 | mkdir JFXGen 21 | cd JFXGen 22 | git clone git@github.com:carldea/JFXGen.git 23 | 24 | gradle -DhostUrl=http://yourhost/path_of_jnlp 25 | 26 | Creating a project for IntelliJ IDE: 27 | ------------------------------------ 28 | cd JFXGen/jfxgenproj 29 | 30 | gradle idea 31 | 32 | cd JFXGen/demos/atomsmasher 33 | 34 | gradle idea 35 | 36 | Launch IntelliJ 37 | 38 | File -> Open Project 39 | 40 | Navigate (Browse) to the jfxgenproj folder then click 'OK'. 41 | Example: 42 | C:\projects\jfxgen\jfxgenproj 43 | 44 | File -> New Module 45 | 46 | Import existing module (select radio button) 47 | Select the browse button '...' to locate *.iml file. 48 | Example: 49 | C:\projects\jfxgen\demos\atomsmasher\atomsmasher.iml 50 | Click 'OK' 51 | Click 'Finish' 52 | 53 | To run the examples: 54 | -------------------- 55 | You can double click the jar file in your file explorer. (On Windows it works) 56 | Provided that you have built the executables you can upload them to your Web server. Make sure that the JNLP file's jnlp XML element having the 'href' attribute contains your location of the jar file (using the -D system property to set the host URL). Another way to run examples in IntelliJ is to Ctrl-Shift-F10 or right click ('Run') the file GameLoopPart2. 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /jfxgenproj/src/main/java/carlfx/gameengine/Sprite.java: -------------------------------------------------------------------------------- 1 | package carlfx.gameengine; 2 | 3 | import javafx.geometry.Point2D; 4 | import javafx.scene.Node; 5 | import javafx.scene.shape.Circle; 6 | 7 | /** 8 | * The Sprite class represents a image or node to be displayed. 9 | * In a 2D game a sprite will contain a velocity for the image to 10 | * move across the scene area. The game loop will call the update() 11 | * and collide() method at every interval of a key frame. A list of 12 | * animations can be used during different situations in the game 13 | * such as rocket thrusters, walking, jumping, etc. 14 | * 15 | * @author cdea 16 | */ 17 | public abstract class Sprite { 18 | 19 | /** 20 | * Current display node 21 | */ 22 | public Node node; 23 | 24 | /** 25 | * velocity vector x direction 26 | */ 27 | public double vX = 0; 28 | 29 | /** 30 | * velocity vector y direction 31 | */ 32 | public double vY = 0; 33 | 34 | /** 35 | * dead? 36 | */ 37 | public boolean isDead = false; 38 | 39 | /** 40 | * collision shape 41 | */ 42 | public Circle collisionBounds; 43 | 44 | /** 45 | * Updates this sprite object's velocity, or animations. 46 | */ 47 | public abstract void update(); 48 | 49 | /** 50 | * Did this sprite collide into the other sprite? 51 | * 52 | * @param other - The other sprite. 53 | * @return boolean - Whether this or the other sprite collided, otherwise false. 54 | */ 55 | public boolean collide(Sprite other) { 56 | 57 | if (collisionBounds == null || other.collisionBounds == null) { 58 | return false; 59 | } 60 | 61 | // determine it's size 62 | Circle otherSphere = other.collisionBounds; 63 | Circle thisSphere = collisionBounds; 64 | Point2D otherCenter = otherSphere.localToScene(otherSphere.getCenterX(), otherSphere.getCenterY()); 65 | Point2D thisCenter = thisSphere.localToScene(thisSphere.getCenterX(), thisSphere.getCenterY()); 66 | double dx = otherCenter.getX() - thisCenter.getX(); 67 | double dy = otherCenter.getY() - thisCenter.getY(); 68 | double distance = Math.sqrt(dx * dx + dy * dy); 69 | double minDist = otherSphere.getRadius() + thisSphere.getRadius(); 70 | 71 | return (distance < minDist); 72 | } 73 | 74 | public void handleDeath(GameWorld gameWorld) { 75 | gameWorld.getSpriteManager().addSpritesToBeRemoved(this); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /jfxgenproj/src/main/java/carlfx/gameengine/SpriteManager.java: -------------------------------------------------------------------------------- 1 | package carlfx.gameengine; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * Sprite manager is responsible for holding all sprite objects, and cleaning up 7 | * sprite objects to be removed. All collections are used by the JavaFX 8 | * application thread. During each cycle (animation frame) sprite management 9 | * occurs. This assists the user of the API to not have to create lists to 10 | * later be garbage collected. Should provide some performance gain. 11 | * @author cdea 12 | */ 13 | public class SpriteManager { 14 | /** All the sprite objects currently in play */ 15 | private final static List GAME_ACTORS = new ArrayList<>(); 16 | 17 | /** A global single threaded list used to check collision against other 18 | * sprite objects. 19 | */ 20 | private final static List CHECK_COLLISION_LIST = new ArrayList<>(); 21 | 22 | /** A global single threaded set used to cleanup or remove sprite objects 23 | * in play. 24 | */ 25 | private final static Set CLEAN_UP_SPRITES = new HashSet<>(); 26 | 27 | /** */ 28 | public List getAllSprites() { 29 | return GAME_ACTORS; 30 | } 31 | 32 | /** 33 | * VarArgs of sprite objects to be added to the game. 34 | * @param sprites 35 | */ 36 | public void addSprites(Sprite... sprites) { 37 | GAME_ACTORS.addAll(Arrays.asList(sprites)); 38 | } 39 | 40 | /** 41 | * VarArgs of sprite objects to be removed from the game. 42 | * @param sprites 43 | */ 44 | public void removeSprites(Sprite... sprites) { 45 | GAME_ACTORS.removeAll(Arrays.asList(sprites)); 46 | } 47 | 48 | /** Returns a set of sprite objects to be removed from the GAME_ACTORS. 49 | * @return CLEAN_UP_SPRITES 50 | */ 51 | public Set getSpritesToBeRemoved() { 52 | return CLEAN_UP_SPRITES; 53 | } 54 | 55 | /** 56 | * Adds sprite objects to be removed 57 | * @param sprites varargs of sprite objects. 58 | */ 59 | public void addSpritesToBeRemoved(Sprite... sprites) { 60 | if (sprites.length > 1) { 61 | CLEAN_UP_SPRITES.addAll(Arrays.asList((Sprite[]) sprites)); 62 | } else { 63 | CLEAN_UP_SPRITES.add(sprites[0]); 64 | } 65 | } 66 | 67 | /** 68 | * Returns a list of sprite objects to assist in collision checks. 69 | * This is a temporary and is a copy of all current sprite objects 70 | * (copy of GAME_ACTORS). 71 | * @return CHECK_COLLISION_LIST 72 | */ 73 | public List getCollisionsToCheck() { 74 | return CHECK_COLLISION_LIST; 75 | } 76 | 77 | /** 78 | * Clears the list of sprite objects in the collision check collection 79 | * (CHECK_COLLISION_LIST). 80 | */ 81 | public void resetCollisionsToCheck() { 82 | CHECK_COLLISION_LIST.clear(); 83 | CHECK_COLLISION_LIST.addAll(GAME_ACTORS); 84 | } 85 | 86 | /** 87 | * Removes sprite objects and nodes from all 88 | * temporary collections such as: 89 | * CLEAN_UP_SPRITES. 90 | * The sprite to be removed will also be removed from the 91 | * list of all sprite objects called (GAME_ACTORS). 92 | */ 93 | public void cleanupSprites() { 94 | 95 | // remove from actors list 96 | GAME_ACTORS.removeAll(CLEAN_UP_SPRITES); 97 | 98 | // reset the clean up sprites 99 | CLEAN_UP_SPRITES.clear(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /demos/navigateship/src/main/java/carlfx/demos/navigateship/Vec.java: -------------------------------------------------------------------------------- 1 | package carlfx.demos.navigateship; 2 | 3 | /** 4 | * This class represents a container class to hold a Vector in space and direction 5 | * the ship will move. Assuming the center of the ship is the origin the angles can 6 | * be determined by a unit circle via Cartesian coordinates. 7 | * When the user clicks on the screen the mouse coordinates or screen coordinates 8 | * will be stored into the mx and my instance variables. 9 | * The x and y data members are converted to cartesian coordinates before storing. 10 | * 11 | * I purposefully left out getters and setters. In gaming just keep things minimalistic. 12 | * @author cdea 13 | */ 14 | public class Vec { 15 | public double mx; 16 | public double my; 17 | 18 | public double x; 19 | public double y; 20 | 21 | /** 22 | * This is a default constructor which will take a Cartesian coordinate. 23 | * @param x X coordinate of a point on a Cartesian system. 24 | * @param y Y coordinate of a point on a Cartesian system. 25 | */ 26 | public Vec(float x, float y) { 27 | this.x = x; 28 | this.y = y; 29 | } 30 | 31 | /** 32 | * Constructor will convert mouse click points into Cartesian coordinates based on the sprite's center point as 33 | * the origin. 34 | * @param mx Mouse press' screen X coordinate. 35 | * @param my Mouse press' screen Y coordinate. 36 | * @param centerX Screen X coordinate of the center of the ship sprite. 37 | * @param centerY Screen Y coordinate of the center of the ship sprite. 38 | */ 39 | public Vec(double mx, double my, double centerX, double centerY) { 40 | this.x = convertX(mx, centerX); 41 | this.y = convertY(my, centerY); 42 | this.mx = mx; 43 | this.my = my; 44 | } 45 | 46 | /** 47 | * Returns a Cartesian coordinate system's quadrant from 1 to 4. 48 | *

49 |      *     first quadrant - 1 upper right
50 |      *     second quadrant - 2 upper left
51 |      *     third quadrant - 3 lower left
52 |      *     fourth quadrant - 4 lower right
53 |      * 
54 | * @return int quadrant number 1 through 4 55 | */ 56 | public int quadrant() { 57 | int q = 0; 58 | if (x > 0 && y > 0) { 59 | q =1; 60 | } else if (x < 0 && y > 0) { 61 | q = 2; 62 | } else if (x < 0 && y < 0) { 63 | q = 3; 64 | } else if (x > 0 && y < 0) { 65 | q = 4; 66 | } 67 | return q; 68 | } 69 | @Override 70 | public String toString(){ 71 | return "(" + x + "," + y + ") quadrant=" + quadrant(); 72 | } 73 | /** 74 | * Converts point's X screen coordinate into a Cartesian system. 75 | * @param mouseX Converts the mouse X coordinate into Cartesian system based on the ship center X (originX). 76 | * @param originX The ship center point's X coordinate. 77 | * @return double value of a Cartesian system X coordinate based on the origin X. 78 | */ 79 | static double convertX(double mouseX, double originX) { 80 | return mouseX - originX; 81 | } 82 | 83 | /** 84 | * Converts point's Y screen coordinate into a Cartesian system. 85 | * @param mouseY Converts the mouse Y coordinate into Cartesian system based on the ship center Y (originY). 86 | * @param originY The ship center point's Y coordinate. 87 | * @return double value of a Cartesian system Y coordinate based on the origin Y. 88 | */ 89 | static double convertY(double mouseY, double originY) { 90 | return originY - mouseY; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /demos/navigateship/src/main/java/carlfx/demos/navigateship/Atom.java: -------------------------------------------------------------------------------- 1 | package carlfx.demos.navigateship; 2 | 3 | import carlfx.gameengine.GameWorld; 4 | import carlfx.gameengine.Sprite; 5 | import javafx.animation.FadeTransitionBuilder; 6 | import javafx.event.ActionEvent; 7 | import javafx.event.EventHandler; 8 | import javafx.scene.CacheHint; 9 | import javafx.scene.paint.Color; 10 | import javafx.scene.paint.RadialGradient; 11 | import javafx.scene.paint.RadialGradientBuilder; 12 | import javafx.scene.paint.Stop; 13 | import javafx.scene.shape.Circle; 14 | import javafx.scene.shape.CircleBuilder; 15 | import javafx.util.Duration; 16 | 17 | /** 18 | * A spherical looking object (Atom) with a random radius, color, and velocity. 19 | * When two atoms collide each will fade and become removed from the scene. The 20 | * method called implode() implements a fade transition effect. 21 | * 22 | * @author cdea 23 | */ 24 | public class Atom extends Sprite { 25 | 26 | 27 | /** 28 | * Constructor will create a optionally create a gradient fill 29 | * circle shape. This sprite will contain a JavaFX Circle node. 30 | * 31 | * @param radius The radius of the circular shape. 32 | * @param fill Fill color inside circle. 33 | * @param gradientFill boolean to fill shape as gradient. 34 | */ 35 | public Atom(double radius, Color fill, boolean gradientFill) { 36 | Circle sphere = CircleBuilder.create() 37 | .centerX(radius) 38 | .centerY(radius) 39 | .radius(radius) 40 | .fill(fill) 41 | .cache(true) 42 | .cacheHint(CacheHint.SPEED) 43 | .build(); 44 | if (gradientFill) { 45 | RadialGradient rgrad = RadialGradientBuilder.create() 46 | .centerX(sphere.getCenterX() - sphere.getRadius() / 3) 47 | .centerY(sphere.getCenterY() - sphere.getRadius() / 3) 48 | .radius(sphere.getRadius()) 49 | .proportional(false) 50 | .stops(new Stop(0.0, Color.WHITE), new Stop(1.0, fill)) 51 | .build(); 52 | 53 | sphere.setFill(rgrad); 54 | } 55 | // set javafx node to a circle 56 | node = sphere; 57 | collisionBounds = sphere; 58 | 59 | } 60 | 61 | /** 62 | * Change the velocity of the atom particle. 63 | */ 64 | @Override 65 | public void update() { 66 | node.setTranslateX(node.getTranslateX() + vX); 67 | node.setTranslateY(node.getTranslateY() + vY); 68 | } 69 | 70 | /** 71 | * Returns a node casted as a JavaFX Circle shape. 72 | * 73 | * @return Circle shape representing JavaFX node for convenience. 74 | */ 75 | public Circle getAsCircle() { 76 | return (Circle) node; 77 | } 78 | 79 | /** 80 | * Animate an implosion. Once done remove from the game world 81 | * 82 | * @param gameWorld - game world 83 | */ 84 | public void implode(final GameWorld gameWorld) { 85 | vX = vY = 0; 86 | FadeTransitionBuilder.create() 87 | .node(node) 88 | .duration(Duration.millis(300)) 89 | .fromValue(node.getOpacity()) 90 | .toValue(0) 91 | .onFinished(new EventHandler() { 92 | @Override 93 | public void handle(ActionEvent arg0) { 94 | isDead = true; 95 | gameWorld.getSceneNodes().getChildren().remove(node); 96 | 97 | } 98 | }) 99 | .build() 100 | .play(); 101 | } 102 | 103 | public void handleDeath(GameWorld gameWorld) { 104 | implode(gameWorld); 105 | super.handleDeath(gameWorld); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /demos/atomsmasher/src/main/java/carlfx/demo/atomsmasher/Atom.java: -------------------------------------------------------------------------------- 1 | package carlfx.demos.atomsmasher; 2 | 3 | import carlfx.gameengine.GameWorld; 4 | import carlfx.gameengine.Sprite; 5 | import javafx.animation.FadeTransitionBuilder; 6 | import javafx.event.ActionEvent; 7 | import javafx.event.EventHandler; 8 | import javafx.scene.paint.Color; 9 | import javafx.scene.paint.RadialGradient; 10 | import javafx.scene.paint.RadialGradientBuilder; 11 | import javafx.scene.paint.Stop; 12 | import javafx.scene.shape.Circle; 13 | import javafx.scene.shape.CircleBuilder; 14 | import javafx.util.Duration; 15 | 16 | /** 17 | * A spherical looking object (Atom) with a random radius, color, and velocity. 18 | * When two atoms collide each will fade and become removed from the scene. The 19 | * method called implode() implements a fade transition effect. 20 | * 21 | * @author cdea 22 | */ 23 | public class Atom extends Sprite { 24 | 25 | public Atom(double radius, Color fill) { 26 | Circle sphere = CircleBuilder.create() 27 | .centerX(radius) 28 | .centerY(radius) 29 | .radius(radius) 30 | .cache(true) 31 | .build(); 32 | 33 | RadialGradient rgrad = RadialGradientBuilder.create() 34 | .centerX(sphere.getCenterX() - sphere.getRadius() / 3) 35 | .centerY(sphere.getCenterY() - sphere.getRadius() / 3) 36 | .radius(sphere.getRadius()) 37 | .proportional(false) 38 | .stops(new Stop(0.0, fill), new Stop(1.0, Color.BLACK)) 39 | .build(); 40 | 41 | sphere.setFill(rgrad); 42 | 43 | // set javafx node to a circle 44 | node = sphere; 45 | 46 | } 47 | 48 | /** 49 | * Change the velocity of the atom particle. 50 | */ 51 | @Override 52 | public void update() { 53 | node.setTranslateX(node.getTranslateX() + vX); 54 | node.setTranslateY(node.getTranslateY() + vY); 55 | } 56 | 57 | @Override 58 | public boolean collide(Sprite other) { 59 | if (other instanceof Atom) { 60 | return collide((Atom)other); 61 | } 62 | return false; 63 | } 64 | 65 | /** 66 | * When encountering another Atom to determine if they collided. 67 | * @param other Another atom 68 | * @return boolean true if this atom and other atom has collided, 69 | * otherwise false. 70 | */ 71 | private boolean collide(Atom other) { 72 | 73 | // if an object is hidden they didn't collide. 74 | if (!node.isVisible() || 75 | !other.node.isVisible() || 76 | this == other) { 77 | return false; 78 | } 79 | 80 | // determine it's size 81 | Circle otherSphere = other.getAsCircle(); 82 | Circle thisSphere = getAsCircle(); 83 | double dx = otherSphere.getTranslateX() - thisSphere.getTranslateX(); 84 | double dy = otherSphere.getTranslateY() - thisSphere.getTranslateY(); 85 | double distance = Math.sqrt( dx * dx + dy * dy ); 86 | double minDist = otherSphere.getRadius() + thisSphere.getRadius() + 3; 87 | 88 | return (distance < minDist); 89 | } 90 | 91 | /** 92 | * Returns a node casted as a JavaFX Circle shape. 93 | * @return Circle shape representing JavaFX node for convenience. 94 | */ 95 | public Circle getAsCircle() { 96 | return (Circle) node; 97 | } 98 | 99 | /** 100 | * Animate an implosion. Once done remove from the game world 101 | * @param gameWorld - game world 102 | */ 103 | public void implode(final GameWorld gameWorld) { 104 | vX = vY = 0; 105 | FadeTransitionBuilder.create() 106 | .node(node) 107 | .duration(Duration.millis(300)) 108 | .fromValue(node.getOpacity()) 109 | .toValue(0) 110 | .onFinished(new EventHandler() { 111 | @Override 112 | public void handle(ActionEvent arg0) { 113 | isDead = true; 114 | gameWorld.getSceneNodes().getChildren().remove(node); 115 | } 116 | }) 117 | .build() 118 | .play(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /demos/navigateship/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'application' 3 | apply plugin: 'idea' 4 | project.ext.group = 'carlfx' 5 | project.version = 0.3 6 | project.ext.jdkVersion = 1.7 7 | project.ext.antJavafxJar = 'build/classes/ant-javafx' 8 | project.ext.jfxgenProj = project(':jfxgenproj') 9 | project.ext.javafxHome = System.getenv('JAVAFX_HOME') 10 | 11 | // ========================================= 12 | // Whenever you want to publish jars to your 13 | // server you can pass in the host url for 14 | // the jfx build to embed the url into the 15 | // jnlp file. 16 | // Example: 17 | // gradle -DhostUrl=http://mysite/here 18 | // ========================================= 19 | sourceCompatibility = jdkVersion 20 | targetCompatibility = jdkVersion 21 | 22 | configurations { 23 | 24 | } 25 | 26 | repositories { 27 | mavenCentral() 28 | flatDir { 29 | dirs "${project.ext.javafxHome}/tools/" 30 | } 31 | 32 | } 33 | 34 | 35 | dependencies { 36 | compile project(':jfxgenproj') 37 | compile files("${project.ext.javafxHome}/rt/lib/jfxrt.jar") 38 | testCompile 'junit:junit:4.10' 39 | } 40 | 41 | 42 | project.ext.mainClass = "carlfx.demos.navigateship.Part3_4_5" 43 | 44 | 45 | task fxjar(dependsOn: [':jfxgenproj:jar', 'classes']) << { 46 | def fx = 'javafx:com.sun.javafx.tools.ant' 47 | ant.sequential { 48 | taskdef(resource: 'com/sun/javafx/tools/ant/antlib.xml', 49 | uri: fx, 50 | classpath: "${project.ext.javafxHome}/tools/ant-javafx.jar") 51 | 52 | "$fx:application"(id: 'app-info', 53 | name: project.name, 54 | mainClass: project.ext.mainClass) 55 | 56 | ext.requiredLibs = configurations.runtime.findAll { File file -> 57 | (!file.name.equals('jfxrt.jar')) 58 | } 59 | copy { 60 | into "build/libs" 61 | from ext.requiredLibs 62 | } 63 | 64 | // So important that the resources fileset jar file must exist in the directory. (jfxgenproj-0.1.jar) 65 | "$fx:jar"(destfile: "build/libs/${project.name}-${project.version}.jar") { 66 | application(refid: 'app-info') 67 | fileset(dir: 'build/resources/main') 68 | fileset(dir: 'build/classes/main') 69 | resources(id: 'images', description: 'runtime classpath') { 70 | requiredLibs.each { File file -> 71 | fileset(dir: 'build/libs', includes: file.name) 72 | } 73 | } 74 | } 75 | 76 | } 77 | } 78 | 79 | task fxJnlp(dependsOn: 'fxjar') << { 80 | def fx = 'javafx:com.sun.javafx.tools.ant' 81 | ant.sequential { 82 | taskdef(resource: 'com/sun/javafx/tools/ant/antlib.xml', 83 | uri: fx, classpath: "${project.ext.javafxHome}/tools/ant-javafx.jar") 84 | 85 | "$fx:application"(id: 'app-info', name: project.name, mainClass: project.ext.mainClass) 86 | 87 | ext.applet_width = '300' 88 | ext.applet_height = '300' 89 | ext.application_title = 'SoundPart5' 90 | ext.application_vendor = 'CarlFX.wordpress.com' 91 | ext.requiredLibs = configurations.runtime.findAll { File file -> (!file.name.equals('jfxrt.jar')) } 92 | "$fx:deploy"( 93 | width: "${ext.applet_width}", 94 | height: "${ext.applet_height}", 95 | outdir: "build/libs", 96 | /*embedJNLP: "true",*/ 97 | outfile: "${ext.application_title}") { 98 | application(refid: 'app-info', mainClass: project.ext.mainClass) 99 | platform(javafx: '2.1+', j2se: '1.7+') 100 | resources(id: 'xyz', description: 'all jars') { 101 | ant.fileset(dir: "build/libs", includes: '*.jar') 102 | } 103 | 104 | info(title: "Sound Manager", vendor: "${ext.application_vendor}") 105 | //permissions(elevated: "true") 106 | } // fx.ant.deploy 107 | 108 | } // ant 109 | 110 | // replace applet end tag with applet-desc 111 | File jnlpFileOrig = new File("build/libs/${ext.application_title}.jnlp") 112 | File jnlpFileTmp = new File("build/libs/${ext.application_title}.jnlp.tmp") 113 | String oldJnlp = jnlpFileOrig.getText() 114 | String newJnlp = oldJnlp.replaceAll("applet>", "applet-desc>") 115 | //println(newJnlp) 116 | //println('======') 117 | // add the host url on the href 118 | def jnlpXml = new XmlParser().parseText(newJnlp) 119 | if (System.properties['hostUrl']) { 120 | jnlpXml.@href = "${System.properties['hostUrl']}/${jnlpXml.@href}" 121 | } 122 | def xmlWriter = new StringWriter() 123 | def xmlNodePrinter = new XmlNodePrinter(new PrintWriter(xmlWriter)) 124 | xmlNodePrinter.setPreserveWhitespace(true) 125 | xmlNodePrinter.print(jnlpXml) 126 | def xmlFinal = xmlWriter.toString() 127 | //println(xmlFinal) 128 | jnlpFileTmp << xmlFinal 129 | jnlpFileOrig.delete() 130 | jnlpFileTmp.renameTo(jnlpFileOrig) 131 | 132 | } 133 | 134 | 135 | defaultTasks 'clean', 'fxJnlp' 136 | -------------------------------------------------------------------------------- /demos/atomsmasher/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin:'application' 3 | apply plugin: 'idea' 4 | project.ext.group = 'carlfx' 5 | project.version = 0.1 6 | project.ext.jdkVersion = 1.7 7 | project.ext.antJavafxJar = 'build/classes/ant-javafx' 8 | project.ext.jfxgenProj = project(':jfxgenproj') 9 | project.ext.javafxHome = System.getenv('JAVAFX_HOME') 10 | 11 | sourceCompatibility = jdkVersion 12 | targetCompatibility = jdkVersion 13 | 14 | configurations { 15 | 16 | } 17 | 18 | repositories { 19 | mavenCentral() 20 | flatDir { 21 | dirs "${project.ext.javafxHome}/tools/" 22 | } 23 | 24 | } 25 | 26 | 27 | dependencies { 28 | compile project(':jfxgenproj') 29 | compile files("${project.ext.javafxHome}/rt/lib/jfxrt.jar") 30 | testCompile 'junit:junit:4.10' 31 | } 32 | 33 | 34 | project.ext.mainClass = "carlfx.demos.atomsmasher.GameLoopPart2" 35 | 36 | task fxjar(dependsOn:[':jfxgenproj:jar', 'classes']) << { 37 | def fx = 'javafx:com.sun.javafx.tools.ant' 38 | ant.sequential{ 39 | taskdef(resource:'com/sun/javafx/tools/ant/antlib.xml', 40 | uri:fx, classpath: "${project.ext.javafxHome}/tools/ant-javafx.jar") 41 | 42 | "$fx:application"(id:'app-info', name:project.name, mainClass:project.ext.mainClass) 43 | 44 | ext.requiredLibs = configurations.runtime.findAll { File file -> (!file.name.equals('jfxrt.jar')) } 45 | copy { 46 | into "build/libs" 47 | from ext.requiredLibs 48 | } 49 | 50 | // So important that the resources fileset jar file must exist in the directory. (jfxgenproj-0.1.jar) 51 | "$fx:jar"(destfile:"build/libs/${project.name}-${project.version}.jar"){ 52 | application(refid:'app-info') 53 | fileset(dir:'build/classes/main') 54 | resources (id:'xyz', description:'runtime classpath'){ 55 | requiredLibs.each { File file -> 56 | fileset(dir:'build/libs', includes: file.name) 57 | } 58 | } 59 | } 60 | 61 | } 62 | } 63 | 64 | task fxJnlp(dependsOn:'fxjar') << { 65 | def fx = 'javafx:com.sun.javafx.tools.ant' 66 | ant.sequential{ 67 | taskdef(resource:'com/sun/javafx/tools/ant/antlib.xml', 68 | uri:fx, classpath: "${project.ext.javafxHome}/tools/ant-javafx.jar") 69 | 70 | "$fx:application"(id:'app-info', name:project.name, mainClass:project.ext.mainClass) 71 | 72 | ext.applet_width = '300' 73 | ext.applet_height = '300' 74 | ext.application_title = 'GameLoopPart2' 75 | ext.application_vendor = 'CarlFX.wordpress.com' 76 | ext.requiredLibs = configurations.runtime.findAll { File file -> (!file.name.equals('jfxrt.jar')) } 77 | "$fx:deploy"( 78 | width: "${ext.applet_width}", 79 | height: "${ext.applet_height}", 80 | outdir: "build/libs", 81 | /*embedJNLP: "true",*/ 82 | outfile: "${ext.application_title}") { 83 | application(refid: 'app-info', mainClass:project.ext.mainClass) 84 | platform(javafx: '2.1+', j2se: '1.7+') 85 | resources (id:'xyz', description:'all jars'){ 86 | ant.fileset(dir: "build/libs", includes: '*.jar') 87 | } 88 | 89 | info(title:"Atom Smasher", 90 | vendor: "${ext.application_vendor}") 91 | //permissions(elevated: "true") 92 | } // fx.ant.deploy 93 | 94 | } // ant 95 | 96 | 97 | // replace applet end tag with applet-desc 98 | File jnlpFileOrig = new File("build/libs/${ext.application_title}.jnlp") 99 | File jnlpFileTmp = new File("build/libs/${ext.application_title}.jnlp.tmp") 100 | String oldJnlp = jnlpFileOrig.getText() 101 | String newJnlp = oldJnlp.replaceAll("applet>", "applet-desc>") 102 | //println(newJnlp) 103 | //println('======') 104 | // add the host url on the href 105 | def jnlpXml = new XmlParser().parseText(newJnlp) 106 | if (System.properties['hostUrl']) { 107 | jnlpXml.@href = "${System.properties['hostUrl']}/${jnlpXml.@href}" 108 | } 109 | def xmlWriter = new StringWriter() 110 | def xmlNodePrinter = new XmlNodePrinter(new PrintWriter(xmlWriter)) 111 | xmlNodePrinter.setPreserveWhitespace(true) 112 | xmlNodePrinter.print(jnlpXml) 113 | def xmlFinal = xmlWriter.toString() 114 | //println(xmlFinal) 115 | jnlpFileTmp << xmlFinal 116 | jnlpFileOrig.delete() 117 | jnlpFileTmp.renameTo(jnlpFileOrig) 118 | 119 | } 120 | 121 | 122 | task fxJnlpTest() << { 123 | 124 | ext.applet_width = '300' 125 | ext.applet_height = '300' 126 | ext.application_title = 'GameLoopPart2' 127 | ext.application_vendor = 'CarlFX.wordpress.com' 128 | 129 | // replace applet end tag with applet-desc 130 | File jnlpFileOrig = new File("build/libs/${ext.application_title}.jnlp") 131 | File jnlpFileTmp = new File("build/libs/${ext.application_title}.jnlp.tmp") 132 | String oldJnlp = jnlpFileOrig.getText() 133 | String newJnlp = oldJnlp.replaceAll("applet>", "applet-desc>") 134 | println(newJnlp) 135 | println('======') 136 | // add the host url on the href 137 | def jnlpXml = new XmlParser().parseText(newJnlp) 138 | jnlpXml.@href = "http://www.jroller.com/carldea/resource/${jnlpXml.@href}" 139 | 140 | 141 | 142 | def xmlWriter = new StringWriter() 143 | def xmlNodePrinter = new XmlNodePrinter(new PrintWriter(xmlWriter)) 144 | xmlNodePrinter.setPreserveWhitespace(true) 145 | xmlNodePrinter.print(jnlpXml) 146 | def xmlFinal = xmlWriter.toString() 147 | println(xmlFinal) 148 | jnlpFileTmp << xmlFinal 149 | jnlpFileOrig.delete() 150 | jnlpFileTmp.renameTo(jnlpFileOrig) 151 | 152 | 153 | } 154 | 155 | 156 | defaultTasks 'clean', 'fxJnlp' 157 | -------------------------------------------------------------------------------- /demos/atomsmasher/src/main/java/carlfx/demo/atomsmasher/AtomSmasher.java: -------------------------------------------------------------------------------- 1 | package carlfx.demos.atomsmasher; 2 | 3 | import carlfx.gameengine.GameWorld; 4 | import carlfx.gameengine.Sprite; 5 | import java.util.Random; 6 | import javafx.animation.Timeline; 7 | import javafx.event.EventHandler; 8 | import javafx.scene.Group; 9 | import javafx.scene.Scene; 10 | import javafx.scene.control.ButtonBuilder; 11 | import javafx.scene.control.Label; 12 | import javafx.scene.input.MouseEvent; 13 | import javafx.scene.layout.HBoxBuilder; 14 | import javafx.scene.layout.VBox; 15 | import javafx.scene.layout.VBoxBuilder; 16 | import javafx.scene.paint.Color; 17 | import javafx.scene.shape.Circle; 18 | import javafx.stage.Stage; 19 | import static javafx.animation.Animation.Status.RUNNING; 20 | import static javafx.animation.Animation.Status.STOPPED; 21 | 22 | /** 23 | * This is a simple game world simulating a bunch of spheres looking 24 | * like atomic particles colliding with each other. When the game loop begins 25 | * the user will notice random spheres (atomic particles) floating and 26 | * colliding. The user is able to press a button to generate more 27 | * atomic particles. Also, the user can freeze the game. 28 | * 29 | * @author cdea 30 | */ 31 | public class AtomSmasher extends GameWorld { 32 | /** Read only field to show the number of sprite objects are on the field*/ 33 | private final static Label NUM_SPRITES_FIELD = new Label(); 34 | 35 | public AtomSmasher(int fps, String title){ 36 | super(fps, title); 37 | } 38 | 39 | /** 40 | * Initialize the game world by adding sprite objects. 41 | * @param primaryStage 42 | */ 43 | @Override 44 | public void initialize(final Stage primaryStage) { 45 | // Sets the window title 46 | primaryStage.setTitle(getWindowTitle()); 47 | 48 | // Create the scene 49 | setSceneNodes(new Group()); 50 | setGameSurface(new Scene(getSceneNodes(), 640, 580)); 51 | primaryStage.setScene(getGameSurface()); 52 | 53 | // Create many spheres 54 | generateManySpheres(150); 55 | 56 | // Display the number of spheres visible. 57 | // Create a button to add more spheres. 58 | // Create a button to freeze the game loop. 59 | final Timeline gameLoop = getGameLoop(); 60 | VBox stats = VBoxBuilder.create() 61 | .spacing(5) 62 | .translateX(10) 63 | .translateY(10) 64 | .children(HBoxBuilder.create() 65 | .spacing(5) 66 | .children(new Label("Number of Particles: "), // show no. particles 67 | NUM_SPRITES_FIELD).build(), 68 | 69 | // button to build more spheres 70 | ButtonBuilder.create() 71 | .text("Regenerate") 72 | .onMousePressed(new EventHandler() { 73 | @Override 74 | public void handle(MouseEvent arg0) { 75 | generateManySpheres(150); 76 | }}).build(), 77 | 78 | // button to freeze game loop 79 | ButtonBuilder.create() 80 | .text("Freeze/Resume") 81 | .onMousePressed(new EventHandler() { 82 | 83 | @Override 84 | public void handle(MouseEvent arg0) { 85 | switch (gameLoop.getStatus()) { 86 | case RUNNING: 87 | gameLoop.stop(); 88 | break; 89 | case STOPPED: 90 | gameLoop.play(); 91 | break; 92 | } 93 | }}).build() 94 | ).build(); // (VBox) stats on children 95 | 96 | // lay down the controls 97 | getSceneNodes().getChildren().add(stats); 98 | } 99 | 100 | 101 | /** 102 | * Make some more space spheres (Atomic particles) 103 | */ 104 | private void generateManySpheres(int numSpheres) { 105 | Random rnd = new Random(); 106 | Scene gameSurface = getGameSurface(); 107 | for (int i=0; i (gameSurface.getWidth() - (circle.getRadius() * 2))) { 121 | newX = gameSurface.getWidth() - (circle.getRadius() * 2); 122 | } 123 | 124 | // check for the bottom of screen the height newY is greater than height 125 | // minus radius times 2(height of sprite) 126 | double newY = rnd.nextInt((int) gameSurface.getHeight()); 127 | if (newY > (gameSurface.getHeight() - (circle.getRadius() * 2))) { 128 | newY = gameSurface.getHeight() - (circle.getRadius() * 2); 129 | } 130 | 131 | circle.setTranslateX(newX); 132 | circle.setTranslateY(newY); 133 | circle.setVisible(true); 134 | circle.setId(b.toString()); 135 | 136 | // add to actors in play (sprite objects) 137 | getSpriteManager().addSprites(b); 138 | 139 | // add sprite's 140 | getSceneNodes().getChildren().add(0, b.node); 141 | 142 | } 143 | } 144 | 145 | /** 146 | * Each sprite will update it's velocity and bounce off wall borders. 147 | * @param sprite - An atomic particle (a sphere). 148 | */ 149 | @Override 150 | protected void handleUpdate(Sprite sprite) { 151 | if (sprite instanceof Atom) { 152 | Atom sphere = (Atom) sprite; 153 | 154 | // advance the spheres velocity 155 | sphere.update(); 156 | 157 | // bounce off the walls when outside of boundaries 158 | if (sphere.node.getTranslateX() > (getGameSurface().getWidth() - 159 | sphere.node.getBoundsInParent().getWidth()) || 160 | sphere.node.getTranslateX() < 0 ) { sphere.vX = sphere.vX * -1; } if (sphere.node.getTranslateY() > getGameSurface().getHeight()- 161 | sphere.node.getBoundsInParent().getHeight() || 162 | sphere.node.getTranslateY() < 0) { 163 | sphere.vY = sphere.vY * -1; 164 | } 165 | } 166 | } 167 | 168 | /** 169 | * How to handle the collision of two sprite objects. Stops the particle 170 | * by zeroing out the velocity if a collision occurred. 171 | * @param spriteA 172 | * @param spriteB 173 | * @return 174 | */ 175 | @Override 176 | protected boolean handleCollision(Sprite spriteA, Sprite spriteB) { 177 | if (spriteA.collide(spriteB)) { 178 | ((Atom)spriteA).implode(this); 179 | ((Atom)spriteB).implode(this); 180 | getSpriteManager().addSpritesToBeRemoved(spriteA, spriteB); 181 | return true; 182 | } 183 | return false; 184 | } 185 | 186 | /** 187 | * Remove dead things. 188 | */ 189 | @Override 190 | protected void cleanupSprites() { 191 | // removes from the scene and backend store 192 | super.cleanupSprites(); 193 | 194 | // let user know how many sprites are showing. 195 | NUM_SPRITES_FIELD.setText(String.valueOf(getSpriteManager().getAllSprites().size())); 196 | 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /jfxgenproj/src/main/java/carlfx/gameengine/GameWorld.java: -------------------------------------------------------------------------------- 1 | package carlfx.gameengine; 2 | 3 | import javafx.animation.Animation; 4 | import javafx.animation.KeyFrame; 5 | import javafx.animation.Timeline; 6 | import javafx.animation.TimelineBuilder; 7 | import javafx.event.ActionEvent; 8 | import javafx.event.EventHandler; 9 | import javafx.scene.Group; 10 | import javafx.scene.Scene; 11 | import javafx.stage.Stage; 12 | import javafx.util.Duration; 13 | 14 | /** 15 | * This application demonstrates a JavaFX 2.x Game Loop. 16 | * Shown below are the methods which comprise of the fundamentals to a 17 | * simple game loop in JavaFX: 18 | *
 19 |  *  initialize() - Initialize the game world.
 20 |  *  beginGameLoop() - Creates a JavaFX Timeline object containing the game life cycle.
 21 |  *  updateSprites() - Updates the sprite objects each period (per frame)
 22 |  *  checkCollisions() - Method will determine objects that collide with each other.
 23 |  *  cleanupSprites() - Any sprite objects needing to be removed from play.
 24 |  * 
25 | * 26 | * @author cdea 27 | */ 28 | public abstract class GameWorld { 29 | 30 | /** 31 | * The JavaFX Scene as the game surface 32 | */ 33 | private Scene gameSurface; 34 | /** 35 | * All nodes to be displayed in the game window. 36 | */ 37 | private Group sceneNodes; 38 | /** 39 | * The game loop using JavaFX's Timeline API. 40 | */ 41 | private static Timeline gameLoop; 42 | 43 | /** 44 | * Number of frames per second. 45 | */ 46 | private final int framesPerSecond; 47 | 48 | /** 49 | * Title in the application window. 50 | */ 51 | private final String windowTitle; 52 | 53 | /** 54 | * The sprite manager. 55 | */ 56 | private final SpriteManager spriteManager = new SpriteManager(); 57 | 58 | private final SoundManager soundManager = new SoundManager(3); 59 | 60 | /** 61 | * Constructor that is called by the derived class. This will 62 | * set the frames per second, title, and setup the game loop. 63 | * 64 | * @param fps - Frames per second. 65 | * @param title - Title of the application window. 66 | */ 67 | public GameWorld(final int fps, final String title) { 68 | framesPerSecond = fps; 69 | windowTitle = title; 70 | // create and set timeline for the game loop 71 | buildAndSetGameLoop(); 72 | } 73 | 74 | /** 75 | * Builds and sets the game loop ready to be started. 76 | */ 77 | protected final void buildAndSetGameLoop() { 78 | 79 | final Duration oneFrameAmt = Duration.millis(1000 / (float) getFramesPerSecond()); 80 | final KeyFrame oneFrame = new KeyFrame(oneFrameAmt, 81 | new EventHandler() { 82 | 83 | @Override 84 | public void handle(javafx.event.ActionEvent event) { 85 | 86 | // update actors 87 | updateSprites(); 88 | 89 | // check for collision 90 | checkCollisions(); 91 | 92 | // removed dead things 93 | cleanupSprites(); 94 | 95 | } 96 | }); // oneFrame 97 | 98 | // sets the game world's game loop (Timeline) 99 | setGameLoop(TimelineBuilder.create() 100 | .cycleCount(Animation.INDEFINITE) 101 | .keyFrames(oneFrame) 102 | .build()); 103 | } 104 | 105 | /** 106 | * Initialize the game world by update the JavaFX Stage. 107 | * 108 | * @param primaryStage The main window containing the JavaFX Scene. 109 | */ 110 | public abstract void initialize(final Stage primaryStage); 111 | 112 | /** 113 | * Kicks off (plays) the Timeline objects containing one key frame 114 | * that simply runs indefinitely with each frame invoking a method 115 | * to update sprite objects, check for collisions, and cleanup sprite 116 | * objects. 117 | */ 118 | public void beginGameLoop() { 119 | getGameLoop().play(); 120 | } 121 | 122 | /** 123 | * Updates each game sprite in the game world. This method will 124 | * loop through each sprite and passing it to the handleUpdate() 125 | * method. The derived class should override handleUpdate() method. 126 | */ 127 | protected void updateSprites() { 128 | for (Sprite sprite : spriteManager.getAllSprites()) { 129 | handleUpdate(sprite); 130 | } 131 | } 132 | 133 | /** 134 | * Updates the sprite object's information to position on the game surface. 135 | * 136 | * @param sprite - The sprite to update. 137 | */ 138 | protected void handleUpdate(Sprite sprite) { 139 | } 140 | 141 | /** 142 | * Checks each game sprite in the game world to determine a collision 143 | * occurred. The method will loop through each sprite and 144 | * passing it to the handleCollision() 145 | * method. The derived class should override handleCollision() method. 146 | */ 147 | protected void checkCollisions() { 148 | // check other sprite's collisions 149 | spriteManager.resetCollisionsToCheck(); 150 | // check each sprite against other sprite objects. 151 | for (Sprite spriteA : spriteManager.getCollisionsToCheck()) { 152 | for (Sprite spriteB : spriteManager.getAllSprites()) { 153 | if (handleCollision(spriteA, spriteB)) { 154 | // The break helps optimize the collisions 155 | // The break statement means one object only hits another 156 | // object as opposed to one hitting many objects. 157 | // To be more accurate comment out the break statement. 158 | break; 159 | } 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * When two objects collide this method can handle the passed in sprite 166 | * objects. By default it returns false, meaning the objects do not 167 | * collide. 168 | * 169 | * @param spriteA - called from checkCollision() method to be compared. 170 | * @param spriteB - called from checkCollision() method to be compared. 171 | * @return boolean True if the objects collided, otherwise false. 172 | */ 173 | protected boolean handleCollision(Sprite spriteA, Sprite spriteB) { 174 | return false; 175 | } 176 | 177 | /** 178 | * Sprites to be cleaned up. 179 | */ 180 | protected void cleanupSprites() { 181 | spriteManager.cleanupSprites(); 182 | } 183 | 184 | /** 185 | * Returns the frames per second. 186 | * 187 | * @return int The frames per second. 188 | */ 189 | protected int getFramesPerSecond() { 190 | return framesPerSecond; 191 | } 192 | 193 | /** 194 | * Returns the game's window title. 195 | * 196 | * @return String The game's window title. 197 | */ 198 | public String getWindowTitle() { 199 | return windowTitle; 200 | } 201 | 202 | /** 203 | * The game loop (Timeline) which is used to update, check collisions, and 204 | * cleanup sprite objects at every interval (fps). 205 | * 206 | * @return Timeline An animation running indefinitely representing the game 207 | * loop. 208 | */ 209 | protected static Timeline getGameLoop() { 210 | return gameLoop; 211 | } 212 | 213 | /** 214 | * The sets the current game loop for this game world. 215 | * 216 | * @param gameLoop Timeline object of an animation running indefinitely 217 | * representing the game loop. 218 | */ 219 | protected static void setGameLoop(Timeline gameLoop) { 220 | GameWorld.gameLoop = gameLoop; 221 | } 222 | 223 | /** 224 | * Returns the sprite manager containing the sprite objects to 225 | * manipulate in the game. 226 | * 227 | * @return SpriteManager The sprite manager. 228 | */ 229 | public SpriteManager getSpriteManager() { 230 | return spriteManager; 231 | } 232 | 233 | /** 234 | * Returns the JavaFX Scene. This is called the game surface to 235 | * allow the developer to add JavaFX Node objects onto the Scene. 236 | * 237 | * @return Scene The JavaFX scene graph. 238 | */ 239 | public Scene getGameSurface() { 240 | return gameSurface; 241 | } 242 | 243 | /** 244 | * Sets the JavaFX Scene. This is called the game surface to 245 | * allow the developer to add JavaFX Node objects onto the Scene. 246 | * 247 | * @param gameSurface The main game surface (JavaFX Scene). 248 | */ 249 | protected void setGameSurface(Scene gameSurface) { 250 | this.gameSurface = gameSurface; 251 | } 252 | 253 | /** 254 | * All JavaFX nodes which are rendered onto the game surface(Scene) is 255 | * a JavaFX Group object. 256 | * 257 | * @return Group The root containing many child nodes to be displayed into 258 | * the Scene area. 259 | */ 260 | public Group getSceneNodes() { 261 | return sceneNodes; 262 | } 263 | 264 | /** 265 | * Sets the JavaFX Group that will hold all JavaFX nodes which are rendered 266 | * onto the game surface(Scene) is a JavaFX Group object. 267 | * 268 | * @param sceneNodes The root container having many children nodes 269 | * to be displayed into the Scene area. 270 | */ 271 | protected void setSceneNodes(Group sceneNodes) { 272 | this.sceneNodes = sceneNodes; 273 | } 274 | 275 | protected SoundManager getSoundManager() { 276 | return soundManager; 277 | } 278 | 279 | /** 280 | * Stop threads and stop media from playing. 281 | */ 282 | public void shutdown() { 283 | getGameLoop().stop(); 284 | getSoundManager().shutdown(); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /demos/navigateship/src/main/java/carlfx/demos/navigateship/TheExpanse.java: -------------------------------------------------------------------------------- 1 | package carlfx.demos.navigateship; 2 | 3 | import carlfx.gameengine.GameWorld; 4 | import carlfx.gameengine.Sprite; 5 | import javafx.event.ActionEvent; 6 | import javafx.event.EventHandler; 7 | import javafx.scene.CacheHint; 8 | import javafx.scene.Group; 9 | import javafx.scene.Node; 10 | import javafx.scene.Scene; 11 | import javafx.scene.control.Button; 12 | import javafx.scene.control.Label; 13 | import javafx.scene.control.TextField; 14 | import javafx.scene.input.KeyCode; 15 | import javafx.scene.input.KeyEvent; 16 | import javafx.scene.input.MouseButton; 17 | import javafx.scene.input.MouseEvent; 18 | import javafx.scene.layout.HBox; 19 | import javafx.scene.layout.VBox; 20 | import javafx.scene.paint.Color; 21 | import javafx.scene.shape.Circle; 22 | import javafx.stage.Stage; 23 | 24 | import java.util.Random; 25 | 26 | /** 27 | * This is a simple game world simulating a bunch of spheres looking 28 | * like atomic particles colliding with each other. When the game loop begins 29 | * the user will notice random spheres (atomic particles) floating and 30 | * colliding. The user will navigate his/her ship by right clicking the mouse to 31 | * trust forward and left click to fire weapon to atoms. 32 | * 33 | * @author cdea 34 | */ 35 | public class TheExpanse extends GameWorld { 36 | 37 | // mouse pt label 38 | Label mousePtLabel = new Label(); 39 | 40 | // mouse press pt label 41 | Label mousePressPtLabel = new Label(); 42 | 43 | TextField xCoordinate = new TextField("234"); 44 | TextField yCoordinate = new TextField("200"); 45 | Button moveShipButton = new Button("Rotate ship"); 46 | 47 | Ship myShip = new Ship(); 48 | 49 | public TheExpanse(int fps, String title) { 50 | super(fps, title); 51 | } 52 | 53 | /** 54 | * Initialize the game world by adding sprite objects. 55 | * 56 | * @param primaryStage The game window or primary stage. 57 | */ 58 | @Override 59 | public void initialize(final Stage primaryStage) { 60 | // Sets the window title 61 | primaryStage.setTitle(getWindowTitle()); 62 | //primaryStage.setFullScreen(true); 63 | 64 | // Create the scene 65 | setSceneNodes(new Group()); 66 | setGameSurface(new Scene(getSceneNodes(), 800, 600)); 67 | getGameSurface().setFill(Color.BLACK); 68 | primaryStage.setScene(getGameSurface()); 69 | // Setup Game input 70 | setupInput(primaryStage); 71 | 72 | 73 | // Create many spheres 74 | generateManySpheres(2); 75 | 76 | // Display the number of spheres visible. 77 | // Create a button to add more spheres. 78 | // Create a button to freeze the game loop. 79 | //final Timeline gameLoop = getGameLoop(); 80 | getSpriteManager().addSprites(myShip); 81 | getSceneNodes().getChildren().add(0, myShip.node); 82 | 83 | // mouse point 84 | VBox stats = new VBox(); 85 | 86 | HBox row1 = new HBox(); 87 | mousePtLabel.setTextFill(Color.WHITE); 88 | row1.getChildren().add(mousePtLabel); 89 | HBox row2 = new HBox(); 90 | mousePressPtLabel.setTextFill(Color.WHITE); 91 | row2.getChildren().add(mousePressPtLabel); 92 | 93 | stats.getChildren().add(row1); 94 | stats.getChildren().add(row2); 95 | 96 | // mouse point 97 | HBox enterCoord1 = new HBox(); 98 | enterCoord1.getChildren().add(xCoordinate); 99 | enterCoord1.getChildren().add(yCoordinate); 100 | enterCoord1.getChildren().add(moveShipButton); 101 | stats.getChildren().add(enterCoord1); 102 | moveShipButton.setOnAction(new EventHandler() { 103 | @Override 104 | public void handle(ActionEvent actionEvent) { 105 | double x = Double.parseDouble(xCoordinate.getText()); 106 | double y = Double.parseDouble(yCoordinate.getText()); 107 | myShip.plotCourse(x, y, false); 108 | } 109 | }); 110 | 111 | // load sound file 112 | getSoundManager().loadSoundEffects("laser", getClass().getClassLoader().getResource("laser_2.mp3")); 113 | 114 | // =================================================== 115 | // Debugging purposes 116 | // uncomment to test mouse press and rotation angles. 117 | //getSceneNodes().getChildren().add(stats); 118 | } 119 | 120 | /** 121 | * Sets up the mouse input. 122 | * 123 | * @param primaryStage The primary stage (app window). 124 | */ 125 | private void setupInput(Stage primaryStage) { 126 | System.out.println("Ship's center is (" + myShip.getCenterX() + ", " + myShip.getCenterY() + ")"); 127 | 128 | EventHandler fireOrMove = new EventHandler() { 129 | @Override 130 | public void handle(MouseEvent event) { 131 | mousePressPtLabel.setText("Mouse Press PT = (" + event.getX() + ", " + event.getY() + ")"); 132 | if (event.getButton() == MouseButton.PRIMARY) { 133 | // Aim 134 | myShip.plotCourse(event.getX(), event.getY(), false); 135 | // fire 136 | Missile m1 = myShip.fire(); 137 | getSpriteManager().addSprites(m1); 138 | 139 | // play sound 140 | getSoundManager().playSound("laser"); 141 | 142 | getSceneNodes().getChildren().add(0, m1.node); 143 | 144 | 145 | } else if (event.getButton() == MouseButton.SECONDARY) { 146 | // determine when all atoms are not on the game surface. Ship should be one sprite left. 147 | if (getSpriteManager().getAllSprites().size() <= 1) { 148 | generateManySpheres(30); 149 | } 150 | 151 | // stop ship from moving forward 152 | myShip.applyTheBrakes(event.getX(), event.getY()); 153 | // move forward and rotate ship 154 | myShip.plotCourse(event.getX(), event.getY(), true); 155 | } 156 | 157 | } 158 | }; 159 | 160 | 161 | // Initialize input 162 | primaryStage.getScene().setOnMousePressed(fireOrMove); 163 | //addEventHandler(MouseEvent.MOUSE_PRESSED, me); 164 | 165 | // set up stats 166 | EventHandler changeWeapons = new EventHandler() { 167 | @Override 168 | public void handle(KeyEvent event) { 169 | if (KeyCode.SPACE == event.getCode()) { 170 | myShip.shieldToggle(); 171 | return; 172 | } 173 | myShip.changeWeapon(event.getCode()); 174 | 175 | } 176 | }; 177 | primaryStage.getScene().setOnKeyPressed(changeWeapons); 178 | 179 | 180 | // set up stats 181 | EventHandler showMouseMove = new EventHandler() { 182 | @Override 183 | public void handle(MouseEvent event) { 184 | mousePtLabel.setText("Mouse PT = (" + event.getX() + ", " + event.getY() + ")"); 185 | } 186 | }; 187 | 188 | primaryStage.getScene().setOnMouseMoved(showMouseMove); 189 | } 190 | 191 | /** 192 | * Make some more space spheres (Atomic particles) 193 | * 194 | * @param numSpheres The number of random sized, color, and velocity atoms to generate. 195 | */ 196 | private void generateManySpheres(int numSpheres) { 197 | Random rnd = new Random(); 198 | Scene gameSurface = getGameSurface(); 199 | for (int i = 0; i < numSpheres; i++) { 200 | Color c = Color.rgb(rnd.nextInt(255), rnd.nextInt(255), rnd.nextInt(255)); 201 | Atom b = new Atom(rnd.nextInt(15) + 5, c, true); 202 | Circle circle = b.getAsCircle(); 203 | // random 0 to 2 + (.0 to 1) * random (1 or -1) 204 | b.vX = (rnd.nextInt(2) + rnd.nextDouble()) * (rnd.nextBoolean() ? 1 : -1); 205 | b.vY = (rnd.nextInt(2) + rnd.nextDouble()) * (rnd.nextBoolean() ? 1 : -1); 206 | 207 | // random x between 0 to width of scene 208 | double newX = rnd.nextInt((int) gameSurface.getWidth()); 209 | 210 | // check for the right of the width newX is greater than width 211 | // minus radius times 2(width of sprite) 212 | if (newX > (gameSurface.getWidth() - (circle.getRadius() * 2))) { 213 | newX = gameSurface.getWidth() - (circle.getRadius() * 2); 214 | } 215 | 216 | // check for the bottom of screen the height newY is greater than height 217 | // minus radius times 2(height of sprite) 218 | double newY = rnd.nextInt((int) gameSurface.getHeight()); 219 | if (newY > (gameSurface.getHeight() - (circle.getRadius() * 2))) { 220 | newY = gameSurface.getHeight() - (circle.getRadius() * 2); 221 | } 222 | 223 | circle.setTranslateX(newX); 224 | circle.setTranslateY(newY); 225 | circle.setVisible(true); 226 | circle.setId(b.toString()); 227 | circle.setCache(true); 228 | circle.setCacheHint(CacheHint.SPEED); 229 | circle.setManaged(false); 230 | // add to actors in play (sprite objects) 231 | getSpriteManager().addSprites(b); 232 | 233 | // add sprite's 234 | getSceneNodes().getChildren().add(b.node); 235 | 236 | } 237 | } 238 | 239 | /** 240 | * Each sprite will update it's velocity and bounce off wall borders. 241 | * 242 | * @param sprite - An atomic particle (a sphere). 243 | */ 244 | @Override 245 | protected void handleUpdate(Sprite sprite) { 246 | // advance object 247 | sprite.update(); 248 | if (sprite instanceof Missile) { 249 | removeMissiles((Missile) sprite); 250 | } else { 251 | bounceOffWalls(sprite); 252 | } 253 | } 254 | 255 | /** 256 | * Change the direction of the moving object when it encounters the walls. 257 | * 258 | * @param sprite The sprite to update based on the wall boundaries. 259 | * TODO The ship has got issues. 260 | */ 261 | private void bounceOffWalls(Sprite sprite) { 262 | // bounce off the walls when outside of boundaries 263 | 264 | Node displayNode; 265 | if (sprite instanceof Ship) { 266 | displayNode = sprite.node;//((Ship)sprite).getCurrentShipImage(); 267 | } else { 268 | displayNode = sprite.node; 269 | } 270 | // Get the group node's X and Y but use the ImageView to obtain the width. 271 | if (sprite.node.getTranslateX() > (getGameSurface().getWidth() - displayNode.getBoundsInParent().getWidth()) || 272 | displayNode.getTranslateX() < 0) { 273 | 274 | // bounce the opposite direction 275 | sprite.vX = sprite.vX * -1; 276 | 277 | } 278 | // Get the group node's X and Y but use the ImageView to obtain the height. 279 | if (sprite.node.getTranslateY() > getGameSurface().getHeight() - displayNode.getBoundsInParent().getHeight() || 280 | sprite.node.getTranslateY() < 0) { 281 | sprite.vY = sprite.vY * -1; 282 | } 283 | } 284 | 285 | /** 286 | * Remove missiles when they reach the wall boundaries. 287 | * 288 | * @param missile The missile to remove based on the wall boundaries. 289 | */ 290 | private void removeMissiles(Missile missile) { 291 | // bounce off the walls when outside of boundaries 292 | if (missile.node.getTranslateX() > (getGameSurface().getWidth() - 293 | missile.node.getBoundsInParent().getWidth()) || 294 | missile.node.getTranslateX() < 0) { 295 | 296 | getSpriteManager().addSpritesToBeRemoved(missile); 297 | getSceneNodes().getChildren().remove(missile.node); 298 | 299 | } 300 | if (missile.node.getTranslateY() > getGameSurface().getHeight() - 301 | missile.node.getBoundsInParent().getHeight() || 302 | missile.node.getTranslateY() < 0) { 303 | 304 | getSpriteManager().addSpritesToBeRemoved(missile); 305 | getSceneNodes().getChildren().remove(missile.node); 306 | } 307 | } 308 | 309 | /** 310 | * How to handle the collision of two sprite objects. Stops the particle 311 | * by zeroing out the velocity if a collision occurred. 312 | * 313 | * @param spriteA Sprite from the first list. 314 | * @param spriteB Sprite from the second list. 315 | * @return boolean returns a true if the two sprites have collided otherwise false. 316 | */ 317 | @Override 318 | protected boolean handleCollision(Sprite spriteA, Sprite spriteB) { 319 | if (spriteA != spriteB) { 320 | if (spriteA.collide(spriteB)) { 321 | 322 | if (spriteA != myShip) { 323 | spriteA.handleDeath(this); 324 | } 325 | if (spriteB != myShip) { 326 | spriteB.handleDeath(this); 327 | } 328 | } 329 | } 330 | 331 | return false; 332 | } 333 | 334 | } 335 | -------------------------------------------------------------------------------- /demos/navigateship/src/main/java/carlfx/demos/navigateship/Ship.java: -------------------------------------------------------------------------------- 1 | package carlfx.demos.navigateship; 2 | 3 | import carlfx.gameengine.Sprite; 4 | import javafx.animation.*; 5 | import javafx.event.ActionEvent; 6 | import javafx.event.EventHandler; 7 | import javafx.scene.CacheHint; 8 | import javafx.scene.Group; 9 | import javafx.scene.Node; 10 | import javafx.scene.image.Image; 11 | import javafx.scene.input.KeyCode; 12 | import javafx.scene.paint.Color; 13 | import javafx.scene.shape.Circle; 14 | import javafx.scene.shape.CircleBuilder; 15 | import javafx.util.Duration; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | /** 21 | * A space ship with 32 directions 22 | * When two atoms collide each will fade and become removed from the scene. The 23 | * method called implode() implements a fade transition effect. 24 | * 25 | * @author cdea 26 | */ 27 | public class Ship extends Sprite { 28 | 29 | /** 30 | * 360 degree turn 31 | */ 32 | private final static int TWO_PI_DEGREES = 360; 33 | 34 | /** 35 | * Number of ship frames and directions the ship is pointing nose 36 | */ 37 | private final static int NUM_DIRECTIONS = 32; 38 | 39 | /** 40 | * The angle of one direction (adjacent directions) (11.25 degrees) 41 | */ 42 | private final static float UNIT_ANGLE_PER_FRAME = ((float) TWO_PI_DEGREES / NUM_DIRECTIONS); 43 | 44 | /** 45 | * Amount of time it takes the ship to move 180 degrees in milliseconds. 46 | */ 47 | private final static int MILLIS_TURN_SHIP_180_DEGREES = 300; 48 | 49 | /** 50 | * When the ship turns on each direction one amount of time for one frame or turn of the ship. (18.75 milliseconds) 51 | */ 52 | private final static float MILLIS_PER_FRAME = (float) MILLIS_TURN_SHIP_180_DEGREES / (NUM_DIRECTIONS / 2); 53 | 54 | /** 55 | * All possible turn directions Clockwise, Counter Clockwise, or Neither when the user clicks mouse around ship 56 | */ 57 | private enum DIRECTION { 58 | CLOCKWISE, COUNTER_CLOCKWISE, NEITHER 59 | } 60 | 61 | /** 62 | * Velocity amount used vector when ship moves forward. scale vector of ship. See flipBook translateX and Y. 63 | */ 64 | private final static float THRUST_AMOUNT = 4.3f; 65 | 66 | /***/ 67 | private final static float MISSILE_THRUST_AMOUNT = 6.3F; 68 | 69 | /** 70 | * Angle in degrees to rotate ship. 71 | */ 72 | 73 | 74 | /** 75 | * Current turning direction. default is NEITHER. Clockwise and Counter Clockwise. 76 | */ 77 | private DIRECTION turnDirection = DIRECTION.NEITHER; 78 | 79 | /** 80 | * The current starting position of the vector or coordinate where the nose of the ship is pointing towards. 81 | */ 82 | private Vec u; // current or start vector 83 | 84 | /** 85 | * All ImageViews of all the possible image frames for each direction the ship is pointing. ie: 32 directions. 86 | */ 87 | private final List directionalShips = new ArrayList<>(); 88 | 89 | /** 90 | * The Timeline instance to animate the ship rotating using images. This is an optical illusion similar to page 91 | * flipping as each frame is displayed the previous visible attribute is set to false. No rotation is happening. 92 | */ 93 | private Timeline rotateShipTimeline; 94 | 95 | /** 96 | * The current index into the list of ImageViews representing each direction of the ship. Zero is the ship 97 | * pointing to the right or zero degrees. 98 | */ 99 | private int uIndex = 0; 100 | 101 | /** 102 | * The end index into the list of ImageViews representing each direction of the ship. Zero is the ship 103 | * pointing to the right or zero degrees. 104 | */ 105 | private int vIndex = 0; 106 | 107 | /** 108 | * The spot where the user has right clicked letting the engine check the ship's center is in this area. 109 | */ 110 | private final Circle stopArea = new Circle(); 111 | 112 | /** 113 | * A group contain all of the ship image view nodes. 114 | */ 115 | private final Group flipBook = new Group(); 116 | 117 | /** 118 | * A key code will be used for weapon selection. 119 | */ 120 | private KeyCode keyCode; 121 | 122 | /** 123 | * Turn shields on 124 | */ 125 | private boolean shieldOn; 126 | 127 | /** 128 | * Green shield to be used as collision bounds. 129 | */ 130 | private Circle shield; 131 | 132 | /** 133 | * A fade effect while the shields are up momentarily 134 | */ 135 | FadeTransition shieldFade; 136 | 137 | /** 138 | * The collision bounding region for the ship 139 | */ 140 | private Circle hitBounds; 141 | 142 | public Ship() { 143 | 144 | // Load one image. 145 | Image shipImage; 146 | shipImage = new Image(getClass().getClassLoader().getResource("ship.png").toExternalForm(), true); 147 | stopArea.setRadius(40); 148 | stopArea.setStroke(Color.ORANGE); 149 | RotatedShipImage prev = null; 150 | // create all the number of directions based on a unit angle. 360 divided by NUM_DIRECTIONS 151 | for (int i = 0; i < NUM_DIRECTIONS; i++) { 152 | RotatedShipImage imageView = new RotatedShipImage(); 153 | imageView.setImage(shipImage); 154 | imageView.setRotate(-1 * i * UNIT_ANGLE_PER_FRAME); 155 | imageView.setCache(true); 156 | imageView.setCacheHint(CacheHint.SPEED); 157 | imageView.setManaged(false); 158 | 159 | imageView.prev = prev; 160 | imageView.setVisible(false); 161 | directionalShips.add(imageView); 162 | if (prev != null) { 163 | prev.next = imageView; 164 | } 165 | prev = imageView; 166 | flipBook.getChildren().add(imageView); 167 | } 168 | 169 | RotatedShipImage firstShip = directionalShips.get(0); 170 | firstShip.prev = prev; 171 | prev.next = firstShip; 172 | // set javafx node to an image 173 | firstShip.setVisible(true); 174 | node = flipBook; 175 | flipBook.setTranslateX(200); 176 | flipBook.setTranslateY(300); 177 | flipBook.setCache(true); 178 | flipBook.setCacheHint(CacheHint.SPEED); 179 | flipBook.setManaged(false); 180 | flipBook.setAutoSizeChildren(false); 181 | initHitZone(); 182 | 183 | } 184 | 185 | /** 186 | * Initialize the collision region for the space ship. 187 | * It's just a inscribed circle. 188 | */ 189 | public void initHitZone() { 190 | // build hit zone 191 | if (hitBounds == null) { 192 | //RotatedShipImage firstShip = directionalShips.get(0); 193 | double hZoneCenterX = 55; 194 | double hZoneCenterY = 34; 195 | hitBounds = CircleBuilder.create() 196 | .centerX(hZoneCenterX) 197 | .centerY(hZoneCenterY) 198 | .stroke(Color.PINK) 199 | .fill(Color.RED) 200 | .radius(15) 201 | .opacity(0) 202 | .build(); 203 | flipBook.getChildren().add(hitBounds); 204 | collisionBounds = hitBounds; 205 | } 206 | 207 | } 208 | 209 | /** 210 | * Change the velocity of the atom particle. 211 | */ 212 | @Override 213 | public void update() { 214 | flipBook.setTranslateX(flipBook.getTranslateX() + vX); 215 | flipBook.setTranslateY(flipBook.getTranslateY() + vY); 216 | 217 | if (stopArea.contains(getCenterX(), getCenterY())) { 218 | vX = 0; 219 | vY = 0; 220 | } 221 | 222 | } 223 | 224 | 225 | private RotatedShipImage getCurrentShipImage() { 226 | return directionalShips.get(uIndex); 227 | } 228 | 229 | /** 230 | * The center X coordinate of the current visible image. See getCurrentShipImage() method. 231 | * 232 | * @return The scene or screen X coordinate. 233 | */ 234 | public double getCenterX() { 235 | RotatedShipImage shipImage = getCurrentShipImage(); 236 | return node.getTranslateX() + (shipImage.getBoundsInLocal().getWidth() / 2); 237 | } 238 | 239 | /** 240 | * The center Y coordinate of the current visible image. See getCurrentShipImage() method. 241 | * 242 | * @return The scene or screen Y coordinate. 243 | */ 244 | public double getCenterY() { 245 | RotatedShipImage shipImage = getCurrentShipImage(); 246 | return node.getTranslateY() + (shipImage.getBoundsInLocal().getHeight() / 2); 247 | } 248 | 249 | /** 250 | * Determines the angle between it's starting position and ending position (Similar to a clock's second hand). 251 | * When the user is shooting the ship nose will point in the direction of the mouse press using the primary button. 252 | * When the user is thrusting to a location on the screen the right click mouse will pass true to the thrust 253 | * parameter. 254 | * 255 | * @param screenX The mouse press' screen x coordinate. 256 | * @param screenY The mouse press' screen ycoordinate. 257 | * @param thrust Thrust ship forward or not. True move forward otherwise false. 258 | */ 259 | public void plotCourse(double screenX, double screenY, boolean thrust) { 260 | // get center of ship 261 | double sx = getCenterX(); 262 | double sy = getCenterY(); 263 | 264 | // get user's new turn position based on mouse click 265 | Vec v = new Vec(screenX, screenY, sx, sy); 266 | if (u == null) { 267 | u = new Vec(1, 0); 268 | } 269 | 270 | 271 | double atan2RadiansU = Math.atan2(u.y, u.x); 272 | double atan2DegreesU = Math.toDegrees(atan2RadiansU); 273 | 274 | double atan2RadiansV = Math.atan2(v.y, v.x); 275 | double atan2DegreesV = Math.toDegrees(atan2RadiansV); 276 | 277 | double angleBetweenUAndV = atan2DegreesV - atan2DegreesU; 278 | 279 | 280 | // if abs value is greater than 180 move counter clockwise 281 | //(or opposite of what is determined) 282 | double absAngleBetweenUAndV = Math.abs(angleBetweenUAndV); 283 | boolean goOtherWay = false; 284 | if (absAngleBetweenUAndV > 180) { 285 | if (angleBetweenUAndV < 0) { 286 | turnDirection = DIRECTION.COUNTER_CLOCKWISE; 287 | goOtherWay = true; 288 | } else if (angleBetweenUAndV > 0) { 289 | turnDirection = DIRECTION.CLOCKWISE; 290 | goOtherWay = true; 291 | } else { 292 | turnDirection = Ship.DIRECTION.NEITHER; 293 | } 294 | } else { 295 | if (angleBetweenUAndV < 0) { 296 | turnDirection = Ship.DIRECTION.CLOCKWISE; 297 | } else if (angleBetweenUAndV > 0) { 298 | turnDirection = Ship.DIRECTION.COUNTER_CLOCKWISE; 299 | } else { 300 | turnDirection = Ship.DIRECTION.NEITHER; 301 | } 302 | } 303 | 304 | double degreesToMove = absAngleBetweenUAndV; 305 | if (goOtherWay) { 306 | degreesToMove = TWO_PI_DEGREES - absAngleBetweenUAndV; 307 | } 308 | 309 | //int q = v.quadrant(); 310 | 311 | uIndex = Math.round((float) (atan2DegreesU / UNIT_ANGLE_PER_FRAME)); 312 | if (uIndex < 0) { 313 | uIndex = NUM_DIRECTIONS + uIndex; 314 | } 315 | vIndex = Math.round((float) (atan2DegreesV / UNIT_ANGLE_PER_FRAME)); 316 | if (vIndex < 0) { 317 | vIndex = NUM_DIRECTIONS + vIndex; 318 | } 319 | String debugMsg = turnDirection + 320 | " U [m(" + u.mx + ", " + u.my + ") => c(" + u.x + ", " + u.y + ")] " + 321 | " V [m(" + v.mx + ", " + v.my + ") => c(" + v.x + ", " + v.y + ")] " + 322 | " start angle: " + atan2DegreesU + 323 | " end angle:" + atan2DegreesV + 324 | " Angle between: " + degreesToMove + 325 | " Start index: " + uIndex + 326 | " End index: " + vIndex; 327 | 328 | System.out.println(debugMsg); 329 | 330 | if (thrust) { 331 | vX = Math.cos(atan2RadiansV) * THRUST_AMOUNT; 332 | vY = -Math.sin(atan2RadiansV) * THRUST_AMOUNT; 333 | } 334 | turnShip(); 335 | 336 | u = v; 337 | } 338 | 339 | private void turnShip() { 340 | 341 | final Duration oneFrameAmt = Duration.millis(MILLIS_PER_FRAME); 342 | RotatedShipImage startImage = directionalShips.get(uIndex); 343 | RotatedShipImage endImage = directionalShips.get(vIndex); 344 | List frames = new ArrayList<>(); 345 | 346 | RotatedShipImage currImage = startImage; 347 | 348 | int i = 1; 349 | while (true) { 350 | 351 | final Node displayNode = currImage; 352 | 353 | KeyFrame oneFrame = new KeyFrame(oneFrameAmt.multiply(i), 354 | new EventHandler() { 355 | 356 | @Override 357 | public void handle(javafx.event.ActionEvent event) { 358 | // make all ship images invisible 359 | for (RotatedShipImage shipImg : directionalShips) { 360 | shipImg.setVisible(false); 361 | } 362 | // make current ship image visible 363 | displayNode.setVisible(true); 364 | 365 | // update the current index 366 | //uIndex = directionalShips.indexOf(displayNode); 367 | } 368 | }); // oneFrame 369 | 370 | frames.add(oneFrame); 371 | 372 | if (currImage == endImage) { 373 | break; 374 | } 375 | if (turnDirection == DIRECTION.CLOCKWISE) { 376 | currImage = currImage.prev; 377 | } 378 | if (turnDirection == DIRECTION.COUNTER_CLOCKWISE) { 379 | currImage = currImage.next; 380 | } 381 | i++; 382 | } 383 | 384 | 385 | if (rotateShipTimeline != null) { 386 | rotateShipTimeline.stop(); 387 | rotateShipTimeline.getKeyFrames().clear(); 388 | rotateShipTimeline.getKeyFrames().addAll(frames); 389 | } else { 390 | // sets the game world's game loop (Timeline) 391 | rotateShipTimeline = TimelineBuilder.create() 392 | .keyFrames(frames) 393 | .build(); 394 | 395 | } 396 | 397 | rotateShipTimeline.playFromStart(); 398 | 399 | 400 | } 401 | 402 | /** 403 | * Stops the ship from thrusting forward. 404 | * 405 | * @param screenX the screen's X coordinate to stop the ship. 406 | * @param screenY the screen's Y coordinate to stop the ship. 407 | */ 408 | public void applyTheBrakes(double screenX, double screenY) { 409 | stopArea.setCenterX(screenX); 410 | stopArea.setCenterY(screenY); 411 | } 412 | 413 | public Missile fire() { 414 | Missile m1; 415 | 416 | float slowDownAmt = 0; 417 | int scaleBeginningMissle; 418 | if (KeyCode.DIGIT2 == keyCode) { 419 | m1 = new Missile(9, Color.BLUE); 420 | slowDownAmt = 1.3f; 421 | scaleBeginningMissle = 11; 422 | } else { 423 | m1 = new Missile(Color.RED); 424 | scaleBeginningMissle = 8; 425 | } 426 | // velocity vector of the missile 427 | m1.vX = Math.cos(Math.toRadians(uIndex * UNIT_ANGLE_PER_FRAME)) * (MISSILE_THRUST_AMOUNT - slowDownAmt); 428 | m1.vY = -Math.sin(Math.toRadians(uIndex * UNIT_ANGLE_PER_FRAME)) * (MISSILE_THRUST_AMOUNT - slowDownAmt); 429 | 430 | // make the missile launch in the direction of the current direction of the ship nose. based on the 431 | // current frame (uIndex) into the list of image view nodes. 432 | RotatedShipImage shipImage = directionalShips.get(uIndex); 433 | 434 | // start to appear in the center of the ship to come out the direction of the nose of the ship. 435 | double offsetX = (shipImage.getBoundsInLocal().getWidth() - m1.node.getBoundsInLocal().getWidth()) / 2; 436 | double offsetY = (shipImage.getBoundsInLocal().getHeight() - m1.node.getBoundsInLocal().getHeight()) / 2; 437 | 438 | // initial launch of the missile (multiply vector by 4 makes it appear at the nose of the ship) 439 | m1.node.setTranslateX(node.getTranslateX() + (offsetX + (m1.vX * scaleBeginningMissle))); 440 | m1.node.setTranslateY(node.getTranslateY() + (offsetY + (m1.vY * scaleBeginningMissle))); 441 | return m1; 442 | } 443 | 444 | public void changeWeapon(KeyCode keyCode) { 445 | this.keyCode = keyCode; 446 | } 447 | 448 | public void shieldToggle() { 449 | 450 | 451 | if (shield == null) { 452 | RotatedShipImage shipImage = getCurrentShipImage(); 453 | double x = shipImage.getBoundsInLocal().getWidth() / 2; 454 | double y = shipImage.getBoundsInLocal().getHeight() / 2; 455 | 456 | // add shield 457 | shield = CircleBuilder.create() 458 | .radius(60) 459 | .strokeWidth(5) 460 | .stroke(Color.LIMEGREEN) 461 | .centerX(x) 462 | .centerY(y) 463 | .opacity(.70) 464 | .build(); 465 | collisionBounds = shield; 466 | shieldFade = FadeTransitionBuilder.create() 467 | .fromValue(1) 468 | .toValue(.40) 469 | .duration(Duration.millis(1000)) 470 | .cycleCount(12) 471 | .autoReverse(true) 472 | .node(shield) 473 | .onFinished(new EventHandler() { 474 | @Override 475 | public void handle(ActionEvent actionEvent) { 476 | shieldOn = false; 477 | flipBook.getChildren().remove(shield); 478 | shieldFade.stop(); 479 | collisionBounds = hitBounds; 480 | } 481 | }) 482 | .build(); 483 | shieldFade.playFromStart(); 484 | 485 | } 486 | shieldOn = !shieldOn; 487 | if (shieldOn) { 488 | collisionBounds = shield; 489 | flipBook.getChildren().add(0, shield); 490 | shieldFade.playFromStart(); 491 | } else { 492 | flipBook.getChildren().remove(shield); 493 | shieldFade.stop(); 494 | collisionBounds = hitBounds; 495 | 496 | } 497 | 498 | 499 | } 500 | 501 | } 502 | --------------------------------------------------------------------------------