Firebirds

Version 6 (coder, 24/11/2014 21:03) → Version 7/14 (coder, 24/11/2014 21:13)

h1. Diving In: The Firebirds Sample

{{>toc}}

This tutorial introduces libavg programming by means of the sample _Firebirds_. This sample is a small 2D Shooter similar to "1942: Joint Strike": The player pilots a World War II fighter, shooting at enemy fighters and dodging them while the landscape scrolls vertically below the planes. Firebirds is based on an XNA tutorial code by the magazine c't and developed by Alfred Bigler and published in the German magazine "c't":https://www-heise.de/ct. Bigler. In the tutorial, we will go through the basic structure of a libavg app, including initialization, the update loop and general scene structure. We'll also cover deriving your own classes from libavg classes and introduce libavg plugins written in C++.

!>screenshot.png!

The complete finished firebirds code can be found in the libavg distribution under @src/samples/firebirds@ (or "online":https://github.com/libavg/libavg/tree/master/src/samples/firebirds), with images, videos and sounds under @media/@ and the plugin source code in the @plugin/@ subdirectory. The libavg code for the game is under 500 lines long - half as many as the original XNA code, even though we've added code to generate shadows of the planes on-the-fly. In addition, we detect collisions between two objects on the screen in a pixel-precise way using a simple libavg plugin. The tutorial assumes that libavg is already installed on the computer.

As development environment, you can use either the Eclipse IDE or the commandline and an editor of your choice.

h2. App Structure

Like all libavg applications, apps, Firebirds makes use of the libavg [[App]] class. This class is instantiated in the last source line of the file:

<pre><code class="python">
app.App().run(FireBirds(), app_resolution='1280x720')
</code></pre>

This line and an empty FireBirds class are enough to open a 1280x720 window and run the libavg main loop until the @Esc@ Esc key is pressed. The app has several command-line parameters (try starting it with @--help@ or @--app-fullscreen=True@) and debug keys ('?' shows a list).

!>gameloop.png!

In libavg, the main loop (for game programmers: 'Game Loop') is handled internally by the library, which calls Python functions in application code whenever certain events occur. The graph on the right shows the control flow in the application. In Firebirds, these Python functions are located in the FireBirds class.

h2. Initialization

@onInit()@ is called once at program start, directly after the window has been opened. In it, the scene is initialized and media such as video and sound clips as well as images are loaded. Of course, the scene can be changed and content loaded later as well.

Content in libavg is stored in a tree structure, the so-called scene graph. This tree contains all media elements (called Nodes) along with the information necessary to render them - such as position, size and opacity. Similar to html, the graph is structured using "DivNodes":https://www.libavg.de/reference/svn/areanodes.html#libavg.avg.DivNode. @DivNodes@. The scene graph can contain objects of classes defined by the Application App programmer. In our case, for instance, the aircraft are defined in a separate class, with two derived classes for the player's and enemy aircraft. Other elements in the scene graph include bullets, a scrolling background, and the components of the heads-up display such as the score, lives left and a gun heat gauge:

!scenegraph.png!

Here are the parts of the @onInit()@ function that create the initial scene graph:

<pre><code class="python">
class FireBirds(app.MainDiv):
def onInit(self):
self.__gameMusic = avg.SoundNode(href='Fire_Birds.mp3', loop=True,
volume=0.75, parent=self)
[...]
self.__shadowDiv = avg.DivNode(parent=self)
self.__gameDiv = avg.DivNode(size=self.size, parent=self)
self.__guiDiv = avg.DivNode(parent=self)
[...]
self.__player = PlayerAircraft(self.__shadowDiv, gunCtrl, parent=self.__gameDiv)
[...]
self.__gameMusic.play()
</code></pre>

h2. Game Flow

Once @onInit()@ has been called, the application enters the main loop. In the main loop consists of handling events, updating the world state and redrawing the screen. Events from input devices (keyboard, mouse, touch, etc.) and timers are handled in callbacks that the application defines, such as @__onKeyDown()@ and @__onKeyUp()@. In @__onFrame()@, the application calculates a new world state.

The actual rendering is handled internally by libavg. By default, updates in libavg are synced to screen updates. Usually, these take place 60 times per second, so there are 16.7 milliseconds seconds available for one iteration of the game loop. If the loop takes longer, we miss a screen update and there is a chance the game will stutter.

In addition, Firebirds uses two functions functions, @__start()@ and @__stop()@, that it calls when the game starts (@__start()@) and when the player has no more lives left (@__stop()@). Subscribing to the @ON_FRAME@ event and deregistering is also done in these functions, while subscribing to keyboard events happens in @onInit()@. The "subscribe()":https://www.libavg.de/reference/svn/basenodes.html#libavg.avg.Publisher.subscribe calls shown below cause libavg to call the named specific functions whenever an event occurs:

<pre><code class="python">
class FireBirds(app.MainDiv):
def onInit(self):
[... Initialize scene graph ...]
self.__start()
player.subscribe(player.KEY_DOWN, self.__onKeyDown)
player.subscribe(player.KEY_UP, self.__onKeyUp)
[...]

def __start(self):
[...]
self.__frameHandlerId = player.subscribe(player.ON_FRAME, self.__onFrame)
[...]

def __stop(self):
player.unsubscribe(player.ON_FRAME, self.__frameHandlerId)
self.__frameHandlerId = None
[...]
</code></pre>

Firebirds uses the following callbacks:

<pre><code class="python">
class FireBirds(app.MainDiv):
[...]
def __onKeyDown(self, event):
[...]
def __onKeyUp(self, event):
[...]
def __onFrame(self):
[...]
</code></pre>

In our application, @__onKeyDown()@ and @__onKeyUp()@ functions only keep track of the current key states. Actions resulting from key presses are handled during @__onFrame()@, in @PlayerAircraft.update()@. @__onFrame()@ calculates a new world state: It moves some Nodes and creates and deletes others. All game logic is here, collisions are detected, points are counted, etc.. The time delta between one frame and the next is always available using "Player.getFrameDuration()":https://www.libavg.de/reference/svn/player.html#libavg.avg.Player.getFrameDuration and used to calculate movement. movement in the game world. All game elements are positioned moved in accordance with the time elapsed. The alternative - moving them a fixed distance per frame - should be avoided, since that would cause the gameplay to become faster or slower depending on the capabilities of the computer. Worse, if syncing to screen updates is off, the game might update so quickly that the player has no chance to react. react quickly enough.

h2. The @PlayerAircraft@ Class

Elements in a libavg scene graph are derived from the "Node":https://www.libavg.de/reference/svn/basenodes.html#libavg.avg.Node class. libavg provides a number of derived Node classes for images, videos, etc., and the application can derive it's own classes as well.

As an example, we'll go through the @PlayerAircraft@ class. It's derived from @_Aircraft@, which is in turn derived from "DivNode":https://www.libavg.de/reference/svn/areanodes.html#libavg.avg.DivNode, so it can be inserted into the scene graph and will be rendered each frame along with the rest of the scene. @DivNodes@ are intrinsically invisible, and since an invisible aircraft makes for bad gameplay, the @_Aircraft@ class creates several nodes for the visuals and adds them as child nodes into the tree. The most important one is "VideoNode":https://www.libavg.de/reference/svn/areanodes.html#libavg.avg.VideoNode that shows the actual airplane in motion. Since libavg is able to start and stop videos as well as loop them practically instantaneously, this is the easiest way to create moving images of game elements. The videos have an alpha (transparency) channel so they blend seamlessly into the background. If the plane explodes, a second video containing the explosion is played. Shadows are handled here as well, but placed in a separate layer in the scene graph so they're behind all aircraft. Shadows are "ImageNodes":https://www.libavg.de/reference/svn/areanodes.html#libavg.avg.ImageNode with an added "blur effect":https://www.libavg.de/reference/svn/fx.html#libavg.avg.BlurFXNode.

h2. Collision detection

Detecting collisions between aircraft in a pixel-precise way would be too slow if it were done in Python. For this reason, we implemented a small libavg plugin - a C++ library that extends libavg. The @collisiondetector@ plugin defines a new class that is exposed to python, the @CollisionDetector@. It is initialized with two bitmaps, and when @detect()@ is called, it compares the bitmaps pixel-by pixel to see if any non-transparent pixels are at the same position. If so, we have a collision.

h2. Conclusion

This tutorial has covered many of the things necessary to build general libavg apps: The lifecycle, main loop and scene graph are the same whether you're building a scrolling game or a serious application. The same goes for deriving Node classes, reacting to input events and handling application state changes. Of course, we could only introduce a few of the libavg features. We've missed a lot of interesting things, such as gesture support (and [[MouseAndTouchInput|touch input in general]]) and [[OffscreenRendering|offscreen rendering]], rendering]] as well as [[RenderingAttributes|masking]] and other [[FXNodes|effects]].

Still, there should be nothing preventing you from writing your own libavg apps at this point.