JavaFX Canvas Double Buffering

☆樱花仙子☆ 提交于 2019-12-22 12:19:12

问题


I am replicating a classic game, Pong, in Java using JavaFX. I am using java.util.Timer, java.util.TimerTask for the game loop and JavaFX's Canvas for rendering. Is there a way to add double buffering to the Canvas so the animation doesn't flicker? Or should I approach this differently? Bellow is the code. I removed some parts of it, that I think are irrelevant, since the code is around 200 lines long.

Canvas canvas = new Canvas(stageW, stageH);
GraphicsContext gc;

public void start(Stage stage) throws Exception {
    Group root = new Group();

    gc = canvas.getGraphicsContext2D();

    Timer loop = new Timer();

    root.getChildren().add(canvas);

    loop.schedule(new GameLoop(), 0, 1000 / 60);

    stage.setScene(new Scene(root,stageW, stageH));
    stage.show();
}

public class GameLoop extends TimerTask {
    @Override
    public void run() {
        draw(gc);
        collisionDetect();
        ball.move();
    }
}

public void draw() {
    gc.setFill(Color.BLACK);
    gc.fillRect(0, 0, stageW, stageH);

    gc.setFill(Color.WHITE);
    gc.fillRect(lBat.getX(), lBat.getY(), lBat.getW(), lBat.getH());
    gc.fillRect(rBat.getX(), rBat.getY(), rBat.getW(), rBat.getH());
    gc.fillRect(ball.getX(), ball.getY(), ball.getW(), ball.getH());
}

回答1:


You should do this differently.

  1. Timer runs its own thread. You don't need an additional thread for this task.
  2. You are executing modifications to the displayed canvas off of the JavaFX application thread (you should not modify objects in the scene off of the JavaFX thread).
  3. JavaFX has an in-built timer based upon a pulse that is generated for each frame by the JavaFX system. This timer is called an AnimationTimer, you should use that.
  4. You don't need double buffering.

Other higher level facilities such as Timeline or Transitions could also be used, but they are primarily for scene graph objects and you are currently basing your implementation on a Canvas which is not well suited to them.

You could consider switching your implementation from using canvas to the scene graph, which might make the implementation a bit easier, but you can code it either way.

You don't need to double-buffer the canvas as the JavaFX architecture is a delayed drawing architecture. You issue drawing commands and invoke api to adjust the scene graph on the JavaFX application thread, then, when you are done, you relinquish control of the JavaFX application thread. JavaFX will work out internally what needs to be rendered and issue updates to the viewed image using it's internal rendering technology, which just draws complete scenes (or patches the dirty bits). The canvas internal implementation has a command queue which is flushed for each frame to render any changes to the canvas, so you don't get partial updates.

Additionally, given you have a physics based game like Pong, you might want to introduce concepts such as velocity that you apply to moving objects such as the ball and update the object position on each iteration of the callback from the animation timer (this technique is demonstrated in the bouncing ball demo below).

You may be interested in reading a couple of resources:

  • Background information on game loops in JavaFX
  • Explanation of the AnimationTimer
  • Bouncing Ball Demo

Sample AnimationTimer code (from the bouncing ball demo linked):

final LongProperty lastUpdateTime = new SimpleLongProperty(0);
final AnimationTimer timer = new AnimationTimer() {
    @Override
    public void handle(long timestamp) {
        if (lastUpdateTime.get() > 0) {
            long elapsedTime = timestamp - lastUpdateTime.get();
            checkCollisions(ballContainer.getWidth(), ballContainer.getHeight());
            updateWorld(elapsedTime);
            frameStats.addFrame(elapsedTime);
        }
        lastUpdateTime.set(timestamp);
    }
};
timer.start();



回答2:


The best way to achieve 60 fps is using AnimationTimer:

  1. You can extend it through a costume class

 public class AnimationClass extends AnimationTimer {

    @Override
    public void handle(long nano) {
      //Code here
    }
}
  1. You can implement it instantly with an anonymous class

  new AnimationTimer() {
        @Override
        public void handle(long now) {              

        }
    }.start();
}

A nice example is here.



来源:https://stackoverflow.com/questions/36459710/javafx-canvas-double-buffering

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!