cl

David Grace

Bay Area, California

Programmer, SysAdmin, Artist

Advanced Flixel Tutorials: Object Recycling

I use Adam Atomic’s excellent lightweight 2D Flash game library called Flixel. But this article is not a tutorial about how to use Flixel or write games in Flixel; There are plenty of pre-existing blogs which do an excellent job of teaching the basics.

My goal for these series of articles will be to teach more advanced Flixel concepts. Not all of these concepts will be specific to just Flixel, so you might get use out of my tutorial even if you are using something like Flashpunk or an entirely different platform.

Alright, so let’s start with the first one!

Aggressive Object Reycling

Let us take a brief detour and examine the internal mechanism that Flixel uses for representing objects and how it renders a frame. If you’re familiar with the internals of Flixel, you can skip this.

Note: I use “object” in the sense of any component which inherits from the basic Flixel component (FlxBasic) and is meant to be a part of your scene graph. I’m also using “scene graph” to indicate Flixel’s own internal drawing order, which is similar to Flash’s concept of a display list but unconnected. It’s also not exactly a scene graph in the traditional sense, so keep in mind that term is an approximation.

A brief overlook of Flixel’s scene graph

Let us start simple. FlxBasic is the super class that all Flixel objects inherit. FlxBasics is just a shell for a few pieces of core information about your Flixel objects, and does not contain spatial information, nor do they attempt render themselves in any manner. (The draw method is empty, but it is called during a frame render.) They are simply a high-level container for all things Flixel.

The first two useful children of FlxBasic are FlxGroup and FlxObject.

Root of your game objects: FlxObject

FlxObject is the host for a lot of spatially-minded Flixel constructs, such as FlxSprites and FlxText, and in fact it is the most basic component in Flixel which still will participate in physics and collision detection. FlxObjects do not render to a camera in of themselves (outside the debug visualizer), but they contain a bounding box that defines their area and so can easily represent almost any kind of 2D object you wish.

Most of your game will consist of objects descended from FlxObject.

Meet the DisplayObjectContainer analogue: FlxGroup

FlxGroup is a container. Its entire purpose in life is to hold other FlxBasics. In Flixel, it is the elementary building block for scene graph traversal. You start at a root FlxGroup and then walk down each of its members, and that is your Z-index render order.

What is the root FlxGroup? Simple: That is your FlxState. FlxState is no more than a special FlxGroup which has a special method called create which typically kicks off your entire game. When you create a FlxState and add stuff to it, you are setting up the Flixel scene graph.

So in the end, combining the two primary descendants of FlxBasic, you end up with a visual structure such as:

Scene graph processing order

FlxGame calls the draw and update methods on the current FlxState, which then fires the appropriate draw/update methods on all of its children. A single Flash frame looks like so in Flixel:

Flixel of course splits this into two distinct phases: Rendering and updating. When you bring up the Flixel in-game debugger, this refers to the two values underneath the FPS and memory counters.

You can see these counters in the top right corner.

Example of debug stats in Flixel.

The number of objects which were updated and drawn are displayed, including the total time each phase required.

I’m not going to go into this in more detail, as all we need to really understand is that Flixel does two passes through our pool of FlxBasics which consist of our game: An update phase, where typically your control logic happens (such as game play, collisions, etc.) and a render phase. This is critical for the next section, and later for explaining the difference between the visual and active flags.

Runaway scene graph

Why is understanding how Flixel renders a frame important?

In an exceedingly simplified summary: The more FlxBasics which are added to your scene graph, the longer it takes to process a frame and the slower your game will run. So our goal should be to minimize the number of objects which Flixel must iterate over when updating a frame of your game.

A common occurrence is to repeatedly add objects to a FlxGroup. Let’s say you have a bullet for a gun, and when that bullet hits the wall, it creates an explosion and then kill()s itself. The explosion animates over a few frames and then kill()s itself in kind.

Everything seems to work and your game is quite zippy. Now what happens after about five minutes of play in the same level?

It might not be a concern when you’re in the early phases of your game development, but if you take this naive approach to managing your scene graph you will quickly find your frame rates plummeting.

Why? Remember that FlxGroups are containers. They hold FlxBasics, which are usually FlxSprites and assorted rendered objects. If you keep adding objects to this group but never remove them, then the time to process each frame will rise linearly on average. (It’s a little worse off, in that Flixel typically completes two passes through your scene graph: The first time calling update, and the second time calling thedraw methods.)

There are two ways to deal with this.

Spring cleaning

One solution is to be sure to remove our FlxBasics as they go out of activity. This method would entail performing a remove() once you are done with the object.

Such as in the kill() method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestSprite extends FlxSprite
{

protected var _group:FlxGroup ;

public function TestSprite (group:FlxGroup)
{

super () ;
makeGraphic (10, 10, 0xFF00FF00) ;

_group = group ; // Hold on our group so we can remove ourselves from it later
_group.add (this) ; // Add this to our FlxGroup
}

override public function kill ():void
{

super.kill() ; // Sets us to not exist
_group.remove (this) ; // Remove us from our FlxGroup
}
}

There is really nothing seriously wrong with this method. For infrequently added objects, that’s about as complicated as it needs to be. It isn’t ideal that you are creating a new object and then adding/removing it from the FlxGroup, but in most cases it won’t be a huge performance drain if you’re creating a few dozen objects over a handful of seconds.

Recycling

If you want to display a lot of short-lived objects, a better method is to use the object pool design pattern. This is a simple caching method where objects are recycled from a fixed pool. And, in fact, Flixel has built-in support for object pools.

If you have used a FlxEmitter before, then you are already used an object pool. When you create particles with the makeParticles method, you are creating a fixed FlxGroup with a fixed pool of objects. The emitter simple recycles particles as needed instead of creating new particles.

Before we delve further into recycling, let’s take a closer look at a FlxBasic and the different flags that Flixel uses to manage the state of a FlxBasic.

Lifetime of a FlxBasic

When you create a new FlxBasic, whether it is a FlxObject, FlxSprite, etc, it contains four flags which are set by Flixel as true by default.

  • exists: When an object exists, is processed as part of the render/update loop. If an object does not exist, then Flixel will not call its update or draw method at any point during a Flash frame. It overrides all other flags.

  • active: When an object is active, the update method is called.

  • alive: All objects start out alive. When the kill method is called, the alive and exists flags are cleared, so that the object does not update/draw.

  • visible: Easy enough to grasp. When an object is visible, the draw method is called. When it is not visible, the update method will still be called but draw will be skipped.

If an object doesn’t exist, then neither active or visible matter. So in short you can sum up the relations between these flags as:

The alive flag might seem initially somewhat redundant. And in fact it is not directly referenced at all during the FlxState frame loop. But it’s actually quite useful as a way to flag an object as non-functional but still a part of the Flixel scene graph. You can do this by overriding the FlxBasic kill method so that does not set exists=false. This way, the object’s update and draw methods will still be called per frame, but the object can be checked to see if it is alive by your game logic. An example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class MyEnemy extends FlxSprite
{

public function MyEnemy()
{

loadGraphic (MyEnemySpriteSheet, 10, 10) ;
addAnimation ("run", [0, 1, 2, 3, 4], 10, true) ;
addAnimation ("dying", [5, 6, 7, 8], 5, false) ;

play ("run") ;
}

override public function kill():void
{

super.kill() ;

exists = true ; // Keep this FlxSprite as part of the scene graph

play ("dying") ;

var deadTimer:FlxTimer = new FlxTimer() ;
deadTimer.start (1.0, 1, onDeadTimeout) ; // Wait a second
}

override public function update():void
{

super.update() ;

if (alive) // Make sure we do our typical AI stuff while we're alive
doNormalEnemyAI() ;
}

private function onDeadTimeout (timer:FlxTimer):void
{

timer.stop() ;
exists = false ; // Alright, now we can set this as non-existent
}
}

But I am detouring a bit too much here. Let’s return to the exists flag. When an object is set to exists=false, then it is no longer considered needed by the Flixel scene graph. It is at this point it can be overwritten by Flixel or in what we’re about to see, be reused by Flixel as part of a recycle pool.

This is an important concept for our next section.

Managing a FlxGroup pool

As of Flixel 2.55, we can set a maximum size to our FlxGroups in the constructor via the maxSize parameter. What this does by default is prevent you from adding more than maxSize objects to our FlxGroup. But when used in conjunction with the pooling methods, it allows us to easily manage our pool.

The default behavior is to allow unbounded growth of a FlxGroup. The length of the Array which FlxGroup internally uses to store its list of FlxBasics continues to grow without limit.

When you pass in a maxSize as part of the FlxGroup’s constructor (or use maxSize setter), you then place a limit on the size of the Array. After it reaches maxSize, it will not grow. The FlxGroup add method will silently fail. It doesn’t matter the state of the exist flag for objects already in the FlxGroup. As far as Flixel is concerned at this point, when you want to add something you must remove something else, first.

In order to complete our FlxGroup pool, we can utilize the getFirstAvailable method in order to find the first object we can recycle. This method cycles over the FlxGroup pool and locates the first object which has exist set to false.

Note that this returns the first thing it finds which might not be of the right type. For example, if this group contains objects of type MyEnemy1 and MyEnemy2, we might not want to recycle a MyEnemy2 as a MyEnemy1. So you can pass in a class as search parameter to getFirstAvailable in order to make sure you receive a proper recycle candidate.

One strategy is to use the add method to keep adding objects until we hit a maximum, and then usegetFirstAvailable from then on in order to find candidates for recycling. But why manage it ourselves? Flixel can do this for us!

The recycle method is designed specifically for this concept in mind. You pass it a class which represents the object you wish to create. If the FlxGroup has its maxSize set and that pool has less than maxSize, it will create a new instance and add it to the pool. Otherwise it will return the first object that is available (ie: has exists=false).

If maxSize isn’t set, then recycle will grow the FlxGroup as needed, but prefer to return the first object which has exists set to false.

And that’s all we need in order to create an object pool in Flixel.

Putting it all together

If you’ve followed along so far, congratulations! You have what you need in order to intelligently manage object pools in Flixel. So let’s put all of the into some actual practical use with real code.

You can try out the tutorial by clicking [PLAY], or you can [DOWNLOAD] and try it yourself.