ActionScript 3.0 Particle Systems #3: Rain Effect

Even more ActionScript, this time we’re taking particle depth into account and adding a blur to the stage (apparently it’s quicker to blur everything than the individual objects).

Source code and flash files after the jump if you’re into that kind of thing…

I know. The code is hideously over-commented. Don’t worry about it.

Flash File (ActionScript 3.0) Code:

import flash.events.KeyboardEvent;
import Raindrop;
 
// Function to return us a random number which is within a specified range
function randRange(low:Number, high:Number):Number {
	var randNum:Number = (Math.random() * (high - low)) + low;
	return randNum;
} 
 
// Define our vector (just a dynamic array with a specied type in AS3!)
var raindropVect:Vector.<Raindrop> = new Vector.<Raindrop>;
 
// Define our number of Raindrops to display on the screen and a start/stop animation flag
var numberOfRaindrops:uint = 300;
var maxDropDepth:uint = 1000;
var toggleAnimation = false;
 
// Create our controls message, center it and add it to the screen
var msg:ControlsMessage = new ControlsMessage();
msg.x = stage.stageWidth / 2;
msg.y = 350;
addChild(msg);
 
// Create a flag to only show our controls message once
var showMsg:Boolean = true;
 
function addRaindrops():void
{
	// Create variables to store our x & y speeds, which get passed to the Raindrop constructor
	var dropXSpeed:Number;
	var dropYSpeed:Number;
	var dropZLocation:Number;
 
	// Create however many Raindrops we've entered into our numberOfRaindrops variable
	for (var loop:uint = 0; loop < numberOfRaindrops; loop++) {
 
		// Generate random x speed for our Raindrop instance, but keep the vertical speed uniform
		// Perspective on the depth of the drop will change the drop's visual speed for us :)
		dropXSpeed = randRange(-1, 1);
	    	dropYSpeed = 35;
 
		// Create a new instance of Raindrop (passing x & y speed parameters)
		// and add it to our Vector of Raindrops
		raindropVect.push(new Raindrop(dropXSpeed, dropYSpeed));
 
		// Randomise the x (horizontal) and y (vertical) locations on stage
		// I've actually set this up to pick values within a larger area than
		// the stage itself because with the perfective correction performed
		// and using "normal" 0 to stageX/Y values we end up with only a
		// central area of the stage displaying raindrops! 
		raindropVect[loop].x = (Math.random() * stage.stageWidth * 2) - (stage.stageWidth / 2);
		raindropVect[loop].y = (Math.random() * stage.stageHeight * 2) - (stage.stageHeight / 2);
 
		// Generate a random z location for our Raindrop
		// This z location will provide our scaling, and be related to the raindrop opacity
		// The idea is: flash takes perspective into account, so drops further away will move
		// slower (visually) and reset at a point which isn't off the visible stage area, but
		// the y value is still hitting where the end of the *projected* stage (at that depth)
		// ends. This should give a truer representation of what we see on a rainy day, in theory...
		// For this to work, the raindrops must be quite close to uniform in size and quite
		// close to uniform in speed (otherwise a faster + further drop may look identical
		// to a slower yet closer drop), and alpha (opacity) must be related to distance
		// from the "camera" (i.e. where z = 0)
		dropZLocation = randRange(1, maxDropDepth);	
		raindropVect[loop].z = dropZLocation;
 
		// Set our raindrop alpha as a percentage of our z location
		// We have to use "1 - blah" otherwise the drops gets MORE opaque as
		// they move further away instead of more transparent!! The final 0.1
		// is just a fudge-factor to make sure the raindrop is actually visible,
		// otherwise there's not much point calc'ing it...
		raindropVect[loop].alpha = 1 - ((1 / maxDropDepth) * dropZLocation) + 0.1;
 
		// Add the Raindrop to the stage
		addChild(raindropVect[loop]);
 
	} // End of for loop
 
} // End of addRaindrops function
 
function removeRaindrops():void
{
	// Remove however many Raindrops we're working with from the stage
	for (var loop:uint = 0; loop < numberOfRaindrops; loop++) {
 
		// Call our custom destructor to unbind any Raindrop eventListeners
		raindropVect[loop].RaindropDestructor();
 
		// Remove the Raindrop from the stage
		removeChild(raindropVect[loop]);		
	}
 
	// Remove all raindrops from the vector
	// We can't just use "raindropVect = null;" or we'll lose our global instance of the vector!
	while (raindropVect.length > 0) {
		raindropVect.pop();
	}
 
} // End of removeRaindrops function
 
function raindropController(e:KeyboardEvent):void
{
	// If the animation is stopped, add the raindrops and flip the flag
	if (toggleAnimation == false) 
	{
		addRaindrops();
		toggleAnimation = true;
 
		// Only remove message after first display, as it'll only be on stage once
		if (showMsg == true) 
		{
			showMsg = false;
			removeChild(msg);
		}
	}
	else {
		// Otherwise animation must be running and we want it to stop, so remove the
		// raindrops and flip the flag
		removeRaindrops();
		toggleAnimation = false;
	}
 
} // End of raindropController function
 
// With all our functions in place, bind the RaindropController function to a keypress
stage.addEventListener(KeyboardEvent.KEY_DOWN, raindropController);

ActionScript 3.0 Raindrop Class Code:

package
{
	// Import the MovieClip and Event classes
	import flash.display.MovieClip;
	import flash.events.Event;
 
	public class Raindrop extends MovieClip
	{
		// Define our Raindrop's properties
		var ySpeed:Number;
		var xSpeed:Number;
		static var minimumYSpeed:Number = 35;
 
		// Constructor
		function Raindrop(theDropXSpeed:Number, theDropYSpeed:Number):void
		{
			// Add an eventlistener for the Drop instance that calls the updateDrop
			// function every time we draw a new frame
			this.addEventListener(Event.ENTER_FRAME, updateRaindrop);
 
			// Set our Drop's x and y speeds as per the constructor's passed parameters
			this.xSpeed = theDropXSpeed;
			this.ySpeed = theDropYSpeed;
 
		} // End of constructor
 
		// Destructor to unbind the Raindrops's ENTER_FRAME event listener when we destroy it
		public function RaindropDestructor():void
		{
			this.removeEventListener(Event.ENTER_FRAME, updateRaindrop);
		} 
 
		function updateRaindrop(e:Event):void
		{
			// Move our raindrop by adding it's speed to it's current location
			this.x += this.xSpeed;
			this.y += this.ySpeed;
 
			// If our raindrop has gone off the bottom of the stage, reset the height back to
			// above the stage and re-randomise the x speed
			if (this.y > (stage.stageHeight + this.height))
			{
				// Reset y position to above the stage. We have to add some random jitter to
				// our location above the stage (up to our minimum y speed) because we're moving the
				// rain in big steps, and if we reset all the raindrops to -just- above the
				// stage we end up with horizontal bands of drops separated by the  minimum
				// vertical drop speed, and it looks naff(er) ;)
				// Try changing the jitter from minimumYSpeed to 2 or something to see what I mean...
				this.y = 0 - (this.height / 2) - (Math.random() * minimumYSpeed);
 
				// Reset x position to somewhere within our expanded range
				this.x = (Math.random() * stage.stageWidth * 2) - (stage.stageWidth / 2);
 
				// Re-randomise the horizontal speed but leave our constant vertical speed alone
				this.xSpeed = (Math.random() * 2) - 1;
 
			} // End of if raindrop is off the bottom of the stage
 
		} // End of updateDrop function
 
	} // End of class
 
} // End of package

No, really. I know it’s over-commented by a factor of 20 or something – it’s okay. I’m learning. Strip ’em out if they offend thine eyes ;)

Adobe Flash CS4 files can be found: here

9 thoughts on “ActionScript 3.0 Particle Systems #3: Rain Effect”

    1. Well, depending on what you mean:

      – If you mean stop at a certain time (i.e. run for X amount of seconds and then stop)? – then you could create a timer which starts when the animation does and toggles the stop flag (via calling the toggleAnimation function) when the timer flres X seconds later

      – If you mean stop after a certain number of frames, then you could create a framecount variable at startup and increment it each time a frame is drawn, then call the toggleAnimation function when the framecount reaches a given value (i.e. 2000 frames drawn or such).

      Have a go at it yourself (it’s good practice!), and if you’re having problems drop another comment in here and I’ll do the modification and post it here, k?

      Cheers! =D

  1. Hi,

    In my case, I try to implement as raining from the cloud and it would stop after a certain number of frames(TIMELINE).
    I tried so many times to implement it but I just couldn’t get it right as I never learn AS3.0 before(I just learn myself few weeks ago and I am just the beginner).
    Can you please kindly help or teach me how to implement it ?

    Thank you !

    1. Ahh… Okay, I see what you mean now.

      The problem is that the code only runs on a single frame in the timeline (the first frame), and when you move off the frame it stops running the code that was on that frame, and starts running any code on the (now) current frame.

      Have you tried adding the following code to the frame you want to stop the rain on?:

      removeRaindrops();
      toggleAnimation = false;

      Or, just copy and paste the contents of the removeRaindrops() function into code for the frame where you want to stop the raindrops from falling.

      Or, and this one’s a bit cheeky, just count the number of raindrops with a variable, and when X amount of raindrops have fallen (i.e. 2000 or however long you want it to last), make it so that each new raindrop has an opacity/alpha of zero (i.e. it’s completely transparent) after the set amount of raindrops have been spawned! In this way, the rain will still be there, but will be invisible. It’s not a great solution, but it’d do the job!

      I haven’t done that much with AS3 code running on timelines, so give some of the above a try, and if it’s still not working for you let me know and I’ll have a look into it.

      Don’t worry – we’ll get there :)

  2. Hmm.. I tried it but it cannot be done based on your example.

    I was trying to modify your example so that it will start raining and stop raining automatically from cloud.
    So, I try to implement but I just couldn’t get it right.

    Actually it doesn’t matter on which timeframe(timeline) or time to be used, as long as it can be used to trigger the raindrops start and stop at certain of time/timeframe.
    Of course I would be prefer timeframe(timeline) since I was trying to implement an animation based on timeframe(timeline).

    Thank you.

    1. Hey King,

      I put together a quick example of calling functions from different locations on the timeline which you can see here: Rain-on-Timeline.zip

      The important part to remember is that you can only put AS3 code on keyframes – not just any normal frame. But once you’re on a keyframe, you can access any variables or functions you’ve previously defined (i.e. defined back on frame 1 or whatever).

      The rain stops really abruptly in the example code provided – I just call a toggleTheAnimation() function on each frame where I want to turn the rain on or off, and the function adds raindrops to the raindropVect vector or removes them as appropriate.

      Also, remember that you can add an event listener and attach it to Event.ENTER_FRAME (example) if you wanted to run a function each frame (such as to fade things in, or move them around etc.). In the Raindrop.as class, this is exactly what I’m doing to make sure the updateRaindrop function is called to move the position of each raindrop each time a frame is drawn, but you can add other functions to do whatever you want each frame :)

      I really hope this has been of use to you as it’s taken me an hour or two to sort out – if not, then there’s not much else I can do as I’m really short on time with my teaching, studying and family life at the moment, and they have to take priority.

      Best of luck & have fun learning AS3 – it’s a nice little language and I hope you get to make some cool stuff =D

      Cheers,
      r3dux

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.