How to create beatiful snow in flash (using ActionScript 3)

Posted at January 25, 2011

In this article I’m going to make realistic snow in ActionScript 3, I programmed this in Adobe Flash Builder. I uploaded a youtube video with the result, but the realtime flash example looks a lot better.

First I post the code, which is licensed under copyleft (that means you can use it as long as you credit me and also license it under copyleft). and after that I’ll explain the code so you’ll understand it.

I’ll create 1000 snowflakes all falling down. You can manipulate the wind using your keyboard arrows and your mouse.

This was the first real program I ever created so the code is probably not very optimized (for example, when writing this I didn’t knew about the default drag capabilities of AS3) and/or ugly. To understand this article, basic programming knowledge is very useful.

There are two classes. The first class is Ball:

/** Ball.as -- Nov 24, 2010
 *
 *
 * @author Mike van Rossum 
 *
 *
 * Copyleft 2011, all wrongs reversed.
 */
package
{
    // Import everything we need
    import flash.display.Sprite;
    import flash.text.TextField;

public class Ball extends Sprite
{
    // Import everything we need
    public var baseX:int;
    public var angle:Number;
    public var sway:Number;
    public var largeness:int;

    public function Ball()
    {
        largeness = Math.random()*8;
        graphics.beginFill(0xffffff);
        graphics.drawCircle(0,0,largeness);
        graphics.endFill();
    }
}

And all the magic happens in the second class:

/** Snow.as -- Nov 24, 2010
 *
 *
 * @author Mike van Rossum 
 *
 * Copyleft 2011, all wrongs reversed.
 */
package
{
  // Import everything we need
  import flash.display.Bitmap;
  import flash.display.Sprite;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.events.Event;
  import flash.events.KeyboardEvent;
  import flash.events.MouseEvent;
  import flash.geom.Point;

public class Snow extends Sprite
  {
    //declare variables
    private var sleepStart:Point;
    private var sleepStop:Point;
    private var wind:int;
    private var line:Sprite;
    private var angle:Number;
    private var flakes:Array;
    private var screen:Sprite;

public function Snow()
{

  //I always add these lines in all my AS3 programs 
  //those make sure every 1 pixel we calculate really is 
  //1 pixel on the screen. The last one makes sure that 
  //the 0,0 point is in the left-upper corner of the screen.

  stage.scaleMode = StageScaleMode.NO_SCALE;
  stage.align = StageAlign.TOP_LEFT;

  //here I make sure all these functions are run when they are supposed to.

  addEventListener(Event.ENTER_FRAME, moveAll);
  stage.addEventListener(KeyboardEvent.KEY_DOWN, changeTheWind);
  stage.addEventListener(MouseEvent.MOUSE_DOWN, startLine);
  stage.addEventListener(MouseEvent.MOUSE_UP, endLine);

  //Here I make a rectangle with the same size as the 
  //screen and fill it black. This is our background.

  screen = new Sprite();
  screen.graphics.beginFill(0x000000,1);
  screen.graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight);
  addChild(screen);

  //Here I call the function createSnow to be executed.
  createSnow();

  //Those are for my own drag and drop function
  sleepStart = new Point();
  sleepStop = new Point();

  //I add the line that will be drawed already
  //so I only have to manipulate it's form later
  line = new Sprite();
  addChild(line);
}

//This function is executed every frame, 
//and makes sure everything animates. 
//Note that I call the function moveSnow here, this means that 
//every frame the function moveSnow is executed.

public function moveAll(event:Event):void
{
  line.alpha -= .1;
  moveFlakes();
}  

//Here is the function where I create all the snow flakes. 
//I specify how I make one snow flake, and how I position it on 
//the screen. After that I put that code into a for loop so it will 
//be executed multiple times (in my case 1000). I use Math.random 
//function to calculate random numbers so that every time my code 
//is executed, the numbers are not the same.

public function createSnow():void
{
  var flake:Ball;

  //I put all those 1000 flakes in an array so I 
  //can easy manipulate them later on.

  flakes = new Array();

  for (var i:int = 0; i < 1000; i++)
  {
    flake = new Ball();
    flake.baseX = Math.random() * stage.stageWidth;
    flake.x = flake.baseX;
    flake.y = Math.random() * stage.stageHeight - stage.stageHeight;
    flake.angle = Math.random() * 10;
    flake.sway = flake.largeness * 7;
    addChild(flake);
    flakes.push(flake);
  }
}

//As you see probably have seen you can manipulate the 
//wind by using your mouse and the keyboard. The latter 
//is described in this function. The function is called 
//every time someone pushes any button on his keyboard. 

public function changeTheWind(Event:KeyboardEvent):void
{

  //We make use of the if statement so that something only happens 
  //when the button pressed is the right or left arrow.

  if (Event.keyCode == 39) // = arrow right
  {
    wind ++;
  }
  else if (Event.keyCode == 37) // = arrow left
  {
    wind --;
  }
}  

//These two functions check how far you dragged your mouse. 
//They save the location where you press you mouse and 
//compare that to where you release it. Based on the 
//difference in X position of the two locations the wind speed is set.

//These locations are stored in the vars we set in the function Snow. 
//So that we can re-use those vars when someone makes a new drag.

public function startLine(Event:MouseEvent):void
{
  sleepStart.x = Event.stageX;
  sleepStart.y = Event.stageY;
}  

public function endLine(Event:MouseEvent):void
{
  sleepStop.x = Event.stageX;
  sleepStop.y = Event.stageY;
  drawLine();
}

//After those mouse locations are saved a line is drawed. The 
//start point is the start point of the mous and the end 
//point is where you stop dragging it. In the function moveAll 
//there is a line which makes the line we just created every 
//frame a little les visable.

//Every second time someone makes a drag, the variable line is re-used.

public function drawLine():void
{
  var lengte:int;
  line.graphics.clear();
  line.graphics.lineStyle(5,0x7FD1E7);
  line.graphics.moveTo(sleepStart.x,sleepStart.y);
  line.graphics.lineTo(sleepStop.x,sleepStop.y);
  line.alpha = 1;
  lengte = sleepStop.x - sleepStart.x;
  wind = lengte/12;
}    

//This is where all the already created flakes are moved.

//Note that all the movement is relative to the size of the flake (the largeness 
//variable from Ball). This creates the illusion of depth. The smaller 
//flakes look further behind and therefor need to move slower.

public function moveFlakes():void
{
  //This line makes sure that all the 
  //code nested in the brackets ( { } ) is run everytime for every flake. 
  //Every flake is called one time during this. As you can imagine this puts 
  //a lot of stress to your PC because every bit of code inside these brackets 
  //has to be run 24 (frames per second) times 1000 (the number of flakes) = 24 000 times per second.

  for each (var vlok:Ball in flakes)
  {
    //For the X position of the flake I use a little math trick 
    //including the sinus (I go a lot further with that in this 
    //dutch tutorial about atoms). You don't have to understand this 
    //line, just know that this is the sway move of all the flakes. 
    //The size of the sway is decided by the largeness of the Flake 
    //(from the class Ball).

    vlok.x = vlok.baseX + Math.sin(vlok.angle) * vlok.sway;

    //On this line we pull down all the flakes, again relative 
    //to their size. When we add something to the Y position 
    //of a Sprite and the Y- zero position is on top. We're moving 
    //it downwards.

    vlok.y += vlok.largeness /2;
    vlok.angle += .1;
    vlok.baseX += wind/7*vlok.largeness;

    //Now we got all the snow moving but when 
    //it leaves the screen we're lost in an empty 
    //black screen and still your computer has to 
    //calculate the position of a Sprite 24 000 times 
    //every second. Therefor you can find 3 if statements here. 
    //They wrap your screen so to say. It just means that if a 
    //flake gets outside the screen, put it back on the other side. 
    //The first if statement is for the bottom border. The other two 
    //for the sides.

    if (vlok.y > stage.stageHeight)
    {
      vlok.y = 0 - vlok.sway;
      vlok.baseX = Math.random() * stage.stageWidth;
      vlok.x = vlok.baseX;
    }

    if (vlok.x - vlok.sway > stage.stageWidth)
    {
      vlok.baseX = 1 - vlok.sway;
      vlok.y = Math.random() * stage.stageHeight;
    }

    if (vlok.x < 0 - vlok.sway)
    {
      vlok.baseX = stage.stageWidth - 1 + vlok.sway;
      vlok.y = Math.random() * stage.stageHeight;
    }    

  }
}

In the detailed comments I explained how everything works.

Posted at January 25, 2011, under AS3.