JavaFX Canvas 双缓冲

JavaFX Canvas Double Buffering

我正在 Java 使用 JavaFX 复制经典游戏 Pong。我在游戏循环中使用 java.util.Timer、java.util.TimerTask,在渲染中使用 JavaFX 的 Canvas。有没有办法向 Canvas 添加双缓冲,这样动画就不会闪烁?或者我应该以不同的方式处理这个问题吗?下面是代码。我删除了其中一些我认为无关紧要的部分,因为代码长约 200 行。

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());
}

达到 60 fps 的最佳方法是使用 AnimationTimer:

  1. 你可以通过服装来延长它class

 public class AnimationClass extends AnimationTimer {

    @Override
    public void handle(long nano) {
      //Code here
    }
}
  1. 您可以使用匿名立即实现它class

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

        }
    }.start();
}

一个很好的例子是here

您应该以不同的方式执行此操作。

  1. 定时器运行它自己的线程。此任务不需要额外的线程。
  2. 您正在 JavaFX 应用程序线程外对显示的 canvas 执行修改(您不应在 JavaFX 线程外修改场景中的对象)。
  3. JavaFX 有一个基于 pulse that is generated for each frame by the JavaFX system. This timer is called an AnimationTimer 的内置计时器,您应该使用它。
  4. 您不需要双缓冲。

其他更高级别的设施,例如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,不太适合他们。

您可以考虑将您的实现从使用 canvas 切换到场景图,这可能会使实现更容易一些,但您可以用任何一种方式进行编码。

您不需要双重缓冲 canvas,因为 JavaFX 架构是延迟绘图架构。您发出绘图命令并调用 api 来调整 JavaFX 应用程序线程上的场景图,然后,当您完成后,您放弃对 JavaFX 应用程序线程的控制。 JavaFX 将在内部计算出需要渲染的内容,并使用其内部渲染技术对查看的图像进行更新,该技术仅绘制完整的场景(或修补脏位)。 canvas 内部实现有一个命令队列,它会为每一帧刷新以呈现对 canvas 的任何更改,因此您不会获得部分更新。

此外,假设您有像 Pong 这样的基于物理的游戏,您可能想要引入速度等概念,将其应用于运动对象(例如球)并在动画回调的每次迭代中更新对象位置计时器(此技术在下面的弹跳球演示中进行了演示)。

您可能有兴趣阅读一些资源:

示例 AnimationTimer 代码(来自链接的弹跳球演示):

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();