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:
- 你可以通过服装来延长它class
public class AnimationClass extends AnimationTimer {
@Override
public void handle(long nano) {
//Code here
}
}
- 您可以使用匿名立即实现它class
new AnimationTimer() {
@Override
public void handle(long now) {
}
}.start();
}
一个很好的例子是here。
您应该以不同的方式执行此操作。
- 定时器运行它自己的线程。此任务不需要额外的线程。
- 您正在 JavaFX 应用程序线程外对显示的 canvas 执行修改(您不应在 JavaFX 线程外修改场景中的对象)。
- JavaFX 有一个基于 pulse that is generated for each frame by the JavaFX system. This timer is called an AnimationTimer 的内置计时器,您应该使用它。
- 您不需要双缓冲。
其他更高级别的设施,例如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();
我正在 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:
- 你可以通过服装来延长它class
public class AnimationClass extends AnimationTimer {
@Override
public void handle(long nano) {
//Code here
}
}
- 您可以使用匿名立即实现它class
new AnimationTimer() {
@Override
public void handle(long now) {
}
}.start();
}
一个很好的例子是here。
您应该以不同的方式执行此操作。
- 定时器运行它自己的线程。此任务不需要额外的线程。
- 您正在 JavaFX 应用程序线程外对显示的 canvas 执行修改(您不应在 JavaFX 线程外修改场景中的对象)。
- JavaFX 有一个基于 pulse that is generated for each frame by the JavaFX system. This timer is called an AnimationTimer 的内置计时器,您应该使用它。
- 您不需要双缓冲。
其他更高级别的设施,例如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();