~~NOTOC~~ ====== FFParticleSystem ====== ---- dataentry extension ---- author_mail : michael.trenkler@flintfabrik.de Michael Trenkler description : A particle system based on the original with some new features and various performance improvements # enter a short description of the extension lastupdate_dt : YYYY-MM-DD # the date you created the extension compatible : v1.4 (should work in older versions though) # the Starling version you tested the extension with depends : # if the ext. depends on others, list them here tags : particles, effects, 71squared # enter a few tags, separated by commas homepage_url : https://github.com/shin10/Starling-FFParticleSystem # if the ext. has an URL (e.g. a Gist-page), add it here download_url : https://github.com/shin10/Starling-FFParticleSystem/archive/master.zip # a direct link to the download (e.g. the Gist-archive) ---- ===== Overview ===== {{:extensions:ffpsdemob.jpg?750|live demo}} This is particle system is based on and compatible to the [[extensions:particlesystem|original]] but provides some additional features and various performance improvements. Thanks a lot to [[http://colinnorthway.com/|Colin Northway]] ([[http://www.incredipede.com/|Incredipede]]) sponsoring this extension created for his upcoming game. **Important:** I did anything to make the code as fast as possible but to get the best performance I strongly recommend using the [[http://www.bytearray.org/?p=4789|ASC 2.0 compiler]] and setting it up for [inline] functions. Feature Overview: * particle pool * batching (less draw calls) * multi buffering (avoid stalling) * animated particles * random start frames * filter support * optional custom sorting, code and variables * calculating bounds (optional) * spawnTime, fadeIn/fadeOut * emit angle alligned particle rotation * various performance improvements Additional information about the new features and the [[http://www.flintfabrik.de/pgs/starling/FFParticleSystem/|live demo (~ 5 MB)]] can be found on [[http://www.flintfabrik.de/blog/improved-particle-system-for-starling|my page.]] ===== Configuration ===== Like it's ancestor it's configured with XML files (.pex) which can be created comfortible with tools like the following. * [[http://particledesigner.71squared.com|Particle Designer]], the original from 71squared. It's a great tool that contains access to a large online library of free emitters. Warmly recommended for any OS X user! * [[http://onebyonedesign.com/flash/particleeditor/|Particle Editor]] is a free online tool built with Starling. It lacks the online emitter library, but allows editing and previewing directly within the browser. (More info [[http://blog.onebyonedesign.com/flash/particle-editor-for-starling-framework/|here]].) {{ :extensions:particle-designer.png?400 |Particle Designer from 71squared}} For some of the new features however you'll have to modify your XML by hand or set the properties with AS3. ===== Usage ===== The system is similar to the original PDParticleSystem, but there are some minor differences. To get started you'll have to know a little bit about the two main classes. * SystemOptions * FFParticleSystem Since reading XMLs is very slow (~400x slower then an Object) FFParticleSystem will not be configured directly. Instead you will parse the config, texture and atlasXML if necessary through SystemOptions which can be reused, cloned, modified and so on. Further the SystemOptions will prepare lookup tables for animated textures etc. That way the instantiation of a FFParticleSystem is much faster. Before you create your first FFParticleSystem call the static init() function to create a particle pool and the vertex buffers. Setting this up properly can give you a massive performance boost. Read more about this in the section about batching below. A basic example: // embed configuration XML [Embed(source="fire.pex", mimeType="application/octet-stream")] private static const FireConfig:Class; // embed particle texture [Embed(source = "particle.png")] private static const ParticleTexture:Class; // instantiate embedded objects var psConfig:XML = XML(new FireConfig()); var psTexture:Texture = Texture.fromBitmap(new ParticleTexture()); // create system options var sysOpt:SystemOptions = SystemOptions.fromXML(psConfig, psTexture); // init particle systems once before creating the first instance // creates a particle pool of 1024 // creates four vertex buffers which can batch up to 512 particles each FFParticleSystem.init(1024, false, 512, 4); // create particle system var ps:FFParticleSystem = new FFParticleSystem(sysOpt); ps.x = 160; ps.y = 240; // add it to the stage (juggler will be managed automatically) addChild(ps); // change position where particles are emitted ps.emitterX = 20; ps.emitterY = 40; // start emitting particles ps.start(); // emit particles for two seconds, then stop ps.start(2.0); // stop emitting particles; on restart, it will continue from the current state ps.pause(); // it will continue from the current state ps.resume(); // stop emitting particles; on restart, it will start from scratch ps.stop(); ===== Batching and multi buffering ===== Batching is one of the new features this extension comes with. I assume you are familiar with the general requirements. In addition to those only FFParticleSystems can get batched with each other. They have to share the same parent and be siblings next to each other. Important to know is that the particles get batched into a number of static buffer shared among all systems and created by a single call of the static init() function where the size and number of buffers will be set. So cutting it short, the buffer has to be big enough to hold the batched particles. This is made by design to avoid recreation and garbage collection. A low value may reduce batching abilities, a high value will result in increased upload time and a higher amount of memory. So choosing a reasonable value is important. The last parameter of the init() function is the number of buffers. If you upload a buffer to the GPU and draw it, it will be locked for writing until the GPU is done with the complete frame (including all other draw calls). That means that if you want to write again to it, you'll have to wait until the image has been present//ed// by your graphics card. The buffers will most likely not be freed for writing until the next frame but one. That's why we cycle through a Vector of buffers. This way we can avoid stalling and run additional AS3 code instead of waiting for the GPU to be done. Try to set this value to the number of draw calls caused by FFParticleSystem instances per frame multiplied by two. For example: If you have a maximum of three particle systems, further #1 and #2 will get batched; this results in a total of 2 calls of drawTriangles(); so we'll set the number of buffers to a minimum of 4 for double buffering. Of course memory on mobile devices is sacred so you'll have to trade wisely between your memory and performance. ===== Animations and random start frames ===== Using animated textures and/or random frames is simple. Just add an animation snippet like the following to your .pex or set the values for your SystemOptions directly with AS3. ... All you have to do besides that is feeding the SystemOptions with the xml file for the texture atlas. That way it can look up the frame information and will create a LUT for the frames, cache and pass it on to the particle system. // embed configuration XML [Embed(source="fire.pex", mimeType="application/octet-stream")] private static const FireConfig:Class; // embed particle texture [Embed(source = "particleAtlas.png")] private static const ParticleAtlasTexture:Class; // embed configuration XML [Embed(source="particleAtlas.xml", mimeType="application/octet-stream")] private static const ParticleAtlasXML:Class; // instantiate embedded objects var psConfig:XML = XML(new FireConfig()); var psTexture:Texture = Texture.fromBitmap(new ParticleAtlasTexture()); var psAtalasXML:XML = XML(new ParticleAtlasXML()); // create system options // the atlasXML is necessary for particles with animated texture var sysOpt:SystemOptions = SystemOptions.fromXML(psConfig, psTexture, psAtlasXML); var ps:FFParticleSystem = new FFParticleSystem(sysOpt); ... ===== Optional custom sorting, code and variables ===== Often you won't have to care about the depth index of your particles. Many effects like fire etc. do not need this because of the used blend mode. For all other cases where the index matters you'll have to sort the particles yourself. Additionally you can use this functions to add custom code for your personal needs. If a particle get's removed it will be swapped with the last active particle and deactivated. Let's say you're using blend mode "normal" this will lead to strange behavior. Custom sorting can help you out but also has it's pitfalls. Even though I have added the following versions to my demo code, I want to clarify that only the last is a good choice and the others have only been added for better understanding. This example will totally **fail**: sysOps.sortFunction = ageSortDesc; ... // super bad! private function ageSortDesc(a:Particle, b:Particle):Number { if (a.currentTime < b.currentTime) return 1; if (a.currentTime > b.currentTime) return -1; } return 0; } Since the complete Vector of particles (active and inactive) will be sorted we have to check whether the particle is alive to avoid an even crazier behavior. This example is working but performing **poor**: sysOps.sortFunction = ageSortDesc; ... // bad! private function ageSortDesc(a:Particle, b:Particle):Number { if (a.active && b.active) { if (a.currentTime < b.currentTime) return 1; if (a.currentTime > b.currentTime) return -1; } else if (a.active && !b.active) { return -1; } else if (!a.active && b.active) { return 1; } return 0; } That's a lot of conditionals for comparing a simple value. Luckily there is a much faster way by preparing the inactive particles for sorting. So here is a much **better** example: sysOps.customFunction = customParticleFunction; sysOps.sortFunction = ageSortDesc; ... private function customParticleFunction(particles:Vector., numActive:int):void { // optional custom code for active particles var p:Particle; for (var i:int = 0; i < numActive; ++i) { p = particles[i]; ... // p.customVariables can be used to store additional data } // preparations for sorting according to age! var len:int = particles.length; for (i = numActive; i < len; ++i) { // set current time to a high value, keeping them at the end of the Vector particles[i].currentTime = Number.MAX_VALUE; } } private function ageSortDesc(a:Particle, b:Particle):Number { if (a.currentTime > b.currentTime ) return -1; if (a.currentTime < b.currentTime ) return 1; return 0; } Sorting particles based on their age is only necessary if new particles get added/removed, so this can stay as it is. If you're sorting on another property which might change at any time you can set the forceSortFlag property to true. ===== Changelog ===== * //2014/05/02 09:38//: First public version ===== Source Code ===== You can browse the source code of the particle system on its [[https://github.com/shin10/Starling-FFParticleSystem|GitHub page]]. ===== User Comments =====