I want to create an application that performs many renderings in a canvas.
The normal JavaFX way blocks the GUI: It is realy hard to press the button in the application code below (run with Java 8).
I searched the web, but JavaFX does not support background rendering: All rendering operation (like strokeLine) are stored in a buffer and are executed in the JavaFX application thread later. So I cannot even use two canvases and exchange then after rendering.
Also the javafx.scene.Node.snapshot(SnapshotParameters, WritableImage) cannot be used to create an image in a background thread, as it needs to run inside the JavaFX application thread and so it will block the GUI also.
Any ideas to have a non blocking GUI with many rendering operations? (I just want to press buttons etc. while the rendering is performed somehow in background or paused regularly)
package canvastest;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.stage.Stage;
public class DrawLinieTest extends Application
{
int interations = 2;
double lineSpacing = 1;
Random rand = new Random(666);
List<Color> colorList;
final VBox root = new VBox();
Canvas canvas = new Canvas(1200, 800);
Canvas canvas2 = new Canvas(1200, 800);
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<?> drawShapesFuture;
{
colorList = new ArrayList<>(256);
colorList.add(Color.ALICEBLUE);
colorList.add(Color.ANTIQUEWHITE);
colorList.add(Color.AQUA);
colorList.add(Color.AQUAMARINE);
colorList.add(Color.AZURE);
colorList.add(Color.BEIGE);
colorList.add(Color.BISQUE);
colorList.add(Color.BLACK);
colorList.add(Color.BLANCHEDALMOND);
colorList.add(Color.BLUE);
colorList.add(Color.BLUEVIOLET);
colorList.add(Color.BROWN);
colorList.add(Color.BURLYWOOD);
}
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage)
{
primaryStage.setTitle("Drawing Operations Test");
System.out.println("Init...");
// inital draw that creates a big internal operation buffer (GrowableDataBuffer)
drawShapes(canvas.getGraphicsContext2D(), lineSpacing);
drawShapes(canvas2.getGraphicsContext2D(), lineSpacing);
System.out.println("Start testing...");
new CanvasRedrawTask().start();
Button btn = new Button("test " + System.nanoTime());
btn.setOnAction((ActionEvent e) ->
{
btn.setText("test " + System.nanoTime());
});
root.getChildren().add(btn);
root.getChildren().add(canvas);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private void drawShapes(GraphicsContext gc, double f)
{
System.out.println(">>> BEGIN: drawShapes ");
gc.clearRect(0, 0, gc.getCanvas().getWidth(), gc.getCanvas().getHeight());
gc.setLineWidth(10);
gc.setLineCap(StrokeLineCap.ROUND);
long time = System.nanoTime();
double w = gc.getCanvas().getWidth() - 80;
double h = gc.getCanvas().getHeight() - 80;
int c = 0;
for (int i = 0; i < interations; i++)
{
for (double x = 0; x < w; x += f)
{
for (double y = 0; y < h; y += f)
{
gc.setStroke(colorList.get(rand.nextInt(colorList.size())));
gc.strokeLine(40 + x, 10 + y, 10 + x, 40 + y);
c++;
}
}
}
System.out.println("<<< END: drawShapes: " + ((System.nanoTime() - time) / 1000 / 1000) + "ms");
}
public synchronized void drawShapesAsyc(final double f)
{
if (drawShapesFuture != null && !drawShapesFuture.isDone())
return;
drawShapesFuture = executorService.submit(() ->
{
drawShapes(canvas2.getGraphicsContext2D(), lineSpacing);
Platform.runLater(() ->
{
root.getChildren().remove(canvas);
Canvas t = canvas;
canvas = canvas2;
canvas2 = t;
root.getChildren().add(canvas);
});
});
}
class CanvasRedrawTask extends AnimationTimer
{
long time = System.nanoTime();
#Override
public void handle(long now)
{
drawShapesAsyc(lineSpacing);
long f = (System.nanoTime() - time) / 1000 / 1000;
System.out.println("Time since last redraw " + f + " ms");
time = System.nanoTime();
}
}
}
EDIT Edited the code to show that a background thread that sends the draw operations and than exchange the canvas does not resolve the problem! Because All rendering operation (like strokeLine) are stored in a buffer and are executed in the JavaFX application thread later.
You are drawing 1.6 million lines per frame. It is simply a lot of lines and takes time to render using the JavaFX rendering pipeline. One possible workaround is not to issue all drawing commands in a single frame, but instead render incrementally, spacing out drawing commands, so that the application remains relatively responsive (e.g. you can close it down or interact with buttons and controls on the app while it is rendering). Obviously, there are some tradeoffs in extra complexity with this approach and the result is not as desirable as simply being able to render extremely large amounts of draw commands within the context of single 60fps frame. So the presented approach is only acceptable for some kinds of applications.
Some ways to perform an incremental render are:
Only issue a max number of calls each frame.
Place the rendering calls into a buffer such as a blocking queue and just drain a max number of calls each frame from the queue.
Here is a sample of the first option.
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.concurrent.*;
import javafx.scene.Scene;
import javafx.scene.canvas.*;
import javafx.scene.control.Button;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.*;
public class DrawLineIncrementalTest extends Application {
private static final int FRAME_CALL_THRESHOLD = 25_000;
private static final int ITERATIONS = 2;
private static final double LINE_SPACING = 1;
private final Random rand = new Random(666);
private List<Color> colorList;
private final WritableImage image = new WritableImage(ShapeService.W, ShapeService.H);
private final Lock lock = new ReentrantLock();
private final Condition rendered = lock.newCondition();
private final ShapeService shapeService = new ShapeService();
public DrawLineIncrementalTest() {
colorList = new ArrayList<>(256);
colorList.add(Color.ALICEBLUE);
colorList.add(Color.ANTIQUEWHITE);
colorList.add(Color.AQUA);
colorList.add(Color.AQUAMARINE);
colorList.add(Color.AZURE);
colorList.add(Color.BEIGE);
colorList.add(Color.BISQUE);
colorList.add(Color.BLACK);
colorList.add(Color.BLANCHEDALMOND);
colorList.add(Color.BLUE);
colorList.add(Color.BLUEVIOLET);
colorList.add(Color.BROWN);
colorList.add(Color.BURLYWOOD);
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Drawing Operations Test");
System.out.println("Start testing...");
new CanvasRedrawHandler().start();
Button btn = new Button("test " + System.nanoTime());
btn.setOnAction(e -> btn.setText("test " + System.nanoTime()));
Scene scene = new Scene(new VBox(btn, new ImageView(image)));
primaryStage.setScene(scene);
primaryStage.show();
}
private class CanvasRedrawHandler extends AnimationTimer {
long time = System.nanoTime();
#Override
public void handle(long now) {
if (!shapeService.isRunning()) {
shapeService.reset();
shapeService.start();
}
if (lock.tryLock()) {
try {
System.out.println("Rendering canvas");
shapeService.canvas.snapshot(null, image);
rendered.signal();
} finally {
lock.unlock();
}
}
long f = (System.nanoTime() - time) / 1000 / 1000;
System.out.println("Time since last redraw " + f + " ms");
time = System.nanoTime();
}
}
private class ShapeService extends Service<Void> {
private Canvas canvas;
private static final int W = 1200, H = 800;
public ShapeService() {
canvas = new Canvas(W, H);
}
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
drawShapes(canvas.getGraphicsContext2D(), LINE_SPACING);
return null;
}
};
}
private void drawShapes(GraphicsContext gc, double f) throws InterruptedException {
lock.lock();
try {
System.out.println(">>> BEGIN: drawShapes ");
gc.clearRect(0, 0, gc.getCanvas().getWidth(), gc.getCanvas().getHeight());
gc.setLineWidth(10);
gc.setLineCap(StrokeLineCap.ROUND);
long time = System.nanoTime();
double w = gc.getCanvas().getWidth() - 80;
double h = gc.getCanvas().getHeight() - 80;
int nCalls = 0, nCallsPerFrame = 0;
for (int i = 0; i < ITERATIONS; i++) {
for (double x = 0; x < w; x += f) {
for (double y = 0; y < h; y += f) {
gc.setStroke(colorList.get(rand.nextInt(colorList.size())));
gc.strokeLine(40 + x, 10 + y, 10 + x, 40 + y);
nCalls++;
nCallsPerFrame++;
if (nCallsPerFrame >= FRAME_CALL_THRESHOLD) {
System.out.println(">>> Pausing: drawShapes ");
rendered.await();
nCallsPerFrame = 0;
System.out.println(">>> Continuing: drawShapes ");
}
}
}
}
System.out.println("<<< END: drawShapes: " + ((System.nanoTime() - time) / 1000 / 1000) + "ms for " + nCalls + " ops");
} finally {
lock.unlock();
}
}
}
}
Note that for the sample, it is possible to interact with the scene by clicking the test button while the incremental rendering is in progress. If desired, you could further enhance this to double buffer the snapshot images for the canvas so that the user doesn't see the incremental rendering. Also because the incremental rendering is in a Service, you can use the service facilities to track rendering progress and relay that to the UI via a progress bar or whatever mechanisms you wish.
For the above sample you can play around with the FRAME_CALL_THRESHOLD setting to vary the maximum number of calls which are issued each frame. The current setting of 25,000 calls per frame keeps the UI very responsive. A setting of 2,000,000 would be the same as fully rendering the canvas in a single frame (because you are issuing 1,600,000 calls in the frame) and no incremental rendering will be performed, however the UI will not be responsive while the rendering operations are being completed for that frame.
Side Note
There is something weird here. If you remove all of the concurrency stuff and the double canvases in the code in the original question and just use a single canvas with all logic on the JavaFX application thread, the initial invocation of drawShapes takes 27 seconds, and subsequent invocations take less that a second, but in all cases the application logic is asking the system to perform the same task. I don't know why the initial call is so slow, it seems like a performance issue in the JavaFX canvas implementation to me, perhaps related to inefficient buffer allocation. If that is the case, then perhaps the JavaFX canvas implementation could be tweaked so that a hint for a suggested initial buffer size could be provided, so that it more efficiently allocates space for its internal growable buffer implementation. It might be something worth filing a bug or discussing it on the JavaFX developer mailing list. Also note that the issue of a very slow initial rendering of the canvas is only visible when you issue a very large number (e.g. > 500,000) of rendering calls, so it won't effect all applications.
The issue that is described here has also been discussed on the JavaFX mailing list some months ago in this thread
http://mail.openjdk.java.net/pipermail/openjfx-dev/2015-September/017939.html
The proposed solution is similar to the one given by jewelsea.
Related
i have a problem with the Timeline in JavaFX : the Timeline is locked at 1fps.
KeyFrames aren't triggerred more than this, even if i've put three keyframes :
60 times per second
120 times per second
1 time per second
They're all triggered at the same time : 1 second
TickSystem class :
package TickSystem;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
public class TickSystem implements EventHandler<ActionEvent> {
private KeyFrame kfU; // update
private KeyFrame kfD; // draw
private KeyFrame kfFPS; // FPS count
public Rectangle r;
public int curFrame = 0;
public int tick = 0;
public final Timeline gameLoop = new Timeline(120);
public final Duration updateTime = Duration.millis((double)1000/60); // 60 times per seconds
public final Duration drawTime = Duration.millis((double)1000/120); // 120 times per seconds
public int fps;
private int lastFrames = 0;
public TickSystem(Rectangle r){
this.r = r;
this.kfU = new KeyFrame(updateTime,"tickKeyUpdate", this::handle);
this.kfD = new KeyFrame(drawTime,"tickKeyDraw", this::handleDraw);
this.kfFPS = new KeyFrame(Duration.seconds(1),"tickKeyFPS", this::handleFPS);
this.gameLoop.setCycleCount(Timeline.INDEFINITE);
this.gameLoop.getKeyFrames().add(this.kfU);
this.gameLoop.getKeyFrames().add(this.kfD);
this.gameLoop.getKeyFrames().add(this.kfFPS);
}
public void start(){
this.gameLoop.play();
}
public void pause(){
this.gameLoop.pause();
}
public void stop(){
this.gameLoop.stop();
}
#Override
public void handle(ActionEvent ae) { // for update
this.tick++;
}
public void handleDraw(ActionEvent ae){ // for draw
this.curFrame++;
this.r.setWidth(curFrame);
}
public void handleFPS(ActionEvent ae) { // for FPS
this.fps = this.curFrame - this.lastFrames;
this.lastFrames = this.curFrame;
System.out.println(this.fps);
}
}
Main class :
package TickSystem;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Data");
primaryStage.setResizable(true);
Group root = new Group();
Scene scene = new Scene(root,400,400);
Rectangle r = new Rectangle(10,10,100,100);
r.setFill(Color.RED);
root.getChildren().add(r);
TickSystem loop = new TickSystem(r);
primaryStage.setScene(scene);
primaryStage.show();
loop.start();
}
}
So, the rectangle must gain 1px on width each time the handleDraw function is called, so 120 times per second.
Actually, he only gain one pixel per second. And i'm at 1fps on the handleFPS function.
This function must print the number of times the handleDraw function has been called each seconds
EDIT :
I've made these three KeyFrames cause i try to make a 2D game.
I've already make a good part of this game on Java with Swing and i need to update infos (like player poisition) 60 times per second but i try to draw informations on screen 120 times per second.
JavaFX sounds better to me for the GUI, so i left java swing behind.
These class are for testing and i'm new on JavaFX. Thanks for your time.
You have three key frames; one at 1/120 second, one at 1/60 second, and one at 1 second. Since the longest duration of any key frame is one second, the duration of one cycle of the timeline is one second.
Therefore, during one cycle of the timeline, the following three things happen:
At 1/120 second, handleDraw() is invoked
At 1/60 second, handle() is invoked
At 1 second, handleFPS() is invoked
So during one cycle of the timeline (1 second), handleDraw() and handle() are invoked once each.
You set the cycle count to INDEFINITE, so once one cycle is completed, it repeats; this happens indefinitely.
One solution is to use a separate timeline for each of the individual tasks. This will not add any appreciable overhead to the application.
(As an aside: there is no point here in implementing EventHandler. You never use an instance of TickSystem as an event handler; you only use the lambda expressions.)
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
public class TickSystem {
private KeyFrame kfU; // update
private KeyFrame kfD; // draw
private KeyFrame kfFPS; // FPS count
public Rectangle r;
public int curFrame = 0;
public int tick = 0;
public final Timeline gameLoop = new Timeline(60);
private final Timeline drawLoop = new Timeline(120);
private final Timeline fpsLoop = new Timeline(1000);
public final Duration updateTime = Duration.millis((double)1000/60); // 60 times per seconds
public final Duration drawTime = Duration.millis((double)1000/120); // 120 times per seconds
public int fps;
private int lastFrames = 0;
public TickSystem(Rectangle r){
this.r = r;
this.kfU = new KeyFrame(updateTime,"tickKeyUpdate", this::handleUpdate);
this.kfD = new KeyFrame(drawTime,"tickKeyDraw", this::handleDraw);
this.kfFPS = new KeyFrame(Duration.seconds(1),"tickKeyFPS", this::handleFPS);
this.gameLoop.setCycleCount(Timeline.INDEFINITE);
this.drawLoop.setCycleCount(Timeline.INDEFINITE);
this.fpsLoop.setCycleCount(Timeline.INDEFINITE);
this.fpsLoop.getKeyFrames().add(this.kfFPS);
this.gameLoop.getKeyFrames().add(this.kfU);
this.drawLoop.getKeyFrames().add(this.kfD);
}
public void start(){
this.gameLoop.play();
this.fpsLoop.play();
this.drawLoop.play();
}
public void pause(){
this.gameLoop.pause();
this.fpsLoop.pause();
this.drawLoop.pause();
}
public void stop(){
this.gameLoop.stop();
this.drawLoop.stop();
this.fpsLoop.stop();
}
public void handleUpdate(ActionEvent ae) { // for update
this.tick++;
}
public void handleDraw(ActionEvent ae){ // for draw
this.curFrame++;
this.r.setWidth(curFrame);
}
public void handleFPS(ActionEvent ae) { // for FPS
this.fps = this.curFrame - this.lastFrames;
this.lastFrames = this.curFrame;
System.out.println(this.fps);
}
}
Or, more succinctly:
public class TickSystem {
private Rectangle r;
private int curFrame = 0;
private int tick = 0;
private final List<Timeline> timelines = new ArrayList<>();
private int fps;
private int lastFrames = 0;
public TickSystem(Rectangle r){
this.r = r;
timelines.add(createTimeline(60, this::handleUpdate));
timelines.add(createTimeline(120, this::handleDraw));
timelines.add(createTimeline(1, this::handleFPS));
}
private Timeline createTimeline(int frequency, EventHandler<ActionEvent> handler) {
Timeline timeline = new Timeline(frequency);
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(1000.0 / frequency), handler));
timeline.setCycleCount(Animation.INDEFINITE);
return timeline;
}
public void start(){
timelines.forEach(Timeline::play);
}
public void pause(){
timelines.forEach(Timeline::pause);
}
public void stop(){
timelines.forEach(Timeline::stop);
}
public void handleUpdate(ActionEvent ae) { // for update
this.tick++;
}
public void handleDraw(ActionEvent ae){ // for draw
this.curFrame++;
this.r.setWidth(curFrame);
}
public void handleFPS(ActionEvent ae) { // for FPS
this.fps = this.curFrame - this.lastFrames;
this.lastFrames = this.curFrame;
System.out.println(this.fps);
}
}
Another solution would be to use an AnimationTimer: see https://stackoverflow.com/a/60685975/2189127
You should also note that your FPS keyframe/timeline is not really measuring frames per second, in the sense of how frequently the scene graph is repainted. It is only measuring how frequently the width of the rectangle is updated (how often the property value is changed).
I'm trying to make a simple 2D game in Java.
As far as I know, my game should consist of two threads: "event dispatch thread" (for GUI operations) and "game thread" (for game loop).
I created an outline but could not find where to place the game loop.
in short, I'm trying to create a game loop without freezing my UI thread.
I would be grateful if you could give any information about the things I did wrong.
That's my game loop (You can also give tips to create a better game loop):
while(true) {
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("Forge and Attack");
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(true);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setFocusable(true);
frame.add(new MyPanel());
}
}
class MyPanel extends JPanel implements KeyListener, MouseListener {
public MyPanel() {
setBackground(Color.BLACK);
setOpaque(true);
addKeyListener(this);
addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e){
}
});
}
#Override
public void paint(Graphics g) {
}
}
I thought this an interesting topic to expand on... I have covered the questions you asked as well as showed the maybe a better or correct way of doing certain things like painting, and listening for keys pressed, as well as some others like separation of concerns and making the entire game more reusable/expandable.
1. Where to place the game loop?
So this isn't straight forward and can depend on each individuals coding style, but really all we seek to achieve here is to create the game loop and start it at an appropriate time. I believe code speaks a 1000 words (sometimes it might just be 1000 words :)), but below is some code which in the most minimally possible way (still producing a valid working example) shows where a game loop can be created/placed and used in the code, the code is heavily commented for clarity and understanding:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class MyGame {
private Scene scene;
private Sprite player;
private Thread gameLoop;
private boolean isRunning;
public MyGame() {
createAndShowUI();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(MyGame::new);
}
/**
* Here we will create our swing UI as well as initialise and setup our
* sprites, scene, and game loop and other buttons etc
*/
private void createAndShowUI() {
JFrame frame = new JFrame("MyGame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
player = new Sprite(/*ImageIO.read(getClass().getResourceAsStream("...."))*/);
this.scene = new Scene();
this.scene.add(player);
this.addKeyBindings();
this.setupGameLoop();
frame.add(scene);
frame.pack();
frame.setVisible(true);
// after setting the frame visible we start the game loop, this could be done in a button or wherever you want
this.isRunning = true;
this.gameLoop.start();
}
/**
* This method would actually create and setup the game loop The game loop
* will always be encapsulated in a Thread, Timer or some sort of construct
* that generates a separate thread in order to not block the UI
*/
private void setupGameLoop() {
// initialise the thread
gameLoop = new Thread(() -> {
// while the game "is running" and the isRunning boolean is set to true, loop forever
while (isRunning) {
// here we do 2 very important things which might later be expanded to 3:
// 1. We call Scene#update: this essentially will iterate all of our Sprites in our game and update their movments/position in the game via Sprite#update()
this.scene.update();
// TODO later on one might add a method like this.scene.checkCollisions in which you check if 2 sprites are interesecting and do something about it
// 2. We then call JPanel#repaint() which will cause JPanel#paintComponent to be called and thus we will iterate all of our sprites
// and invoke the Sprite#render method which will draw them to the screen
this.scene.repaint();
// here we throttle our game loop, because we are using a while loop this will execute as fast as it possible can, which might not be needed
// so here we call Thread#slepp so we can give the CPU some time to breathe :)
try {
Thread.sleep(15);
} catch (InterruptedException ex) {
}
}
});
}
private void addKeyBindings() {
// here we would use KeyBindings (https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html) and add them to our Scene/JPanel
// these would allow us to manipulate our Sprite objects using the keyboard below is 2 examples for using the A key to make our player/Sprite go left
// or the D key to make the player/Sprite go to the right
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "A pressed");
this.scene.getActionMap().put("A pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.LEFT = true;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "A released");
this.scene.getActionMap().put("A released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.LEFT = false;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "D pressed");
this.scene.getActionMap().put("D pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.RIGHT = true;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "D released");
this.scene.getActionMap().put("D released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.RIGHT = false;
}
});
}
public class Scene extends JPanel {
private final ArrayList<Sprite> sprites;
public Scene() {
// we are using a game loop to repaint, so probably dont want swing randomly doing it for us
this.setIgnoreRepaint(true);
this.sprites = new ArrayList<>();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// this method gets called on Scene#repaint in our game loop and we then render each in our game
sprites.forEach((sprite) -> {
sprite.render(g2d);
});
}
#Override
public Dimension getPreferredSize() {
// because no components are added to the JPanel, we will have a default sizxe of 0,0 so we instead force the JPanel to a size we want
return new Dimension(500, 500);
}
public void add(Sprite go) {
this.sprites.add(go);
}
private void update() {
// this method gets called on Scene#update in our game loop and we then update the sprites movement and position our game
sprites.forEach((go) -> {
go.update();
});
}
}
public class Sprite {
private int x = 50, y = 50, speed = 5;
//private final BufferedImage image;
public boolean LEFT, RIGHT, UP, DOWN;
public Sprite(/*BufferedImage image*/) {
//this.image = image;
}
public void render(Graphics2D g2d) {
//g2d.drawImage(this.image, this.x, this.y, null);
g2d.fillRect(this.x, this.y, 100, 100);
}
public void update() {
if (LEFT) {
this.x -= this.speed;
}
if (RIGHT) {
this.x += this.speed;
}
if (UP) {
this.y -= this.speed;
}
if (DOWN) {
this.y += this.speed;
}
}
}
}
2. Tips to create a better game loop
This very much like the first point in my answer is very subjective to what you are trying to achieve and at what granularity will your problem be satisfied with. So instead of prescribing 1 type of game loop. Let us look at the various kinds we can have:
First what is a game loop?*
The game loop is the overall flow control for the entire game program. It’s a loop because the game keeps doing a series of actions over and over again until the user quits. Each iteration of the game loop is known as a frame. Most real-time games update several times per second: 30 and 60 are the two most common intervals. If a game runs at 60 FPS (frames per second), this means that the game loop completes 60 iterations every second.
a. While loop
This we have seen in the above example and is simply a while loop encapsulated inside a Thread with possibly a Thread#sleep call to help throttle CPU usage. This and the Swing Timer are probably the most basic you can use.
gameLoop = new Thread(() -> {
while (isRunning) {
this.scene.update();
this.scene.repaint();
try {
Thread.sleep(15);
} catch (InterruptedException ex) {
}
}
});
Pros:
Easy to implement
All updating and rendering, repainting is done in a separate thread from the EDT
Cons:
Cannot guarantee the same frame rate on various PCs, so movement of the game might look better/worse or slower/faster on various computers depending on the hardware
b. Swing Timer
Similar to the while loop, a Swing Timer can be used in which an action event is fired periodically, because it is fired periodically we can simply use an if statement to check if the game is running and then call our necessary methods
gameLoop = new Timer(15, (ActionEvent e) -> {
if (isRunning) {
MyGame.this.scene.update();
MyGame.this.scene.repaint();
}
});
Pros:
Easy to implement
Cons:
Runs on the EDT which is not necessary or wanted as we are not updating any Swing components but rather simply painting to it
Cannot guarantee the same frame rate on various PCs, so movement of the game might look better/worse or slower/faster on various computers depending on the hardware
c. Fixed time step*
This is a more complex game loop (but simpler than a variable time step game loop). This works on the premise that we want to achieve a specific FPS i.e. 30 or 60 frames per second, and thus we simply make sure we call our update and rendering methods that exact number of times per seconds.
Update methods do not accept a "time elapsed", as they assume each update is for a fixed time period. Calculations may be done as position += distancePerUpdate. The example includes an interpolation during render.
gameLoop = new Thread(() -> {
//This value would probably be stored elsewhere.
final double GAME_HERTZ = 60.0;
//Calculate how many ns each frame should take for our target game hertz.
final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
//If we are able to get as high as this FPS, don't render again.
final double TARGET_FPS = 60;
final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
//At the very most we will update the game this many times before a new render.
//If you're worried about visual hitches more than perfect timing, set this to 1.
final int MAX_UPDATES_BEFORE_RENDER = 5;
//We will need the last update time.
double lastUpdateTime = System.nanoTime();
//Store the last time we rendered.
double lastRenderTime = System.nanoTime();
while (isRunning) {
double now = System.nanoTime();
int updateCount = 0;
//Do as many game updates as we need to, potentially playing catchup.
while (now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER) {
MyGame.this.scene.update();
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}
//If for some reason an update takes forever, we don't want to do an insane number of catchups.
//If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if (now - lastUpdateTime > TIME_BETWEEN_UPDATES) {
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
//Render. To do so, we need to calculate interpolation for a smooth render.
float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES));
MyGame.this.scene.render(interpolation);
lastRenderTime = now;
//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
//allow the threading system to play threads that are waiting to run.
Thread.yield();
//This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
//You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
//FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
//On my OS it does not unpuase the game if i take this away
try {
Thread.sleep(1);
} catch (Exception e) {
}
now = System.nanoTime();
}
}
});
This loop will need other changes such to take place to allow for interpolation:
Scene:
public class Scene extends JPanel {
private float interpolation;
#Override
protected void paintComponent(Graphics g) {
...
sprites.forEach((sprite) -> {
sprite.render(g2d, this.interpolation);
});
}
public void render(float interpolation) {
this.interpolation = interpolation;
this.repaint();
}
}
Sprite:
public class Sprite {
public void render(Graphics2D g2d, float interpolation) {
g2d.fillRect((int) (this.x + interpolation), (int) (this.y + interpolation), 100, 100);
}
}
Pros:
predictable, deterministic FPS on various computers/hardware
clearer calculation code
Cons:
not synced to monitor v-sync (causes jittery graphics unless you interpolate) - this example interpolates
limited max frame rate (unless you interpolate) - this example interpolates
d. Variable time step*
Usually used when a physics system is being implemented, or whenever a record of elapsed time is needed, I.e. animations. Physics/animation updates are passed a "time elapsed since last update" argument and are hence framerate-dependent. This may mean doing calculations as position += distancePerSecond * timeElapsed.
gameLoop = new Thread(() -> {
// how many frames should be drawn in a second
final int FRAMES_PER_SECOND = 60;
// calculate how many nano seconds each frame should take for our target frames per second.
final long TIME_BETWEEN_UPDATES = 1000000000 / FRAMES_PER_SECOND;
// track number of frames
int frameCount;
// if you're worried about visual hitches more than perfect timing, set this to 1. else 5 should be okay
final int MAX_UPDATES_BETWEEN_RENDER = 1;
// we will need the last update time.
long lastUpdateTime = System.nanoTime();
// store the time we started this will be used for updating map and charcter animations
long currTime = System.currentTimeMillis();
while (isRunning) {
long now = System.nanoTime();
long elapsedTime = System.currentTimeMillis() - currTime;
currTime += elapsedTime;
int updateCount = 0;
// do as many game updates as we need to, potentially playing catchup.
while (now - lastUpdateTime >= TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BETWEEN_RENDER) {
MyGame.this.scene.update(elapsedTime);//Update the entity movements and collision checks etc (all has to do with updating the games status i.e call move() on Enitites)
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}
// if for some reason an update takes forever, we don't want to do an insane number of catchups.
// if you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if (now - lastUpdateTime >= TIME_BETWEEN_UPDATES) {
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
MyGame.this.scene.repaint(); // draw call for rendering sprites etc
long lastRenderTime = now;
//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
while (now - lastRenderTime < TIME_BETWEEN_UPDATES && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
Thread.yield();
now = System.nanoTime();
}
}
});
Scene:
public class Scene extends JPanel {
private void update(long elapsedTime) {
// this method gets called on Scene#update in our game loop and we then update the sprites movement and position our game
sprites.forEach((go) -> {
go.update(elapsedTime);
});
}
}
Sprite:
public class Sprite {
private float speed = 0.5f;
public void update(long elapsedTime) {
if (LEFT) {
this.x -= this.speed * elapsedTime;
}
if (RIGHT) {
this.x += this.speed * elapsedTime;
}
if (UP) {
this.y -= this.speed * elapsedTime;
}
if (DOWN) {
this.y += this.speed * elapsedTime;
}
}
}
Pros:
smooth
easier to to code
Cons:
non-deterministic, unpredictable at very small or large steps
I have several custom control. One of the requirements is to have the control be able to flash based on it's state. I am using a fade transition, which works great.
How can I sync the fade transition so that if multiple controls are flashing on the screen, they are fading in and out at the same rate?
The only thing I can think of to make this work easily is to have a static ParallelTransition that each instance adds it's fade transition to when active but that just doesn't sound like a clean approach to me.
You could use binding link everything to a key value which changes based upon a timeline.
The following code will create a bunch of circles, then fire off blinking on them at various times, but once the blinking starts, all of the circles continue blinking in unison (don't run this if you are prone to epilepsy...)
You can play around with the values and the interpolators used in the timeline to get the effect you want.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.Random;
public class Synchronicity extends Application {
private static final double N = 10;
private static final double R = 10;
private static final Duration D = Duration.seconds(2);
private static final double MIN_VAL = 0.1;
private static final double MAX_VAL = 1;
private static final Random r = new Random();
private final DoubleProperty opacity = new SimpleDoubleProperty(MAX_VAL);
private final Timeline oscillator = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(opacity, MAX_VAL, Interpolator.EASE_BOTH)),
new KeyFrame(D.divide(2), new KeyValue(opacity, MIN_VAL, Interpolator.EASE_BOTH))
);
#Override
public void start(final Stage stage) {
Pane layout = new Pane();
for (int i = 0; i < N; i++) {
Circle circle = new Circle(R, Color.FIREBRICK);
circle.setCenterX(2 * R + i * R * 3);
circle.setCenterY(R * 2);
layout.getChildren().add(circle);
PauseTransition pause = new PauseTransition(D.multiply(r.nextDouble() * N));
pause.setOnFinished(e -> blink(circle));
pause.play();
}
layout.setMinSize(R + N * R * 3,R * 4);
stage.setScene(new Scene(layout));
stage.show();
oscillator.setAutoReverse(true);
oscillator.setCycleCount(Timeline.INDEFINITE);
oscillator.play();
}
private void blink(Node node) {
node.opacityProperty().bind(opacity);
}
public static void main(String[] args) {
launch(args);
}
}
I have recently created a libgdx game with this QuizDialog interface
and I have added the CountDownTimer which I have followed from the different tutorials here, but when I execute the game, it tells
Exception in thread "LWJGL Application" java.lang.RuntimeException: Stub!
at android.os.CountDownTimer.<init>(CountDownTimer.java:4)
at com.boontaran.games.SICT.LevelQuizDialog$2$1.<init>(LevelQuizDialog.java:184)
at com.boontaran.games.SICT.LevelQuizDialog$2.clicked(LevelQuizDialog.java:184)
at com.badlogic.gdx.scenes.scene2d.utils.ClickListener.touchUp(ClickListener.java:89)
at com.badlogic.gdx.scenes.scene2d.InputListener.handle(InputListener.java:57)
at com.badlogic.gdx.scenes.scene2d.Stage.touchUp(Stage.java:346)
at com.boontaran.games.StageGame.touchUp(StageGame.java:300)
at com.badlogic.gdx.backends.lwjgl.LwjglInput.processEvents(LwjglInput.java:325)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:199)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:114)
so far here is my code,
package com.boontaran.games.SICT;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Handler;
import android.os.CountDownTimer;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.NinePatch;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.boontaran.MessageEvent;
public class LevelQuizDialog extends Group {
public static final int ON_CLOSE = 1;
public static final int ON_OPEN = 2;
public static final int LevelCompleted2 = 1;
public static final int LevelFailed2 = 2;
//private Timer timer;
public LevelQuizDialog( int score) {
// final int score2 = score;
NinePatch patch = new NinePatch(SICT.atlas.findRegion("dialog_bg"),60,60,60,60);
Image bg = new Image(patch);
bg.setSize(980, 670);
setSize(bg.getWidth() , bg.getHeight());
addActor(bg);
final Image title = new Image(SICT.atlas.findRegion("quiz"));
addActor(title);
title.setX((getWidth() - title.getWidth())/2);
title.setY(getHeight() - title.getHeight() - 100);
final Image title2 = new Image(SICT.atlas.findRegion("level_completed"));
//addActor(title2);
title2.setX((getWidth() - title2.getWidth())/2);
title2.setY(getHeight() - title2.getHeight() - 100);
final Image title3 = new Image(SICT.atlas.findRegion("level_failed"));
// addActor(title3);
title3.setX((getWidth() - title3.getWidth())/2);
title3.setY(getHeight() - title3.getHeight() - 100);
Image image2 = new Image(SICT.atlas.findRegion("border"));
image2.setSize(300,120);
addActor(image2);
image2.setPosition(390,300);
//score label
LabelStyle style = new LabelStyle();
style.font = SICT.font1;
style.fontColor = new Color(0x624601ff);
Label label = new Label("Score :", style);
addActor(label);
label.setPosition(590, 600);
LabelStyle style2 = new LabelStyle();
style2.font = SICT.font2;
style2.fontColor = new Color(0x624601ff);
//the score
Label scoreLabel = new Label(String.valueOf(score) , style2);
addActor(scoreLabel);
scoreLabel.setPosition(800, 600);
//Label array1 = new Label("", array1); and the checking
Random random = new Random();
int array = random.nextInt(10) + 1;
int array2 = random.nextInt(10) +1;
final int answer = random.nextInt(20) +5;
final int checker = array + array2;
Label StringArray = new Label(String.valueOf(array), style2);
addActor(StringArray);
StringArray.setPosition(417,325);
Label StringArray2 = new Label(String.valueOf(array2), style2);
addActor(StringArray2);
StringArray2.setPosition(520, 325);
Label StringArray3 = new Label(String.valueOf(answer), style2);
addActor(StringArray3);
StringArray3.setPosition(620, 325);
//timer
//setPosition(20, 600);
final ImageButton CheckBtn = new ImageButton(
new TextureRegionDrawable(SICT.atlas.findRegion("check1")),
new TextureRegionDrawable(SICT.atlas.findRegion("check2")));
addActor(CheckBtn);
//CheckBtn.setSize(120, 120);
CheckBtn.setX(400);
CheckBtn.setY(70);
final ImageButton WrongBtn = new ImageButton(
new TextureRegionDrawable(SICT.atlas.findRegion("wrong1")),
new TextureRegionDrawable(SICT.atlas.findRegion("wrong2")));
addActor(WrongBtn);
//WrongBtn.setSize(120, 120);
WrongBtn.setX(550);
WrongBtn.setY(70);
CheckBtn.addListener(new ClickListener(){
#Override
public void clicked(InputEvent event, float x, float y) {
WrongBtn.clearListeners();
if(checker == answer )
{
removeActor(title);
addActor(title2);
}
if(checker != answer)
{
removeActor(title);
addActor(title3);
}
}
});
//fire event on button click
WrongBtn.addListener(new ClickListener(){
#Override
public void clicked(InputEvent event, float x, float y) {
CheckBtn.clearListeners();
if(checker != answer )
{
removeActor(title);
addActor(title2);
}
if(checker == answer)
{
removeActor(title);
addActor(title3);
}
new CountDownTimer(4000, 4000) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
fire(new MessageEvent(ON_CLOSE));
}
}.start();
}
});
}
}
You don't need any fancy stuff. You can access the frame time from anywhere in LibGDX, it's called delta or Gdx.graphics.getRawDeltaTime() since Gdx.graphics.getDeltaTime() gives a smoothed version. So all you need to do is having a counter that increases by this delta and a fixed value like 10 seconds.
Since you probably want to count down we should decrease the counter:
float time, counter = 10f;
public void update(float delta)
{
counter -= Gdx.graphics.getRawDeltaTime();
//if you have delta passed as a overload you can do
counter -= delta; //This is smoothed but should not matter for a quiz game
if (counter <= 3)
{
//Play annoying sound to make you stress even more
}
if (counter <= 0)
{
//Time is up!
//You can just reset the counter by setting it equal to time again.
//But there will almost always be left over (counter will be less then 0)
//So if this is important (probably not for a quiz game)
counter = time + -counter; //Add the time this round took too long.
}
}
This is all there is to it. If you want to draw the time in seconds you should round the counter up.
Exception in thread "LWJGL Application" java.lang.RuntimeException: Stub
You are using the two imports:
import java.util.Timer;
import android.os.CountDownTimer;
Also create a variable for the countdown timer (though that's my preferred style).
android.os.CountDownTimer countDownTimer = new android.os.CountDownTimer(4000, 4000) {
If I'm not wrong, you are running your game on your desktop; you can not use Android classes in your desktop build. They only contain stub implementations of the classes.
import android.os.CountDownTimer;
You are making a game not an application. Read more about game programming, and game loops. This answer is an example of very simple implementation of timers.
I have an Java FX scene with a start button and several rectangles which represent the tiles of a map. I also have drawn a sphere which represents my explorer (it has to explore the map), but I am having difficulties with running the animation.
In my OnMouseClicked handler for the start button, I start an algorithm for exploring the map which changes the position of the sphere and the colors of the tiles which have been visited. The problem is that the scene won't update itself while the algorithm is running, so I only get to see how the final scene will look like (after the algorithm has stopped running). How can I force a scene update so I can see all the color changes sequentially?
Later edit:
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Test extends Application {
private static final double boxOuterSize = 50;
private static final double boxInnerSize = 48;
private static final double boxCornerRadius = 20;
private Stage applicationStage;
private Scene applicationScene;
private static double sceneWidth = 1024;
private static double sceneHeight = 800;
private static HBox container = new HBox();
private static Group root = new Group();
private Rectangle[] rectangles = new Rectangle[10];
#Override
public void start(Stage mainStage) throws Exception {
applicationStage = mainStage;
container.setSpacing(10);
container.setPadding(new Insets(10, 10, 10, 10));
try {
applicationScene = new Scene(container, sceneWidth, sceneHeight);
applicationScene.addEventHandler(EventType.ROOT,(EventHandler<? super Event>)this);
applicationScene.setFill(Color.WHITE);
} catch (Exception exception) {
System.out.println ("exception : "+exception.getMessage());
}
applicationStage.setTitle("HurtLockerRobot - Tema 3 IA");
applicationStage.getIcons().add(new Image("icon.png"));
applicationStage.setScene(applicationScene);
for(int i=0; i<10; i++) {
Rectangle r = new Rectangle();
r.setFill(Color.BLUE);
r.setX(i * boxOuterSize);
r.setY(0);
r.setWidth(boxInnerSize);
r.setHeight(boxInnerSize);
r.setArcHeight(boxCornerRadius);
r.setArcWidth(boxCornerRadius);
r.setSmooth(true);
rectangles[i] = r;
root.getChildren().add(rectangles[i]);
}
container.getChildren().add(root);
Button startButton = new Button("Start");
startButton.setOnMouseClicked(new EventHandler<Event>() {
#Override
public void handle(Event arg0) {
for(int i=0; i<10; i++) {
rectangles[i].setFill(Color.RED);
// TODO: some kind of scene refresh here
}
}
});
container.getChildren().add(startButton);
applicationStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Initially all the rectangles are blue. The behavior I want to obtain here is to see the rectangles changing colors sequentially. The problem is that I only get to see the end result (all the rectangles change their color at the same time).
This is an old question and it caught my eye since this is a very general issue faced by people new to JavaFX.
The problem that OP is facing is because he updates all the rectangles at once, without waiting.
OP can wait by either creating a new Thread, put the thread on sleep for an estimated seconds for every iteration of the loop and then update the color of the rectangle on JavaFX application thread by using Platform.runLater.
#Override
public void handle(Event arg0) {
new Thread(() -> {
for(int i=0; i<10; i++) {
try {
Thread.sleep(1000); // Wait for 1 sec before updating the color
} catch (InterruptedException e) {
e.printStackTrace();
}
int finalI = i;
Platform.runLater(() -> rectangles[finalI].setFill(Color.RED));// Update on JavaFX Application Thread
}
}).start();
The above snippet is more of a traditional way of doing things. If we want to use the "JavaFX" ways of doing things, we can achieve the same by using an Animation.
Below is a code snippet which will wait for x-seconds before changing the color of the rectangle. It doesn't need any extra thread since the wait is handled by PauseTransition applied for each rectangle.
startButton.setOnMouseClicked(new EventHandler<Event>() {
#Override
public void handle(Event arg0) {
for(int i=0; i<10; i++) {
PauseTransition pauseTransition = new PauseTransition(Duration.seconds(i));
int finalI = i;
pauseTransition.setOnFinished(event -> rectangles[finalI].setFill(Color.RED));
pauseTransition.play();
}
}
});
It creates a PauseTransition for each rectangle and depending on its index in the array rectangles, it waits for the same number of seconds before updating the color.
This is because of :
exception : Test cannot be cast to javafx.event.EventHandler
Well, I have no idea how Class cast exception came up.
Otherwise, to delay, you can use Thread.sleep().
UPDATE:
Its good to use AnimationTimer to create an animation, you don't need to refresh anything.
Here, I have done a short EG to show color rect using FillTransition.
CODE:
import javafx.animation.FillTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class NewFXMain1 extends Application {
private static final double boxOuterSize = 50;
private static final double boxInnerSize = 48;
private static final double boxCornerRadius = 20;
private Rectangle rectangles = new Rectangle();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("rect");
Button btn = new Button();
StackPane root = new StackPane();
Rectangle r = new Rectangle();
r.setFill(Color.BLUE);
r.setX(2 * boxOuterSize);
r.setY(0);
r.setWidth(boxInnerSize);
r.setHeight(boxInnerSize);
r.setArcHeight(boxCornerRadius);
r.setArcWidth(boxCornerRadius);
r.setSmooth(true);
r.localToScene(boxOuterSize, boxOuterSize);
rectangles = r;
root.getChildren().add(rectangles);
btn.setText("display");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
FillTransition ft = new FillTransition(Duration.millis(3000), rectangles, Color.RED, Color.BLUE);
ft.setCycleCount(4);
ft.setAutoReverse(true);
ft.play();
}
});
root.getChildren().add(btn);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
}