Home > Mobile >  JavaFX Timeline locked at 1fps
JavaFX Timeline locked at 1fps

Time:11-25

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).

  • Related