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());
}
You should do this differently.
- Timer runs its own thread. You don't need an additional thread for this task.
- 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).
- 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.
- 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:
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();
The best way to achieve 60 fps is using AnimationTimer:
- You can extend it through a costume class
public class AnimationClass extends AnimationTimer {
@Override
public void handle(long nano) {
//Code here
}
}
- 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