
I don’t usually post about two lines of code snippets, or about workarounds to that damn bug or about basic tutorials for beginners. I honestly haven’t enough time and will to post on a daily basis. Usually I use this blog to mantain me focused on a single project I find interesting and take it to the end (not a really working however ).
Logic comes first, code is the last step
So what about the title? What’s the the perfect beginner but somehow satisfying tutorial?
Answer snow/rain/starfield and you got it, and here we have a basic starfield class tutorial.
However the main reason I am posting this is not about the 30-40 code lines involved in rendering a random starfield but make you stop a moment and think about a possible easier way to do what you are doing.
I’ll try to be clearer: this morning I was working on a flash animation and just wanted to place a background starfield on my bucholic landscape view. So I quickly created the class but was missing something… off course a starfield is not cool if stars don’t twinkle (winkle, scintillate
shine… vary in light intensity etc.). So I started to recall the milions of experiments like this I did years ago in Actionscript 1 and ok… the first answer was something like:
- Generate every star as a sprite/movieclip (possibily in a main container)
- list the instance in some array, dictionary (whatever you prefer)
- to make ‘em twinkle, at every frame, do a for loop on every instance and vary its alpha with some some increment/decrement/sine calculation (o simply randomize it if you are lazy)
or you can have a Star class togheter with the Starfield class and let the Star class think about everything, so it creates its EnterFrame event to start twinkling when added (but it’s always a bad idea to have hundres of EnterFrame events, when you can have only one in the main class.
But…
Flash teach me that there are always many options to get to the same results (that is what I really like most about flash developing), AND… that bitmap filters and blend modes are a gift to hardcore developers with tremendous possibilities.
So.. do we really want a subtle background effect to keep 50% of the CPU? I don’t.
perlinNoise is not the solutions… is how you use it
The main idea here is simply use a perlinNoise, which we know is a tremendous versatile function.
But how to use perlinNoise to let our stars twinkle?
One first option could be to create a bitmapData, apply perlinNoise with a moving offset at every frame, do a for loop on every pixel and using getPixel grab the color of that pixel and use it to change the corresponding star instance alpha.
What could we gain from this? NOTHING… simply you don’t have to do variations math in other way, but now we still have the for loop, but with even more code in it, and remember this loop must be running at every frame. We passed from 50% CPU use to 75%!
So what the solution? Easy: we keep perlinNoise, but we use masks and blendModes.
Just look at this simple code:
var sprite:Sprite=new Sprite();
addChild(sprite);
for(var i:uint=0; i<10; i++) {
for(var j:uint=0; j<10; j++) {
sprite.graphics.beginFill(0xffffff);
sprite.graphics.drawCircle(25+j*50, 25+i*50, 20);
}
}
which produces this result.
now look at this, we add a background bitmap with a perlinNoise function applied before of the previous code:
var offs:Array=[new Point(0,0), new Point(0,0)];
var bd:BitmapData=new BitmapData(500, 500, false);
bd.perlinNoise(50, 50, 2, 0, false, true, 7, true, offs);
var bmp:Bitmap=new Bitmap(bd);
addChild(bmp);
which produces this result (I made circles half transparent to better understand the effect.
Ok, but here we are speaking about writing simple code, but even about CPU performances... such a big perlinNoise changing at every frame would kill every CPU, but do we really need THAT detail?
We absolutely don't need a per-circle gradient, we simply need that every circle has its own full value.
To reduce perlinNoise detail and CPU use is very easy, we try replacing the previous code with this:
var offs:Array=[new Point(0,0), new Point(0,0)];
var bd:BitmapData=new BitmapData(10, 10, false);
bd.perlinNoise(2, 2, 2, 0, false, true, 7, true, offs);
var bmp:Bitmap=new Bitmap(bd);
addChild(bmp);
bmp.scaleX=bmp.scaleY=50;
which produces this result
See what happened? We generate a tiny 10x10 bitmapData and the simply scale the relative bitmap to a corresponding factor. Try to comment the last line and you will see actual size of the bitmap.
This time the result is almost perfect... really insignificant CPU usage and every circle has its own full value.
So here comes the trick, now we simply use the circles as mask for the bitmap:
bmp.mask=sprite;
which produces this result
and set the bitmap blendMode to BlendMode.SCREEN (or BlendMode.HARDLIGHT)
bmp.blendMode=BlendMode.SCREEN;
which produces this result
Ok, now to animate we simply change the offset of the perlinNoise function. In the previous code I use two octaves, so that we can animate the second one. If we animate the first octave we'll have an effect that look like a scrolling and not like a variation.
So we add this:
addEventListener(Event.ENTER_FRAME, onframe);
function onframe(event:Event):void {
offs[1].x+=.02;
offs[1].y+=.05;
bd.perlinNoise(2, 2, 2, 0, false, true, 7, true, offs);
}
which produces this result
Now it's just need to randomize size and positions, and here is the full code:
var offs:Array=[new Point(0,0), new Point(0,0)];
var bd:BitmapData=new BitmapData(10, 10, false);
bd.perlinNoise(2, 2, 2, 0, false, true, 7, true, offs);
var bmp:Bitmap=new Bitmap(bd);
addChild(bmp);
bmp.scaleX=bmp.scaleY=50;
var sprite:Sprite=new Sprite();
addChild(sprite);
for(var i:uint=0; i<1000; i++) {
sprite.graphics.beginFill(0xffffff);
sprite.graphics.drawCircle(Math.random()*500, Math.random()*500, Math.random()*1.5);
}
bmp.mask=sprite;
bmp.blendMode=BlendMode.SCREEN;
addEventListener(Event.ENTER_FRAME, onframe);
function onframe(event:Event):void {
offs[1].x+=.02;
offs[1].y+=.05;
bd.perlinNoise(2, 2, 2, 0, false, true, 7, true, offs);
}
You have to play with the size of the perlinNoise and the offset speed to change the intensity of the effect. You could even notice that a 10x10 bitmapData could reveal too low detail for what you need so you can try to change its size to 40x40 (remember to change bitmap scale accordingly).
Starfield class
I prepared a class with everything included and a parameter to change the bitmapdata detail and relative bitmap size automatically.
So simply download Starfield class.
The constructor is this:
public function Starfield(pw:Number, ph:Number, pcount:uint, pminSize:Number=1, pmaxSize:Number=2, pperlinNoiseSize:Number=5, pspeedX:Number=.1, pspeedY:Number=.05, pbdSize:uint=30) {
and here a sample usage:
import com.oaxoa.fx.Starfield;
var ss:Starfield=new Starfield(500, 500, 1000, .1, 1.5, 2);
addChild(ss);
ss.startTwinkle();
//ss.debug=true;
which produces this final result (2-3% CPU usage with 1000 stars).
The last commented line set the class in debug mode, which is quite usefull to fine tune the effect.
Steps summary
Final comments
So this is the end of this kilometric post. As usally took me 15 minutes to write the code and 3 hours to write the post, prepare examples and explain the main idea.
What I like in this post is not the argauble result (LOL) but the main concepts:
- Logic comes first, code is just an addition to get thing done
- You can always achive the same results in many ways (for this simple stuff we could walk 10+ different paths
- With bitmaps tricks and blendModes life can be really easier and your CPU cooler
- Blogging is a heavy duty
I clearly exaggerated the CPU usage difference... don't think that with the first method described you could really reach 50% of load, but it's surely heavier to manage and more code to write.
As usual if you found this useful or interesting, just drop me two lines in the comment.
p.s.: what do you think of this new theme I found? I will customize it a little when have some time.
