Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Wednesday, December 30, 2015

A Looping Background for a Game Using JavaFX and Scene Builder

Games like Super Mario Bros or Flappy Birds scroll a background from right to left to give the appearance of motion.  This example instantiates a single long image twice and scrolls both.  While the first image scrolls off, the second image begins to appear.  The second image scrolls off and the first image -- now reset -- repeats the loop.


All of the code in this post can be found on GitHub at bekwam/examples-javafx-repos1.

This video demonstrates the technique.  A blue sky appears over a green ground.  Several white clouds come into view and are scrolled off the left.  Because the image is large and the white clouds were created individually, it isn't apparent right away that the background is in a loop.  Focusing on the game -- not just the scrolling -- will make the background even less relevant.



THIS IS NOW SANDBOXED

If you would like to see the animation for yourself, here is the JNLP: https://www.bekwam.net/background-sandbox/background.jnlp.

Originally, I had to make this an all-permissions.  Read more about the sandboxing here.

Alternatively, you can download the code and run it from your IDE.

Scene Builder

To start the demo off, let's look at Scene Builder.  I use two ImageViews containing the same 2000px image for the background.  The ImageViews are added to a Pane.  The first ImageView is partially displayed in the following screenshot (half of it is cropped by the Pane container).  The second ImageView is completely cropped.  It has an X of 2000, so it's not on the screen at startup.

Background App Scene Builder Definition
The Pane containing the ImageViews is combined with a control Button in a StackPane.

Scene Builder is key in this post because it lets me quickly preview the screen by simply pressing Control-P.  I can also tweak the resizing behavior and test the compression and spacing and different sizes.

Main Program

The main is a trivial display of a Stage and Scene.  The Scene Builder-generated FXML file is loaded using an FXMLLoader.  I start the animation by executing a method in the Controller.  The start action is initiated from the Stage's onShown.

Controller

The algorithm that I'm using for the app is to slide both ImageViews from right to left.  The first ImageView, background1, is initially shown.  Because background1 is wider than the screen, it is shown for a while.  Once the 2000 pixels of background1 start rolling off the screen, the second ImageView, background2, is shown.  It has been moving during background1's animation.  However, because it was offset with an X=2000 in Scene Builder, it doesn't come into view until background1 starts leaving the view.

This simultaneous motion -- both ImageViews moving but with an offset -- is perfect for a JavaFX ParallelTransition.  ParallelTransition can move two similarly-sized animations in lockstep.  In this case, I have identical TranslateTransitions for each ImageView.  Again, the difference in in the initial offset.

Here is the code for the ParallelTransition in the Controller @FXML initialize() method.  Note that the ParallelTransition's status drives the control Button at the end of the method.

private int BACKGROUND_WIDTH = 2000;
private ParallelTransition parallelTransition;
 
@FXML
public void initialize() {
  
 TranslateTransition translateTransition =
        new TranslateTransition(Duration.millis(10000), background1);
 translateTransition.setFromX(0);
 translateTransition.setToX(-1 * BACKGROUND_WIDTH);
 translateTransition.setInterpolator(Interpolator.LINEAR);
 
 TranslateTransition translateTransition2 =
        new TranslateTransition(Duration.millis(10000), background2);
 translateTransition2.setFromX(0);
 translateTransition2.setToX(-1 * BACKGROUND_WIDTH);
 translateTransition2.setInterpolator(Interpolator.LINEAR);

 parallelTransition = 
  new ParallelTransition( translateTransition, translateTransition2 );
 parallelTransition.setCycleCount(Animation.INDEFINITE);

 //
 // Sets the label of the Button based on the animation state
 //
 parallelTransition.statusProperty().addListener((obs, oldValue, newValue) -> {
  if( newValue == Animation.Status.RUNNING ) {
   btnControl.setText( "||" );
  } else {
   btnControl.setText( ">" );
  }
 });
}

The remainder of the Controller are the actions registered to the Button.  Recall that startAnimation() is also called from the Stage's onShown event.

public void startAmination() {
 
 parallelTransition.play();
}

public void pauseAnimation() {
 parallelTransition.pause();
}

@FXML
public void controlPressed() {
 if( parallelTransition.getStatus() == Animation.Status.RUNNING ) {
  pauseAnimation();
 } else {
  startAmination();
 }
}

This post highlights the power and simplicity of the Animations and Transitions of JavaFX.  With a single TranslateTransition, I can send an ImageView across the screen.  A pair of TranslateTransitions can be combined in a ParallelTransition to move two ImageViews in sync.  The composable transitions is a highly scalable design that will allow for layering more complex backgrounds.


No comments:

Post a Comment