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.
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.
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.
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 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
}
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 = ">"
}
}
}
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.
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,
By Carl Walker
President and Principal Consultant of Bekwam, Inc