Firebirds

Version 2 (coder, 24/11/2014 20:39)

1 1 coder
h1. Diving In: The Firebirds Sample
2 1 coder
3 2 coder
{{>toc}}
4 1 coder
5 1 coder
This tutorial introduces libavg programming by means of the sample <i>Firebirds</i>. 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 by the magazine c't and developed by Alfred 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++.
6 2 coder
7 2 coder
!>screenshot.png!
8 2 coder
9 1 coder
10 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. 
11 1 coder
12 1 coder
As development environment, you can use either eclipse or the commandline and an editor of your choice.
13 1 coder
14 1 coder
h2. App Structure
15 1 coder
16 1 coder
Like all libavg apps, Firebirds makes use of the libavg [[App]] class. This class is instantiated in the last source line of the file:
17 1 coder
18 1 coder
<pre><code class="python">
19 1 coder
 app.App().run(FireBirds(), app_resolution='1280x720')
20 1 coder
</code></pre>
21 1 coder
22 1 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). 
23 1 coder
24 1 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. In Firebirds, these Python functions are located in the FireBirds class:
25 1 coder
26 1 coder
Application flow looks like this:
27 1 coder
28 1 coder
*** gameloop.png ***
29 1 coder
30 1 coder
Initialization
31 1 coder
--------------
32 1 coder
33 1 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.
34 1 coder
35 1 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. The scene graph can contain objects of classes defined by the App programmer. In our case, for instance, the aircraft are defined in an aircraft 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:
36 1 coder
37 1 coder
*** scenegraph.png ***
38 1 coder
39 1 coder
Here are the parts of the onInit function that create the initial scene graph:
40 1 coder
41 1 coder
class FireBirds(app.MainDiv):
42 1 coder
    def onInit(self):
43 1 coder
        self.__gameMusic = avg.SoundNode(href='Fire_Birds.mp3', loop=True,
44 1 coder
                volume=0.75, parent=self)
45 1 coder
        [...]
46 1 coder
        self.__shadowDiv = avg.DivNode(parent=self)
47 1 coder
        self.__gameDiv = avg.DivNode(size=self.size, parent=self)
48 1 coder
        self.__guiDiv = avg.DivNode(parent=self)
49 1 coder
        [...]
50 1 coder
        self.__player = PlayerAircraft(self.__shadowDiv, gunCtrl, parent=self.__gameDiv)
51 1 coder
        [...]
52 1 coder
        self.__gameMusic.play()
53 1 coder
54 1 coder
Game Flow
55 1 coder
---------
56 1 coder
57 1 coder
Once init 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. 
58 1 coder
59 1 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 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.
60 1 coder
61 1 coder
In addition, Firebirds uses two 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() calls shown below cause libavg to call specific functions whenever an event occurs:
62 1 coder
63 1 coder
  class FireBirds(app.MainDiv):
64 1 coder
      def onInit(self):
65 1 coder
          [... Initialize scene graph ...]
66 1 coder
          self.__start()
67 1 coder
          player.subscribe(player.KEY_DOWN, self.__onKeyDown)
68 1 coder
          player.subscribe(player.KEY_UP, self.__onKeyUp)
69 1 coder
          [...]
70 1 coder
          
71 1 coder
      def __start(self):
72 1 coder
          [...]
73 1 coder
          self.__frameHandlerId = player.subscribe(player.ON_FRAME, self.__onFrame)
74 1 coder
          [...]
75 1 coder
76 1 coder
      def __stop(self):
77 1 coder
          player.unsubscribe(player.ON_FRAME, self.__frameHandlerId)
78 1 coder
          self.__frameHandlerId = None
79 1 coder
          [...]
80 1 coder
81 1 coder
Firebirds uses the following callbacks:
82 1 coder
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 1 coder
92 1 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 available using player.getFrameDuration() and used to calculate movement in the game world. All game elements are 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 quickly enough.
93 1 coder
94 1 coder
The PlayerAircraft Class
95 1 coder
------------------------
96 1 coder
97 1 coder
Elements in a libavg scene graph are derived from the 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 1 coder
As an example, we'll go through the PlayerAircraft class. It's derived from _Aircraft, which is in turn derived from 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 that shows the actual airplane in motion. Since libavg is able to start and stop videos as well as loop them 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 with an added blur effect.
100 1 coder
101 1 coder
Collision detection
102 1 coder
-------------------
103 1 coder
104 1 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 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.
105 1 coder
106 1 coder
Conclusion
107 1 coder
----------
108 1 coder
109 1 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 handling (and touch input in general) and offscreen rendering as well as masking and other effects.
110 1 coder
111 1 coder
Still, there should be nothing preventing you from writing your own libavg apps at this point.