bekwam courses

Scrolling Game Backgrounds in Kotlin

July 23, 2017

This article describes how to animate a game background as in Flappy Birds. The solution is written in Kotlin using TornadoFX, a JavaFX framework. Two large images of 2,000px are scrolled off the screen from right to left. One image (background2.svg.png) is a background showing the ground, sky, and varying mountains. The other image (clouds.svg.png) is a sparse set of clouds.

The background image is the basis for two ImageView objects which provide seamless scrolling. As one background ImageView scrolls off the screen to the left, another enters the view from the right. This technique is also applied to the cloud ImageViews. The cloud ImageViews are a layer on top of the background ImageViews. Since the cloud image is mostly transparent, the background image is always visible.

Although Scene Builder is not used in this coding demonstration, the following screenshot shows the screen area visible to the user and the long image followed by another long image waiting to scroll into view.

Screenshot of ImageViews Arranged in Scene Builder
Off-Screen Images and ImageView Arrangement

The cloud ImageViews scroll at a different rate. This simulates nature. The closer objects, the mountains, appear to move faster than the clouds. This could be extended to other layers, say a house or mailbox closer than the mountains which would enter and leave the screen faster than both the mountains and the clouds.

This is a video demonstration of the application.

App

The app uses a basic main entry point which is a TornadoFX App "BackgroundApp" and a single TornadoFX View "BackgroundView".

class BackgroundApp : App(BackgroundView::class)

class BackgroundView : View("Background App"){

  private val BACKGROUND_WIDTH = 2000.0
  private val BACKGROUND_DURATION = Duration.seconds(5.0)
  private val CLOUDS_DURATION = Duration.seconds(11.0)

  private val backgroundWrapper = ParallelTransition()
  private val cloudsWrapper = ParallelTransition()

  private val parallelTransition = ParallelTransition(
    backgroundWrapper, cloudsWrapper
  )
  private var btnControl: Button by singleAssign()

After a few constants, some ParallelTransition objects are declared. These are container transformations that are initially empty. After child animations are added, the child animations can be played all at once using a play() command on the ParallelTransition. ParallelTransitions can be nested and there is a ParallelTransition containing both a background ParallelTransition and a cloud ParallelTransition.

Layout

The TypeSafe Builders in TornadoFX give you the ability to build a hierarchical graph of components (the Scene Graph) using declarative programming statements. The root member variable from BackgroundView defines the graph which is a Pane containing four ImageViews wrapped in a StackPane plus a Button. Layout instructions regarding the alignments, sizes, and margins follow.

override val root = stackpane {
  pane {
    imageview("/backgroundapp-images/background2.svg.png") {
      backgroundWrapper.children += createTranslateTransition(this)
    }
    imageview("/backgroundapp-images/background2.svg.png") {
      x = BACKGROUND_WIDTH
      backgroundWrapper.children += createTranslateTransition(this)
    }
    imageview("/backgroundapp-images/clouds.svg.png") {
      cloudsWrapper.children += createTranslateTransition(this,CLOUDS_DURATION)
    }
    imageview("/backgroundapp-images/clouds.svg.png") {
      x = BACKGROUND_WIDTH
      cloudsWrapper.children += createTranslateTransition(this,CLOUDS_DURATION)
    }
  }

  btnControl = button(">") {
    stackpaneConstraints {
      alignment = Pos.BOTTOM_CENTER
      margin = Insets(0.0, 0.0, 40.0, 0.0)
    }
    setOnAction {
      controlPressed()
    }
  }

  alignment = Pos.CENTER
  minWidth = 768.0
  minHeight = 320.0
  prefWidth = 1024.0
  prefHeight = 768.0
  maxWidth = 1920.0
  maxHeight = 800.0
}

TranslateTransition

TranslateTransition is a JavaFX Animation class that is a convenient way to animate the lateral motion of an object. Four TranslateTransitions are created for each pair of background and cloud ImageViews. The doubling-up of instances is needed since as one instance is sliding off to the left into a negative position, the second instance begins to scroll into view from the right. This happens for both the background pair and the clouds pair, though at different rates.

This factory function will be used to create the ImageViews in the application. The function takes a Node (the ImageView) and a duration. The factory calls for the background can omit the duration parameter since there is a default.

private fun createTranslateTransition(n : Node, duration : Duration = BACKGROUND_DURATION) : TranslateTransition {
    val ttrans = TranslateTransition(duration, n)
    ttrans.fromX = 0.0
    ttrans.toX = -1 * BACKGROUND_WIDTH
    ttrans.interpolator = Interpolator.LINEAR
    return ttrans
  }

ParallelTransition

The init{} method of the View flags the outermost ParallelTransition as INDEFINITE which means that it will run in an infinite loop unless stopped by the button. A ChangeListener is added to the ParallelTransition which changes the Button display to give the user a visual cue of the run state.

init {
  backgroundWrapper.cycleCount = Animation.INDEFINITE   cloudsWrapper.cycleCount = Animation.INDEFINITE   parallelTransition.cycleCount = Animation.INDEFINITE
  parallelTransition.statusProperty().addListener { _, _, newValue ->
    if (newValue === Animation.Status.RUNNING) {
      btnControl.text = "||"
    } else {
      btnControl.text = ">"
    }
  } }

Control

The Button starts and stops the animation with a simple play() or pause() command. This article applied some slight-of-hand to an animation. Pairs of ImageViews were scrolled off and back onto the screen at different rates. The ParallelTransition class enabled the ImagesViews to be paired and manipulated so that they moved in concert. ParallelTransition is composable so that once the background animation and the clouds animation were set, they themselves were combined. A single Button control started and stopped the entire animation. For future study, look at the SequentialTransition class which uses the same pattern but will run the animations in sequence (insertion order) rather than all at once.

Resources

The source code presented in this article series is a Gradle project found in the zip file below.

To run the demo, create an Application configuration in your IDE that will run the BackgroundApp class. In IntellIJ,

  1. Go to Run > Edit Configurations
  2. Select Application and press +
  3. Name the configuration
  4. Select the BackgroundApp Main Cblass.

Headshot of Carl Walker

By Carl Walker

President and Principal Consultant of Bekwam, Inc