Fountain Example
Line Break
Author: Janez Feldin (6 Articles) - Author Website
Janez likes to experiment with flash in his own free time. His other hobbies are playing volleyball, listening to the music, watching movies and above all else, paragliding.
Here is another example, this one more visually appealing (I hope). I made a class, Fountain, not particulary usefull by itself, but can be used to teach some basics for handling bitmap and bitmapdata objects.
I started by creating new flex project. Normal project for web application, and than created new class. I called it Fountain and extended UIComponent. UIComponent only so that bitmap can be added to it. Here is the code for that class:
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | package com.FlexBlog { import flash.display.Bitmap; import flash.display.BitmapData; import flash.events.Event; import flash.events.TimerEvent; import flash.filters.BlurFilter; import flash.geom.Point; import flash.utils.Timer; import flash.utils.getTimer; import mx.core.UIComponent; import mx.events.FlexEvent; public class Fountain extends UIComponent { /* declaration of bitmapdata which will show our particles, and bitmap for our bitmapdata */ private var bmd:BitmapData; private var bm:Bitmap; /* Array of objects. In each object we store positions, speeds,...*/ private var dp_particles:Array = new Array(); /* delay for redrawing the particles (small number -> fast motion, high number -> slow motion */ private var doMotionDelay:Number = 10; /* declaration of timer which calls continuously the function to redraw particles to bitmapdata */ private var motion_timer:Timer; /* some of the variables to change the visual appearance (try experimenting a bit) */ [Bindable] public var maxParticles:int = 300; /* defines how much particles can be created at once (it helps that particles doesn't appear in intervals) */ [Bindable] public var maxParticlesAtOnce:Number = 15; /* x and y position where the particles will appear */ [Bindable] public var sourcePoint:Point = new Point(350,700); /* defines the angle at wich particles will be shot */ [Bindable] public var startAngle:Number = 0; /* particles can be shot at 45° different angle tan start angle */ [Bindable] public var deltaAngle:Number = 45; [Bindable] public var maxStartSpeed:Number = 7; [Bindable] public var minStartSpeed:Number = 3; /* if the particles are bounding they never leave the visible area, so we need another criteria to know when to delete them. If dampingFactor is set to 1 particles won't loose speed when bouncing so they won't be deleted */ [Bindable] public var minEndSpeed:Number = 1; [Bindable] public var gravityY:Number = 0.1; [Bindable] public var gravityX:Number = 0; [Bindable] public var blurFilter:BlurFilter = new BlurFilter(1.5,1.5,1); /* defines how much speed each particle keeps when bouncing of the walls (0 -> no bouncing, 1 -> continuous bouncing) */ [Bindable] public var dampingFactor:Number = 0; /* array of colors for particles. Type color more times if you want more particles to have that color instead of other colors. */ public var colors:Array = [0x0000FF,0x00FF00,0xFF0000,0x00FFFF,0xFF00FF,0xFFFF00]; /* main function of the class, it gets called when you define the class */ public function Fountain() { // since we extended UIComponent we need to inherit // all the properties of new UIcomponent. We achieve // that with calling super() super(); // when the UIComponent initialize and is // fully created, we call the function appComplete this.addEventListener(FlexEvent.CREATION_COMPLETE, appComplete); } /* function to add bitmapdata and bitmap to stage, and to start our drawing by starting the motion_timer */ private function appComplete(e:Event):void { // since we want the width and height of our fountain to // match the width and height set in mxml (or in actionscript // if we define new fountain with actionscript and not mxml) // for our component, we need to define new bitmapdata in // appComplete because before appcomplete gets called this.width // is not yet defined! bmd = new BitmapData(this.width,this.height,false,0x000000); // because we cannot add bitmapdata to stage (or another visual component) // we need to create new bitmap that holds our bitmapdata object bm = new Bitmap(bmd); // finnaly we can indirectly add our bitmapdata to UIComponent this.addChild(bm); // we create new timer, add event listener to it so we // can continuously redraw particles motion_timer = new Timer(doMotionDelay); motion_timer.addEventListener(TimerEvent.TIMER, doMotion); // after everything is set, we start the timer motion_timer.start(); } // function that creates new particles when dp_particles // length is less than maxParticles private function createParticles():void { // some temp variables var i:int; var newParticles:int; // variable that stores tha number of particles // that will be created var temp_speed:Number; var temp_angle:Number; // if statment that checks if we can create all missing // particles at once (because of the maxParticlesAtOnce) // here more particles are missing, than we allow them to // spawn at a time if ( maxParticles-dp_particles.length > maxParticlesAtOnce ) { // we get the random number which will define how much particles // will be created at once, because we can't create all the missing // particles, the random number must be lower than MaxParticlesAtOnce newParticles = Math.ceil(Math.random()*maxParticlesAtOnce); } else { // maxParticles-dp_particles.length defines the number of missing // particles, and also makes sure that our random number is not larger // than this one newParticles = Math.ceil(Math.random()*(maxParticles-dp_particles.length)); } // for loop to create new particles for(i=0;i<newParticles;i++) { // calculates random speed betwen minStartSpeed and maxStartSpeed temp_speed = minStartSpeed + Math.random()*(maxStartSpeed-minStartSpeed); // calculate the starting angle (example: if we have startAngle=90 // and deltaAngle 45 the starting angle can be anywhere between 45 and 135 // with Math.PI/180 transforms angle from ° to radians temp_angle = (-90+startAngle+Math.random()*deltaAngle-deltaAngle/2)*Math.PI/180; // adds new object (particle) to our array of particles with // calculated starting values // with angle we split our speed to x and y direction (easier to draw // if we have x and y speed than speed and angle) // for color we generate random integer between 0 and colors.length // to randomly select color from our array of colors dp_particles.push({x: sourcePoint.x,y: sourcePoint.y, vx: temp_speed*Math.cos(temp_angle), vy: temp_speed*Math.sin(temp_angle), color: colors[Math.floor(Math.random()*colors.length)]}); } } // function which redraws our particles on bitmapdata private function doMotion(e:TimerEvent):void { var i:int; // temporary variable to know if the particle we are // curently rendering was deleted var particle_deleted:Boolean; // we apply the filter to bitmapdata to look cooler // also the filter is applied before the new position is calculated // because we want to blur the trail of the particle only and not // the particle itself bmd.applyFilter(bmd,bmd.rect,new Point(),blurFilter); // if statment to check if there are any particles // missing. If there are we call createParticles() to create new ones if ( dp_particles.length < maxParticles ) { createParticles(); } // for loop to go trought all particles and recalculate their new // position, also checks if any particles are out of visible range // and delete them for(i=0;i<dp_particles.length;i++) { // we set the particle_delted to false just to make sure // that it isn't set to true from last particle particle_deleted = false; // we redraw curently rendering particle, and than // calculates new x and y position (the order could be reverse) bmd.setPixel(dp_particles[i].x,dp_particles[i].y,dp_particles[i].color); // for new position we only add the speed to the curent position dp_particles[i].x = dp_particles[i].x+dp_particles[i].vx; dp_particles[i].y = dp_particles[i].y+dp_particles[i].vy; // two if statements to chek for bounce if ( dp_particles[i].x <= 0 || dp_particles[i].x >= this.width ) { // fro bounce from wall we reverse the direction // of speed and also multiply the speed with dampingFactor // so that we can set how particle bounce dp_particles[i].vx = -dp_particles[i].vx * dampingFactor; } if ( dp_particles[i].y <= 0 || dp_particles[i].y >= this.height ) { dp_particles[i].vy = -dp_particles[i].vy * dampingFactor; } // if statement to check if particle has speed low enough // to be deleted or if it is out of visible range (actualy 5 pixels from sides, // because particles that are bouncing will never leave the visible area) if ( Math.abs(dp_particles[i].vx) < minEndSpeed && Math.abs(dp_particles[i].vy) < minEndSpeed && (dp_particles[i].x < 5 || dp_particles[i].x > this.width-5 || dp_particles[i].y < 5 || dp_particles[i].y > this.height-5) ) { // we remove the particle from out array with splice function. // First parameter (i) is the index, the second one is the count // of the parameters that should be deleted, and next parameters // would be the values to be added to array (we don't need this one) dp_particles.splice(i,1); particle_deleted = true; } // we need to check that particle wasn't deleted. If the particle // would be deleted and we still applied the gravity effect, we // would apply the gravity effect to wrong particle or get an // error(if the deleted particle would be the last one in our array) if ( !particle_deleted ) { dp_particles[i].vx += gravityX; dp_particles[i].vy += gravityY; } } } } } |
After we have created the class we only need to add it to the stage. I did it with mxml, I also added a form which had some sliders in it to quickly change settings.
Here is the mxml code:
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | <?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:FlexBlog="com.FlexBlog.*" minWidth="955" minHeight="600" xmlns:local="*"> <!-- we add our fountain class to stage with mxml. Same could be achieved with actionscript by var name_for_variable:Fountain = new Fountain()--> <FlexBlog:Fountain id="_fountain" x="300" width="700" height="700" /> <!-- Some form items to set the properties of the fountain. each form item is bind to some variable in our Fountain class this is only for quick settings --> <mx:Form left="10" top="10" width="280" bottom="10"> <mx:FormItem label="Max particles" width="100%"> <s:HSlider width="100%" minimum="1" maximum="1000" value="@{_fountain.maxParticles}"/> </mx:FormItem> <mx:FormItem label="Max new Particles" width="100%"> <s:HSlider width="100%" minimum="1" maximum="50" value="@{_fountain.maxParticlesAtOnce}"/> </mx:FormItem> <mx:FormItem label="Source point x" width="100%"> <s:HSlider id="slider_source_x" width="100%" minimum="0" maximum="{_fountain.width}" value="200" change="_fountain.sourcePoint.x = slider_source_x.value"/> </mx:FormItem> <mx:FormItem label="Source point y" width="100%"> <s:HSlider id="slider_source_y" width="100%" minimum="0" maximum="{_fountain.height}" value="200" change="_fountain.sourcePoint.y = slider_source_y.value"/> </mx:FormItem> <mx:FormItem label="Start angle" width="100%"> <s:HSlider width="100%" minimum="0" maximum="360" value="@{_fountain.startAngle}"/> </mx:FormItem> <mx:FormItem label="Delta angle" width="100%"> <s:HSlider width="100%" minimum="0" maximum="360" value="@{_fountain.deltaAngle}"/> </mx:FormItem> <mx:FormItem label="Gravity x" width="100%"> <s:HSlider width="100%" stepSize="0.05" minimum="-1" maximum="1" value="@{_fountain.gravityX}"/> </mx:FormItem> <mx:FormItem label="Gravity y" width="100%"> <s:HSlider width="100%" stepSize="0.05" minimum="-1" maximum="1" value="@{_fountain.gravityY}"/> </mx:FormItem> <mx:FormItem label="Damping factor" width="100%"> <s:HSlider width="100%" stepSize="0.1" minimum="0" maximum="1" value="@{_fountain.dampingFactor}"/> </mx:FormItem> </mx:Form> </s:Application> |
Here is a screenshot of the fountain in action (its animating in the real example, ofcourse):
Related posts:



