Firebirds

Version 11 (coder, 24/11/2014 21:30)

1 1 coder
h1. Diving In: The Firebirds Sample
2 1 coder
3 2 coder
{{>toc}}
4 1 coder
5 9 coder
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 XNA tutorial code developed by Alfred Bigler and published in the German magazine "c't":https://www-heise.de/ct. The Firebirds Python and C++ code was written by Thomas Schott. 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++.
6 2 coder
7 4 coder
!>screenshot.png!
8 1 coder
9 1 coder
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. 
10 1 coder
11 10 coder
As development environment, you can use either the Eclipse IDE or the command line and an editor of your choice.
12 1 coder
13 1 coder
h2. App Structure
14 1 coder
15 11 coder
Like all libavg applications, Firebirds makes use of the [[App]] class. This class is instantiated in the last line of the file "firebirds.py":https://github.com/libavg/libavg/tree/master/src/samples/firebirds/firebirds.py:
16 1 coder
17 1 coder
<pre><code class="python">
18 1 coder
 app.App().run(FireBirds(), app_resolution='1280x720')
19 1 coder
</code></pre>
20 1 coder
21 11 coder
This line and an empty @FireBirds@ class are enough to open a 1280x720 window and run the libavg main loop until the @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). 
22 1 coder
23 4 coder
!>gameloop.png!
24 1 coder
25 4 coder
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. 
26 1 coder
27 4 coder
h2. Initialization
28 1 coder
29 4 coder
@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.
30 1 coder
31 7 coder
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. The scene graph can contain objects of classes defined by the Application 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:
32 1 coder
33 4 coder
!scenegraph.png!
34 1 coder
35 4 coder
Here are the parts of the @onInit()@ function that create the initial scene graph:
36 1 coder
37 4 coder
<pre><code class="python">
38 1 coder
class FireBirds(app.MainDiv):
39 1 coder
    def onInit(self):
40 1 coder
        self.__gameMusic = avg.SoundNode(href='Fire_Birds.mp3', loop=True,
41 1 coder
                volume=0.75, parent=self)
42 1 coder
        [...]
43 1 coder
        self.__shadowDiv = avg.DivNode(parent=self)
44 1 coder
        self.__gameDiv = avg.DivNode(size=self.size, parent=self)
45 1 coder
        self.__guiDiv = avg.DivNode(parent=self)
46 1 coder
        [...]
47 1 coder
        self.__player = PlayerAircraft(self.__shadowDiv, gunCtrl, parent=self.__gameDiv)
48 1 coder
        [...]
49 1 coder
        self.__gameMusic.play()
50 4 coder
</code></pre>
51 1 coder
52 4 coder
h2. Game Flow
53 1 coder
54 4 coder
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. 
55 1 coder
56 7 coder
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 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.
57 1 coder
58 11 coder
In addition, Firebirds uses two functions that it calls when the game starts (@__start()@) and when the player has no more lives left (@__stop()@). Subscribing to the "ON_FRAME":https://www.libavg.de/reference/svn/player.html#libavg.avg.Player.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 functions whenever an event occurs:
59 1 coder
60 4 coder
<pre><code class="python">
61 4 coder
class FireBirds(app.MainDiv):
62 4 coder
    def onInit(self):
63 4 coder
        [... Initialize scene graph ...]
64 4 coder
        self.__start()
65 4 coder
        player.subscribe(player.KEY_DOWN, self.__onKeyDown)
66 4 coder
        player.subscribe(player.KEY_UP, self.__onKeyUp)
67 4 coder
        [...]
68 4 coder
        
69 4 coder
    def __start(self):
70 4 coder
        [...]
71 4 coder
        self.__frameHandlerId = player.subscribe(player.ON_FRAME, self.__onFrame)
72 4 coder
        [...]
73 4 coder
 
74 4 coder
    def __stop(self):
75 4 coder
        player.unsubscribe(player.ON_FRAME, self.__frameHandlerId)
76 4 coder
        self.__frameHandlerId = None
77 4 coder
        [...]
78 4 coder
</code></pre>
79 1 coder
80 1 coder
Firebirds uses the following callbacks:
81 1 coder
82 4 coder
<pre><code class="python">
83 1 coder
  class FireBirds(app.MainDiv):
84 1 coder
          [...]
85 1 coder
      def __onKeyDown(self, event):
86 1 coder
          [...]
87 1 coder
      def __onKeyUp(self, event):
88 1 coder
          [...]
89 1 coder
      def __onFrame(self):
90 1 coder
          [...]
91 4 coder
</code></pre>
92 1 coder
93 7 coder
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. All game elements are positioned 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.
94 1 coder
95 4 coder
h2. The @PlayerAircraft@ Class
96 1 coder
97 4 coder
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. 
98 1 coder
99 11 coder
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. By themselves, @DivNodes@ are invisible, but they can have visible children. 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 a "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.
100 1 coder
101 4 coder
h2. Collision detection
102 1 coder
103 11 coder
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 [[Plugins|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.
104 1 coder
105 5 coder
h2. Conclusion
106 1 coder
107 8 coder
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 (as well as [[MouseAndTouchInput|touch input in general]]), [[OffscreenRendering|offscreen rendering]], [[RenderingAttributes|masking]] and other [[FXNodes|effects]].
108 1 coder
109 9 coder
So, there should be nothing preventing you from writing your own libavg apps at this point and updating your knowledge as you go.