Flash Game Dev Tip #10 – Flixels Internal Structure and Performance Tips

Tip #10 – Flixels Internal Structure and Performance Tips

If you’ve ever wondered just what Flixel does when it starts-up or runs its main loop, then wonder no more :) Here’s the full gory details, with some take-away performance tips at the end.

The Instantiation Process

All games in Flixel extend the FlxGame class, which in turn extends Sprite. When the game is created the following process happens, in the following order:

  1. It hides the system mouse cursor
  2. Calls FlxG.init which clears the bitmap cache, creates a new Sprite (flashGfxSprite) and creates an empty cameras Array
  3. Sets the internal game frame rate
  4. Sets the Flash Player frame rate
  5. Adds an ENTER_FRAME Event Listener which triggers FlxGame.create

Frame rates

Flixel 2.5 uses a Deterministic Delta Timer to handle steps within the framework. A step (as you’ll see later) is processed in the main loop, and is when Flixel performs all of the collision, separation and movement calculations. This is not the same thing as when it renders the game. When you create your game you have to tell FlxGame what game frame rate and Flash Player frame rates you want. From these two values it does the following:
  • Sets the step to be 1000 / Game Frame Rate. So a rate of 30 updates per second would equal a step of 33.
  • Sets the Maximum Accumulation (maxAcc) value to 2000 / Flash Player Frame Rate (fps) – 1.
  • So an fps rate of 30 would equal a maxAcc of 66. The maxAcc can never be less than step.

FlxGame.create

This function is only run once. It’s main responsibilities are creating the various event listeners that Flixel uses:

  1. It sits in a loop until it has access to root. Once it does the ENTER_FRAME event listener (created above) is removed
  2. The Stage scale mode is set to NO_SCALE, alignment to TOP_LEFT and the Flash Frame Rate is set (once again!)
  3. Mouse Up, Down and Mouse Wheel listeners are started
  4. Keyboard Up and Down listeners are started
  5. A new empty sprite called _mouse is created an added to the display list (it will contain the bitmap mouse cursor later)
  6. The FlxDebugger is created and added to the display list
  7. Creates the Sound Tray
  8. Creates the Lost Focus Screen (the large play arrow with the Flixel icon top left) and starts Event.ACTIVATE and DEACTIVATE listeners
  9. Adds an ENTER_FRAME for FlxG.onEnterFrame

Processing ends here until the next Enter Frame event fires. At that point we’re out of the creation phase and into the main loop.

You may feel like doing this to your computer by the end :)

The Main Loop – FlxGame.onEnterFrame

The guts of Flixel. Everything happens from here, one way or another.

  1. It starts by setting a var called mark to equal getTimer. The elapsedMS value is then calculated from this. That is the number of milliseconds that have elapsed since the last enter frame event occurred.
  2. The Sound Tray is updated
  3. If the SWF file has focus then it will process the core of this function, otherwise it bails out.

Core Loop Actions

  1. If the FlxDebugger has been created and is currently paused then it checks to see if a debug step was requested. If it was it runs step once.
  2. If the FlxDebugger is not paused, then the game is running normally and as such it will work out the Accumulator value. It does this by adding the elapsedMS total to it. If it’s higher than the maxAccumulator set in the creation phase then it’s set to equal maxAccumulator.
  3. Something very important happens here. If Accumulator is >= step then it calls the step function. It then reduces the value of Accumulator by step and checks again. It will literally loop this until the accumulator is no longer large enough to warrant running another step call. You’ll notice that when your game first starts up, due to default values step is likely to be called several times. I’ve seen the accumulator go 150, 117, 84, 51, 18 – at which point it stops. So it’ll literally run step 5 times. Typically in a normal game this isn’t the case. As the values balance out step is usually only called once or twice per loop. On some occasions, usually after some heavy garbage collection, step fails to be called at all. This is quite undesirable imho, but is how it works.

The Step

So what happens when Step is called? First it checks to see if there is a Reset Request pending. A Reset Request would occur if you had asked Flixel to change to a new FlxState. It will also always happen the very first time your game is started.

  1. It creates a new instance of the given FlxState
  2. Resets the Replay Timer
  3. Calls FlxG.reset

FlxG.reset

  1. Clears the Bitmap Cache (again :))
  2. Resets Input
  3. Destroys all sounds
  4. Resets various internal arrays and variables to zero
  5. Sets the TimeScale to 1.0
  6. Seeds the random number generator
  7. Sets the World Bounds to -10, -10, Width + 20, Height + 20
  8. Sets World Divisions to 6
  9. Creates the DebugPathDisplay Plugin

Step, continued …

After reset has finished it carries on with the Step:

  1. If a Replay or Replay Recording is requested, it will process that (this will never happen on the first run)
  2. If there is a new state (different from the current State) then switch to it (again, doesn’t happen on the first run)
  3. Set the ACTIVE COUNT to zero
  4. If Replaying it steps through the replay log, otherwise:
  5. Update Input: If a Key is pressed it retains the Boolean for it. If the Mouse has moved, it updates the mouse variables.
  6. If Recording, it records the frame
  7. Calls Update
  8. Sets the Mouse Wheel values to zero

FlxGame Update

  1. Calculates the Mark and FlxG.elapsed values based on the timescale. FlxG.elapsed = FlxG.timeScale * (step / 1000)
  2. Calls FlxG.updateSounds – Which handles panning, proximity and amplitude changes in sounds
  3. Calls FlxG.updatePlugins – Iterates through the plugins list and if active calls the plugin update method
  4. Calls FlxState.update – See below
  5. Calls FlxG.updateCameras – Iterates through the cameras Array (on first pass it’ll just have the one default camera in there). If the camera exists and is active it calls update on it, and the cameras flashSprite is moved based on the new x/y coordinates (if changed) or set in / visible.
  6. Finally if the FlxDebugger is visible it updates the timer on it.

Remember that this can all happen multiple times. Probably the most important part of it is #4 when it calls FlxState.update. FlxStates extend FlxGroup, they don’t have update functions of their own – but FlxGroup does. So every single object you add into your state (FlxSprites, FlxTileMap, etc) are all added into this one “master” group. Even other groups, Flixel doesn’t care how deeply nested they are.

FlxState.update iterates through every object in members array. Providing the object has exists = true and active = true, it calls 3 functions on it:

  1. preUpdate
  2. update
  3. postUpdate

Different objects perform different tasks in these three functions. For example an FlxSprite in its preUpdate call will track the last known coordinates for collision purposes, and also check if the sprite was moving on a path. In postUpdate the FlxSprite will update its motion and animation if there is any. Depending on the type of Flixel object depends what happens in these 3 functions.

It’s also well worth mentioning that typically in the update function of your FlxState is where you normally put commands such as FlxG.collide and FlxG.overlap. We will cover this in more detail in a later tutorial, but it’s still useful to know exactly when in the flow they are handled.

FlxGame.onEnterFrame, continued …

After running step and update as many times as the Accumulator wants, it then calls draw once, and ends the onEnterFrame function.

The draw operation is the most expensive part of Flixel, as it’s where it renders all of the cameras, sprites and objects that build-up the visual display. It does so in the following sequence:

Lock Cameras

FlxG.lockCameras is called, iterating through all active cameras. If useBufferLocking is true, then it will lock the bitmapData per camera (note that the default for this is false). It then calls FlxCamera.fill. This does a fillRect on the fill bitmapData object using a colour value of zero, effectively wiping it clean. It then does a copyPixels from fill to FlxCamera.buffer and finally sets FlxCamera.screen.dirty to true.

FlxState.draw

An FlxState doesn’t have a draw method of its own, although you can over-ride i should you need to do something after all of the children draw operations have finished. FlxState extends FlxGroup, which does have a draw method. Whenever you add something to your State, you are effectively adding it to one giant FlxGroup.

The FlxGroup draw method iterates through all objects in the groups members Array. If the object has exists = true and visible = true then it calls their own draw function. Depending on the data type of the object depends what happens next. The following is the draw process for an FlxSprite:

  1. The FlxSprite iterates through all of the cameras
  2. For every camera it checks if the sprite is visible within that camera
  3. If it is, it then renders the sprite in one of two ways:
  4. If the sprite isn’t rotated, or is using baked rotation, and NO scaling or blend modes have been applied – then it does a copyPixel from the FlxSprites framePixels direct to the camera buffer.
  5. If the FlxSprite is rotated, scaled, or has a blend mode then it creates a Transform Matrix. This is then translated, scaled and rotated accordingly. Once the matrix is ready is runs FlxCamera.buffer.draw using framePixels and the matrix. This is a considerably more expensive operation, so please try and always used baked rotation.
  6. Finally if visualDebug is on it also draws the debug data (such as the bounding box) to FlxG.flashGfxSprite

It will do the above sequence for every single FlxSprite used in your State! And of course not just sprites – but tile maps, buttons, text, etc. So do try and be careful about what you leave laying around in memory. Always make sure you set exists or visible to false when you’re done with an object. Although Flixel is clever enough to not bother doing a copyPixel if the object isn’t in any of the cameras views anyway, it will still avoid all of those checks. And if you’ve got thousands of sprites, that’s a lot of extra baggage you can shed.

However the draw process isn’t finished yet! We’ve just locked and cleaned all the cameras, rendered all the sprites and other objects, so the final two things it does are:

  1. FlxG.drawPlugins. This iterates through all of the plugins, and if they are active it will call their draw methods. It’s important that when creating a plugin you decide when it’s going to process whatever it does. As you can see from this flow plugins are updated FIRST, before all of the game objects (such as your FlxSprites) are. So if you have a plugin that modifies the x/y coordinates of an FlxSprite it’s important to realise that will happen before the calls to things like updateMotion or FlxG.collide – perhaps un-doing the work of your plugin in the process. The same is true of visual changes to sprites. Basically, test, test, test! Sometimes it’s better to have plugins perform graphical changes here, in their draw function. And sometimes it’s better to do it in their update.
  2. Once the plugins have all drawn FlxG.unlockCameras is run. As you can imagine this iterates through all the cameras, only this time if they are visible it will call their FlxCamera.drawFX functions. This internal function handles the flash, fade and shake effects built into Flixel. If any of them are running they are applied right here. Personally I think they ought to be moved to plugins :)
  3. After it’s all shaken and faded the cameras bitmapDatas are actually unlocked. But as you may recall bufferLocking is disabled by default, so in practise this doesn’t actually do much.

All of the above happens every single frame in your game! That could be 30 or even 60 times per second. So it’s easy to see that if you aren’t careful you can be making Flixel do lots more work than it actually needs to.

My take-aways from this post are:

  1. If you find the Delta Timer trouble-some it’s very easy to disable. Just edit the FlxGame.onEnterFrame method and remove the _accumulator checks, and just make sure that step() is run once, and draw() is run once. This is how earlier versions of Flixel used to work, and you may find it improves performance significantly.
  2. Be tidy! Re-use (pool) objects. Don’t leave references laying around. If a Sprite isn’t needed, make sure you kill it properly (active / exists / visible to false) to stop it being rendered.
  3. If you have loads of items in a FlxGroup, but have done something like stick a great big picture over the top, then disable that group until the picture is removed. It’ll save processing on all of the group items that are obscured from view but will be rendered regardless.
  4. Plan your plugins carefully. It makes a significant difference if they are update or draw based. Sometimes moving the processing logic to draw actually helps.
  5. Don’t set your game or flash frame rates too high. Especially if you are not using a zoom mode / have a larger resolution game. Keep them at 30/30 if you can.
  6. Rotated FlxSprites are expensive if you don’t use baked rotation!
  7. Scaling and Blend Modes are expensive! Use sparingly. If you just need a sprite to be say x4 larger in size than it actually is, and the scale won’t change during play, then consider either rendering that sprite x4 bigger in your art package and having 2 versions of it – or scaling it during the create phase of your game, then using the bitmapData to make the actual game sprites from. Basically do what you can to avoid that call to draw() with the matrix transform.
  8. Don’t be scared to dig deep into Flixels guts and change it :) There are definitely things that can be improved in there!

If you’ve made it this far well done! Relax by looking at some beautiful pixel art :)

Posted on July 21st 2011 at 10:47 pm by .
View more posts in Flash Game Dev Tips. Follow responses via the RSS 2.0 feed.


12 Responses

Leave a comment
  • July 21st 2011 at 11:41 pm

    Wow. Well done! I’m a huge performance tweaker (erm.) and I always hack the hell out of the core loop for every game so I can use the spare power for special effects and other Important Things ;)

    Something I found that helps a lot is to get rid of the unnecessary draw call that all _animated_ sprites make–when they change frames, they copy the new frame to framePixels, and then framePixels gets copied to the camera(s) buffer. So I dig into FlxSprite.calcFrame and delete the copyPixels() call. Then in FlxSprite.draw() I do camera.buffer.copyPixels(_pixels, _flashRect, …)… as opposed to camera.buffer.copyPixels(framePixels, _flashRect, …). Just have to be careful and make sure _flashRect isn’t altered during update() (by default it isn’t).

    That mod helps a lot if you have a ton of animated sprites on the screen, like I tend to have with all the explosions and item animations going on. But yeah, there’s totally a LOT to do to improve performance in Flixel. Every new release gives us interesting new features but a lot more overhead, so I do a lot of work to hack it down to only what is essential for my project.

  • July 22nd 2011 at 12:00 am

    Cheers Corey – for me the biggest “issue” I have with it, is that it’ll render EVERYTHING regardless. For example you could have 100 tiny sprites, all obstructed by 1 giant picture, and it’ll render all 101 items without caring if the ones “below” the picture can be seen or not. Of course this is not an easy problem to solve – and I wonder at what cost it would be to work out if an object is truly visible or not below another. It might be more expensive than just blitting the data regardless! Especially when you factor in alpha levels.

  • July 22nd 2011 at 1:27 am

    Oh, yeah that’s an interesting problem I haven’t run into (I do mostly top-down shmup-style games these days). I use a static grid for all of my collisions that works wonders, so to solve that one I’d probably go through each cell the bigger (ie occupies more than 1 cell) sprite occupies and see what’s in just the outter/border cells (since the big sprite has transparency around its edges), then add those to a unique draw list that the state loop goes through.

    Huh. That would be a bit of work, perhaps, and the savings would depend on how many sprites you usually have on screen. Just a thought!

  • Nshen
    July 22nd 2011 at 7:18 am

    Could you explain more detail about FlxG.overlap method,and what is FlxQuadTree and how it works?

  • July 22nd 2011 at 10:01 am

    I’ll deal with Flixel collision in another post (it’s a big subject!)

  • wolf
    July 22nd 2011 at 12:19 pm

    Great post! I’m not a grandmaster programmer so it always takes me a while through reading code and comments just to get a rough idea of what a class is doing, so to have it spelled out in detail like this is fantastic.

    1 question:
    If “an fps rate of 30 would equal a maxAcc of 66″, then how is it possible that you’ve “seen the accumulator go 150, 117, 84″? Shouldn’t it be limited to the maximum accumulation value?

  • July 23rd 2011 at 12:42 pm

    wolf – that phenomena only happens the first loop through flixel. When the elapsedMS time can be massive, it can take a few steps to work down through it. Once the game is up and running this rarely happens.

  • July 24th 2011 at 3:49 am

    Rich,

    Thanks for this entry. This goes perfectly with what you were talking about on the forums in response to my question about element indexing. In fact, I’m wondering if that’s what made you jump to this? It really helps put a name to the face so to speak on the AS3 display list as it pertains to Flixel.

  • ExpertNovice
    July 29th 2011 at 11:56 am

    Guys, please make your performance increases available via the forums! I think everyone would just LOVE you guys if you made Flixel even more awesome.

    Just don’t break commonly used code too often. ;)

  • Nublet
    February 8th 2012 at 12:59 am

    Hi, I’m no programmer, more of an artist, but I do understand concepts quickly, it’s just in my case I don’t have very much time because of my deadlines, so I’m asking you for help. I have created a ~15,000px wide and 960px high FlxSprite background, just that with a simple moving square, it is taking ~145 mb memory. No collisions yet, I just wanted to see if I could create a large background/level. How would you tackle this problem (large map with a moving square)?

  • Daniel
    December 29th 2012 at 6:48 am

    @Nublet

    You could manage that by breaking the level into an array of sprites, and only draw sprites that will be visible on the screen. The problem with your system is that you have a HUGE picture that flash has to move all of 40 times per second. Not very efficient when we only need to see 5% of it.

Make yourself heard