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 ...

Saturday, January 9, 2016

Composable JavaFX ParallelTransitions in a Layered Game Background

This blog post demonstrates how to wrap several ParallelTransitions to produce an interesting animation background.  The background scrolls from right to left giving the effect of motion.  The background consists of hills and clouds which are moving at different rates.  This looks more realistic from the point-of-view of the user than a simple animation where all objects in the background move in lock step.

All of the code in this demonstration is available on GitHub.  Look for the files with names starting with "Improved".  This post builds on an earlier one and code from the earlier post is included in this Maven project.

This video shows a background with hills and clouds moving at different rates across the screen.



Scene Builder


This screenshot shows the Scene Builder .fxml used in the demonstration.  A StackPane holds the animation and overlays a control Button.  The animation is packaged in a Pane which allows for absolute positioning.  The Pane includes 4 ImageViews:

  • background1
  • background2
  • clouds1
  • clouds2

Scene Builder Showing 4 ImageViews in a Pane

clouds1 and clouds2 are displayed over background1 and background2.  The cloud PNGs are mostly transparent, allowing the hills of the background PNGs to show.  All the ImageViews (and images) are 2000px wide and 800px high.

The "ones" -- cloud1 and background1 -- are positioned at X=0.  The "twos" --- cloud2 and background2 -- are offset by their widths (2000px) by setting their X's to 2000.  The Pane controls the viewing area which is smaller than the ImageViews (1024x768 resizable to 1920x800).

The Algorithm


The animation works by shifting ImageViews from right to left in relation to the viewable area.  As background1 (starting at 0) is rolled off the screen, background2 (starting at 2000) comes into view.  As background2 rolls off the screen, background1 was reset and begins scrolling into view.  cloud1 and cloud2 work in the same way.  Remember, the cloud ImageViews are mostly transparent and displayed over the background ImageViews.

 @FXML
 ImageView background1;

 @FXML
 ImageView background2;

 @FXML
 ImageView clouds1;

 @FXML
 ImageView clouds2;

To get the effect of moving at different rates, the cloud ImageViews are animated at a different rate than the background ImageViews.  The animation is performed with a set of TranslateTransitions wrapped up in two levels of ParallelTransitions.  The TranslateTransitions shift the ImageViews from right to left by animation the ImageView X property.  The TranslateTransitions use the same fromX and toX properties, but use different Durations depending on if it is a cloud ImageView or a background ImageView.  Remember, that the "ones" are initialized with different Xs than the "twos".

 TranslateTransition background1Transition =
         new TranslateTransition(Duration.millis(8000), background1);
 background1Transition.setFromX(0);
 background1Transition.setToX(-1 * BACKGROUND_WIDTH);
 background1Transition.setInterpolator(Interpolator.LINEAR);
 
 TranslateTransition background2Transition =
            new TranslateTransition(Duration.millis(8000), background2);
 background2Transition.setFromX(0);
 background2Transition.setToX(-1 * BACKGROUND_WIDTH);
 background2Transition.setInterpolator(Interpolator.LINEAR);

 TranslateTransition clouds1Transition =
         new TranslateTransition(Duration.millis(20000), clouds1);
 clouds1Transition.setFromX(0);
 clouds1Transition.setToX(-1 * BACKGROUND_WIDTH);
 clouds1Transition.setInterpolator(Interpolator.LINEAR);
 
 TranslateTransition clouds2Transition =
            new TranslateTransition(Duration.millis(20000), clouds2);
 clouds2Transition.setFromX(0);
 clouds2Transition.setToX(-1 * BACKGROUND_WIDTH);
 clouds2Transition.setInterpolator(Interpolator.LINEAR);

ParallelTransitions


As mentioned earlier, two sets of ParallelTransitions are used in the animation.  ParallelTransitions allow you to run animations at the same time.  For the first level of animation, the background ImageViews are paired in a ParallelTransition and the cloud ImageViews are paired in a ParallelTransition.  This shifts each set of ImageView equally and allows for one ImageView to come into view before the other has left the view.

 ParallelTransition backgroundWrapper = new ParallelTransition(
   background1Transition, background2Transition
   );
 backgroundWrapper.setCycleCount(Animation.INDEFINITE);
  

 ParallelTransition cloudsWrapper = new ParallelTransition(
   clouds1Transition, clouds2Transition
   );
 cloudsWrapper.setCycleCount(Animation.INDEFINITE);

The second level of ParallelTransition is to allow the faster animation -- the background -- to restart while the clouds finish up.  You can put all the TranslateTransitions into one ParallelTransition, but there would be a pause when the background animation finishes early.  (The hills stop moving while the clouds continue for another 4 seconds.)

 ParallelTransition parallelTransition = 
  new ParallelTransition( backgroundWrapper,
     cloudsWrapper );

 parallelTransition.setCycleCount(Animation.INDEFINITE);

There is some additional code for the Button that will play() and pause() the animation.  The Button's text is set using a ChangeListener bound to the animation's status property.

Running the App


You can run the app from the source code, or run a sandboxed version here.

This design can be expanded to allow other aspects of the background to move at different rates.  For example, I could introduce a second hills layer that moves independently using another set of ImageViews.  You have to be a little careful not to go to the well one too many times.  This animation works because it's difficult to pick up the pattern of clouds.  However, it's easier to see the pattern of hills.  This is ok because the user will likely be focused on the graphics involved in the gameplay.  If I wanted to display something like trees that are going by at a rate quicker than the hills, I might introduce some randomness, sending different trees or clusters of trees across the screen in a trees layer.


2 comments: