final Duration oneFrameAmt = Duration.millis(1000/60);final KeyFrame oneFrame = new KeyFrame(oneFrameAmt,new EventHandler() {@Overridepublic void handle(javafx.event.ActionEvent event) {// update actorsupdateSprites();// check for collisioncheckCollisions();// removed dead thingscleanupSprites();}}); // oneFrame// sets the game world's game loop (Timeline)TimelineBuilder.create().cycleCount(Animation.INDEFINITE).keyFrames(oneFrame).build().play();
package carlfx.gameengine;import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.util.Duration;/*** This application demonstrates a JavaFX 2.x Game Loop.* Shown below are the methods which comprise of the fundamentals to a* simple game loop in JavaFX:
** <strong>initialize()</strong> - Initialize the game world.* <strong>beginGameLoop()</strong> - Creates a JavaFX Timeline object containing the game life cycle.* <strong>updateSprites()</strong> - Updates the sprite objects each period (per frame)* <strong>checkCollisions()</strong> - Method will determine objects that collide with each other.* <strong>cleanupSprites()</strong> - Any sprite objects needing to be removed from play.** @author cdea*/
public abstract class GameWorld {/** The JavaFX Scene as the game surface */private Scene gameSurface;/** All nodes to be displayed in the game window. */private Group sceneNodes;/** The game loop using JavaFX's <code>Timeline</code> API.*/private static Timeline gameLoop;/** Number of frames per second. */private final int framesPerSecond;/** Title in the application window.*/private final String windowTitle;/*** The sprite manager.*/private final SpriteManager spriteManager = new SpriteManager();/*** Constructor that is called by the derived class. This will* set the frames per second, title, and setup the game loop.* @param fps - Frames per second.* @param title - Title of the application window.*/public GameWorld(final int fps, final String title) {framesPerSecond = fps;windowTitle = title;// create and set timeline for the game loopbuildAndSetGameLoop();}/*** Builds and sets the game loop ready to be started.*/protected final void buildAndSetGameLoop() {final Duration oneFrameAmt = Duration.millis(1000/getFramesPerSecond());final KeyFrame oneFrame = new KeyFrame(oneFrameAmt,new EventHandler() {@Overridepublic void handle(javafx.event.ActionEvent event) {// update actorsupdateSprites();// check for collisioncheckCollisions();// removed dead thingscleanupSprites();}}); // oneFrame// sets the game world's game loop (Timeline)setGameLoop(TimelineBuilder.create().cycleCount(Animation.INDEFINITE).keyFrames(oneFrame).build());}/*** Initialize the game world by update the JavaFX Stage.* @param primaryStage*/public abstract void initialize(final Stage primaryStage);/**Kicks off (plays) the Timeline objects containing one key frame* that simply runs indefinitely with each frame invoking a method* to update sprite objects, check for collisions, and cleanup sprite* objects.**/public void beginGameLoop() {getGameLoop().play();}/*** Updates each game sprite in the game world. This method will* loop through each sprite and passing it to the handleUpdate()* method. The derived class should override handleUpdate() method.**/protected void updateSprites() {for (Sprite sprite:spriteManager.getAllSprites()){handleUpdate(sprite);}}/** Updates the sprite object's information to position on the game surface.* @param sprite - The sprite to update.*/protected void handleUpdate(Sprite sprite) {}/*** Checks each game sprite in the game world to determine a collision* occurred. The method will loop through each sprite and* passing it to the handleCollision()* method. The derived class should override handleCollision() method.**/protected void checkCollisions() {// check other sprite's collisionsspriteManager.resetCollisionsToCheck();// check each sprite against other sprite objects.for (Sprite spriteA:spriteManager.getCollisionsToCheck()){for (Sprite spriteB:spriteManager.getAllSprites()){if (handleCollision(spriteA, spriteB)) {// The break helps optimize the collisions// The break statement means one object only hits another// object as opposed to one hitting many objects.// To be more accurate comment out the break statement.break;}}}}/*** When two objects collide this method can handle the passed in sprite* objects. By default it returns false, meaning the objects do not* collide.* @param spriteA - called from checkCollision() method to be compared.* @param spriteB - called from checkCollision() method to be compared.* @return boolean True if the objects collided, otherwise false.*/protected boolean handleCollision(Sprite spriteA, Sprite spriteB) {return false;}/*** Sprites to be cleaned up.*/protected void cleanupSprites() {spriteManager.cleanupSprites();}/*** Returns the frames per second.* @return int The frames per second.*/protected int getFramesPerSecond() {return framesPerSecond;}/*** Returns the game's window title.* @return String The game's window title.*/public String getWindowTitle() {return windowTitle;}/*** The game loop (Timeline) which is used to update, check collisions, and* cleanup sprite objects at every interval (fps).* @return Timeline An animation running indefinitely representing the game* loop.*/protected static Timeline getGameLoop() {return gameLoop;}/*** The sets the current game loop for this game world.* @param gameLoop Timeline object of an animation running indefinitely* representing the game loop.*/protected static void setGameLoop(Timeline gameLoop) {GameWorld.gameLoop = gameLoop;}/*** Returns the sprite manager containing the sprite objects to* manipulate in the game.* @return SpriteManager The sprite manager.*/protected SpriteManager getSpriteManager() {return spriteManager;}/*** Returns the JavaFX Scene. This is called the game surface to* allow the developer to add JavaFX Node objects onto the Scene.* @return*/public Scene getGameSurface() {return gameSurface;}/*** Sets the JavaFX Scene. This is called the game surface to* allow the developer to add JavaFX Node objects onto the Scene.* @param gameSurface The main game surface (JavaFX Scene).*/protected void setGameSurface(Scene gameSurface) {this.gameSurface = gameSurface;}/*** All JavaFX nodes which are rendered onto the game surface(Scene) is* a JavaFX Group object.* @return Group The root containing many child nodes to be displayed into* the Scene area.*/public Group getSceneNodes() {return sceneNodes;}/*** Sets the JavaFX Group that will hold all JavaFX nodes which are rendered* onto the game surface(Scene) is a JavaFX Group object.* @param sceneNodes The root container having many children nodes* to be displayed into the Scene area.*/protected void setSceneNodes(Group sceneNodes) {this.sceneNodes = sceneNodes;}}
package carlfx.gameengine;import java.util.*;/*** Sprite manager is responsible for holding all sprite objects, and cleaning up* sprite objects to be removed. All collections are used by the JavaFX* application thread. During each cycle (animation frame) sprite management* occurs. This assists the user of the API to not have to create lists to* later be garbage collected. Should provide some performance gain.* @author cdea*/
public class SpriteManager {/** All the sprite objects currently in play */private final static List GAME_ACTORS = new ArrayList<>();/** A global single threaded list used to check collision against other* sprite objects.*/private final static List CHECK_COLLISION_LIST = new ArrayList<>();/** A global single threaded set used to cleanup or remove sprite objects* in play.*/private final static Set CLEAN_UP_SPRITES = new HashSet<>();/** */public List getAllSprites() {return GAME_ACTORS;}/*** VarArgs of sprite objects to be added to the game.* @param sprites*/public void addSprites(Sprite... sprites) {GAME_ACTORS.addAll(Arrays.asList(sprites));}/*** VarArgs of sprite objects to be removed from the game.* @param sprites*/public void removeSprites(Sprite... sprites) {GAME_ACTORS.removeAll(Arrays.asList(sprites));}/** Returns a set of sprite objects to be removed from the GAME_ACTORS.* @return CLEAN_UP_SPRITES*/public Set getSpritesToBeRemoved() {return CLEAN_UP_SPRITES;}/*** Adds sprite objects to be removed* @param sprites varargs of sprite objects.*/public void addSpritesToBeRemoved(Sprite... sprites) {if (sprites.length > 1) {CLEAN_UP_SPRITES.addAll(Arrays.asList((Sprite[]) sprites));} else {CLEAN_UP_SPRITES.add(sprites[0]);}}/*** Returns a list of sprite objects to assist in collision checks.* This is a temporary and is a copy of all current sprite objects* (copy of GAME_ACTORS).* @return CHECK_COLLISION_LIST*/public List getCollisionsToCheck() {return CHECK_COLLISION_LIST;}/*** Clears the list of sprite objects in the collision check collection* (CHECK_COLLISION_LIST).*/public void resetCollisionsToCheck() {CHECK_COLLISION_LIST.clear();CHECK_COLLISION_LIST.addAll(GAME_ACTORS);}/*** Removes sprite objects and nodes from all* temporary collections such as:* CLEAN_UP_SPRITES.* The sprite to be removed will also be removed from the* list of all sprite objects called (GAME_ACTORS).*/public void cleanupSprites() {// remove from actors listGAME_ACTORS.removeAll(CLEAN_UP_SPRITES);// reset the clean up spritesCLEAN_UP_SPRITES.clear();}
}
package carlfx.gameengine;import java.util.ArrayList;
import java.util.List;
import javafx.animation.Animation;
import javafx.scene.Node;/*** The Sprite class represents a image or node to be displayed.* In a 2D game a sprite will contain a velocity for the image to* move across the scene area. The game loop will call the update()* and collide() method at every interval of a key frame. A list of* animations can be used during different situations in the game* such as rocket thrusters, walking, jumping, etc.* @author cdea*/
public abstract class Sprite {/** Animation for the node */public List animations = new ArrayList<>();/** Current display node */public Node node;/** velocity vector x direction */public double vX = 0;/** velocity vector y direction */public double vY = 0;/** dead? */public boolean isDead = false;/*** Updates this sprite object's velocity, or animations.*/public abstract void update();/*** Did this sprite collide into the other sprite?** @param other - The other sprite.* @return*/public boolean collide(Sprite other) {return false;}
}
package carlfx;import carlfx.gameengine.GameWorld;
import javafx.application.Application;
import javafx.stage.Stage;/*** The main driver of the game.* @author cdea*/
public class GameLoopPart2 extends Application {GameWorld gameWorld = new AtomSmasher(60, "JavaFX 2 GameTutorial Part 2 - Game Loop");/*** @param args the command line arguments*/public static void main(String[] args) {launch(args);}@Overridepublic void start(Stage primaryStage) {// setup title, scene, stats, controls, and actors.gameWorld.initialize(primaryStage);// kick off the game loopgameWorld.beginGameLoop();// display windowprimaryStage.show();}}
package carlfx;import carlfx.gameengine.GameWorld;
import carlfx.gameengine.Sprite;
import java.util.Random;
import javafx.animation.Timeline;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBoxBuilder;
import javafx.scene.layout.VBox;
import javafx.scene.layout.VBoxBuilder;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import static javafx.animation.Animation.Status.RUNNING;
import static javafx.animation.Animation.Status.STOPPED;/*** This is a simple game world simulating a bunch of spheres looking* like atomic particles colliding with each other. When the game loop begins* the user will notice random spheres (atomic particles) floating and* colliding. The user is able to press a button to generate more* atomic particles. Also, the user can freeze the game.** @author cdea*/
public class AtomSmasher extends GameWorld {/** Read only field to show the number of sprite objects are on the field*/private final static Label NUM_SPRITES_FIELD = new Label();public AtomSmasher(int fps, String title){super(fps, title);}/*** Initialize the game world by adding sprite objects.* @param primaryStage*/@Overridepublic void initialize(final Stage primaryStage) {// Sets the window titleprimaryStage.setTitle(getWindowTitle());// Create the scenesetSceneNodes(new Group());setGameSurface(new Scene(getSceneNodes(), 640, 580));primaryStage.setScene(getGameSurface());// Create many spheresgenerateManySpheres(150);// Display the number of spheres visible.// Create a button to add more spheres.// Create a button to freeze the game loop.final Timeline gameLoop = getGameLoop();VBox stats = VBoxBuilder.create().spacing(5).translateX(10).translateY(10).children(HBoxBuilder.create().spacing(5).children(new Label("Number of Particles: "), // show no. particlesNUM_SPRITES_FIELD).build(),// button to build more spheresButtonBuilder.create().text("Regenerate").onMousePressed(new EventHandler() {@Overridepublic void handle(MouseEvent arg0) {generateManySpheres(150);}}).build(),// button to freeze game loopButtonBuilder.create().text("Freeze/Resume").onMousePressed(new EventHandler() {@Overridepublic void handle(MouseEvent arg0) {switch (gameLoop.getStatus()) {case RUNNING:gameLoop.stop();break;case STOPPED:gameLoop.play();break;}}}).build()).build(); // (VBox) stats on children// lay down the controlsgetSceneNodes().getChildren().add(stats);}/*** Make some more space spheres (Atomic particles)*/private void generateManySpheres(int numSpheres) {Random rnd = new Random();Scene gameSurface = getGameSurface();for (int i=0; i (gameSurface.getWidth() - (circle.getRadius() * 2))) {newX = gameSurface.getWidth() - (circle.getRadius() * 2);}// check for the bottom of screen the height newY is greater than height// minus radius times 2(height of sprite)double newY = rnd.nextInt((int) gameSurface.getHeight());if (newY > (gameSurface.getHeight() - (circle.getRadius() * 2))) {newY = gameSurface.getHeight() - (circle.getRadius() * 2);}circle.setTranslateX(newX);circle.setTranslateY(newY);circle.setVisible(true);circle.setId(b.toString());// add to actors in play (sprite objects)getSpriteManager().addSprites(b);// add sprite'sgetSceneNodes().getChildren().add(0, b.node);}}/*** Each sprite will update it's velocity and bounce off wall borders.* @param sprite - An atomic particle (a sphere).*/@Overrideprotected void handleUpdate(Sprite sprite) {if (sprite instanceof Atom) {Atom sphere = (Atom) sprite;// advance the spheres velocitysphere.update();// bounce off the walls when outside of boundariesif (sphere.node.getTranslateX() > (getGameSurface().getWidth() -sphere.node.getBoundsInParent().getWidth()) ||sphere.node.getTranslateX() < 0 ) { sphere.vX = sphere.vX * -1; } if (sphere.node.getTranslateY() > getGameSurface().getHeight()-sphere.node.getBoundsInParent().getHeight() ||sphere.node.getTranslateY() < 0) {sphere.vY = sphere.vY * -1;}}}/*** How to handle the collision of two sprite objects. Stops the particle* by zeroing out the velocity if a collision occurred.* @param spriteA* @param spriteB* @return*/@Overrideprotected boolean handleCollision(Sprite spriteA, Sprite spriteB) {if (spriteA.collide(spriteB)) {((Atom)spriteA).implode(this);((Atom)spriteB).implode(this);getSpriteManager().addSpritesToBeRemoved(spriteA, spriteB);return true;}return false;}/*** Remove dead things.*/@Overrideprotected void cleanupSprites() {// removes from the scene and backend storesuper.cleanupSprites();// let user know how many sprites are showing.NUM_SPRITES_FIELD.setText(String.valueOf(getSpriteManager().getAllSprites().size()));}
}
package carlfx;import carlfx.gameengine.GameWorld;
import carlfx.gameengine.Sprite;
import javafx.animation.FadeTransitionBuilder;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.paint.Color;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.RadialGradientBuilder;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CircleBuilder;
import javafx.util.Duration;/*** A spherical looking object (Atom) with a random radius, color, and velocity.* When two atoms collide each will fade and become removed from the scene. The* method called implode() implements a fade transition effect.** @author cdea*/
public class Atom extends Sprite {public Atom(double radius, Color fill) {Circle sphere = CircleBuilder.create().centerX(radius).centerY(radius).radius(radius).cache(true).build();RadialGradient rgrad = RadialGradientBuilder.create().centerX(sphere.getCenterX() - sphere.getRadius() / 3).centerY(sphere.getCenterY() - sphere.getRadius() / 3).radius(sphere.getRadius()).proportional(false).stops(new Stop(0.0, fill), new Stop(1.0, Color.BLACK)).build();sphere.setFill(rgrad);// set javafx node to a circlenode = sphere;}/*** Change the velocity of the atom particle.*/@Overridepublic void update() {node.setTranslateX(node.getTranslateX() + vX);node.setTranslateY(node.getTranslateY() + vY);}@Overridepublic boolean collide(Sprite other) {if (other instanceof Atom) {return collide((Atom)other);}return false;}/*** When encountering another Atom to determine if they collided.* @param other Another atom* @return boolean true if this atom and other atom has collided,* otherwise false.*/private boolean collide(Atom other) {// if an object is hidden they didn't collide.if (!node.isVisible() ||!other.node.isVisible() ||this == other) {return false;}// determine it's sizeCircle otherSphere = other.getAsCircle();Circle thisSphere = getAsCircle();double dx = otherSphere.getTranslateX() - thisSphere.getTranslateX();double dy = otherSphere.getTranslateY() - thisSphere.getTranslateY();double distance = Math.sqrt( dx * dx + dy * dy );double minDist = otherSphere.getRadius() + thisSphere.getRadius() + 3;return (distance < minDist);}/*** Returns a node casted as a JavaFX Circle shape.* @return Circle shape representing JavaFX node for convenience.*/public Circle getAsCircle() {return (Circle) node;}/*** Animate an implosion. Once done remove from the game world* @param gameWorld - game world*/public void implode(final GameWorld gameWorld) {vX = vY = 0;FadeTransitionBuilder.create().node(node).duration(Duration.millis(300)).fromValue(node.getOpacity()).toValue(0).onFinished(new EventHandler() {@Overridepublic void handle(ActionEvent arg0) {isDead = true;gameWorld.getSceneNodes().getChildren().remove(node);}}).build().play();}
}