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.
CodePudding user response:
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).