Lightning effect in Flash AS3

2 Flares Twitter 0 Facebook 0 Google+ 2 Pin It Share 0 Email -- Filament.io 2 Flares ×

I was looking for class that would create a really cool lightning effect for me just like that, without having to deal with mathematics and co. Useless to say that when looking for such a thing, this will most likely end as a waste of time.
Well, I was wrong. I found this fabulous piece of art over there: http://blog.oaxoa.com/2009/07/27/actionscript-3-as3-lightning-thunderbolt-electric-discharge-class/

I take absolutely no credits for what I have done as it’s a pure copy of what this guy did. I just needed to re-do it in order to understand and see how it works and how this perlin noise effect works in flash. You know, working with flash is cool but when you start hearing about perlin noise effect and other sorts of effects, you just automatically calm down and say “yeah, this can be done, but… is it fast on rendering ?”
Well, I have to admit that I was pretty surprised. This is doing ok.

This is how it looks.

Get Adobe Flash player

This is not a real copy of what can be found on Oaxoa blog, I actually tried to optimize it a bit and take only the things I needed. I’m using vectors to speed up this thing a bit and instead of using a gradient bitmap to smooth the effect on the border, I use a math function. I hope this helps by not having to draw a vector on a BitmapData on each frame.

A lot of things can be customized:

  • number of children and their life time
  • thickness of lightning
  • starting and ending positions
  • steps of rendering: the more steps, the less straight this would look
  • wavelengths and amplitudes of effect
  • etc…

In a few words

How this works ?
It uses the perlinNoise() function that you can find in a BitmapData object. This function produces a Perlin Noise image that you can control in many ways. The black and white noise (can be specified) that it creates allows us to have a “map” of negative and positive positions whether a pixel is black or white (grey being the “middle” or 0). We then use this map to get this wave effect.
Two maps are actually being used. One for the main, “big”, wave and an other one for the small disturbance effect that can be seen on that main wave.

You can grab the sources below but I heavily recommend you to visit Oaxoa blog as there are many interesting things to learn.

Rabbit Lightning

And for more convenience, here is the as3 source code of the Lightning class (it still needs the EventsManager class and a couple of other classes that can be found in the archive to work)

package rabbit.effects.lightning
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.geom.Point;
import flash.utils.Timer;
import rabbit.managers.events.EventsManager;
import rabbit.utils.UniqueIdUtil;

/**
* ...
* @author Thomas John (@) thomas.john@open-design.be
* @copyright Copyright Thomas John, all rights reserved 2009.
*/
public class Lightning extends Sprite
{
private var eManager:EventsManager = EventsManager.getInstance();
private var eventGroup:String = "";

public var bTest1:Bitmap;
public var bTest2:Bitmap;

public var bd1:BitmapData;
public var bd2:BitmapData;

public var seed1:int = 0;
public var seed2:int = 0;

public var aOffsetsBd1:Array;
public var aOffsetsBd2:Array;

public static var vStepsAll:Vector. = new Vector.();
public var vSteps:Vector.;
public var vChildren:Vector. = new Vector.();

public var _startX:Number = 250.0;
public var _startY:Number = 250.0;
public var _endX:Number = 10.0;
public var _endY:Number = 100.0;
public var _speed1:Number = 0.02;
public var _speed2:Number = 0.02;
private var _steps:int = 0;
public var _wavelength1:Number = 0.05;
public var _wavelength2:Number = 0.4;
public var _amplitude1:Number = 0.08;
public var _amplitude2:Number = 0.6;

public var _thickness:Number = 2.0;
public var _color:Number = 0xFFFFFF;
public var _alpha:Number = 1.0;

public var angle:Number = 0.0;
public var len:Number = 0.0;
public var dx:Number = 0.0;
public var dy:Number = 0.0;

public var bIsChild:Boolean = false;
public var parentLightning:Lightning;
public var _stepStart:int;
public var _stepEnd:int;
public var _graphics:Graphics;

public var timer:Timer = new Timer(3000);

public var bAllowUpdate:Boolean = true;

public function Lightning()
{
eventGroup = UniqueIdUtil.getUniqueId(8);
//_speed1 *= 0.25;
//_speed2 *= 0.25;
eManager.add(timer, TimerEvent.TIMER, timer_timerHandler, eventGroup);
//eManager.add(eManager, Event.REMOVED, steps_removedHandler, eventGroup);
}

//private function steps_removedHandler(e:Event):void
//{
//timer_timerHandler(null);
//}

private function timer_timerHandler(e:Event):void
{
getNewStepsFromParent();

var i:int = 0;
var n:int = vChildren.length;

for (i = 0; i < n; i++)
{
vChildren[i].getNewStepsFromParent();
}

timer.stop();
timer.reset();
timer.delay = 1000 + Math.random() * 4000;
timer.start();
}

public function getNewStepsFromParent():void
{
_stepStart = Math.floor(Math.random() * (parentLightning.steps - 2));
_stepEnd = _stepStart + Math.floor(Math.random() * (parentLightning.steps - _stepStart - 2)) + 2;

//trace("getNewStepsFromParent", _stepStart, _stepEnd);

if ( _stepStart > _stepEnd || _stepStart < 0 || _stepEnd >= parentLightning.steps )
{
bAllowUpdate = false;
}
else
{
bAllowUpdate = true;
steps = (_stepEnd - _stepStart) * 1;
}
}

public function init():void
{
if( _steps <= 0 ) steps = 50;

seed1 = Math.round(Math.random() * 100);
seed2 = Math.round(Math.random() * 100);

aOffsetsBd1 = [new Point(0, 0), new Point(0, 0)];
aOffsetsBd2 = [new Point(0, 0), new Point(0, 0)];

if ( !_graphics ) _graphics = graphics;

//if ( !bIsChild )
//{
//bTest1 = new Bitmap(bd1);
//bTest1.smoothing = true;
//bTest1.y = _endY + 100;
//bTest1.width = 200;
//bTest1.height = 200;
//addChild(bTest1);
//
//bTest2 = new Bitmap(bd2);
//bTest2.smoothing = true;
//bTest2.y = _endY + 310;
//bTest2.width = 200;
//bTest2.height = 200;
//addChild(bTest2);
//}

}

public function kill():void
{
eManager.removeAllFromGroup(eventGroup);
//removeStepsFromVector();
}

public function update():void
{
if ( !bAllowUpdate ) return;

//_speed1 += 0.0001;
//_speed2 += 0.0001;
//trace(_speed1, _speed2);

if ( bIsChild )
{
_startX = parentLightning.vSteps[_stepStart].x;
_startY = parentLightning.vSteps[_stepStart].y;
_endX = parentLightning.vSteps[_stepEnd].x;
_endY = parentLightning.vSteps[_stepEnd].y;
angle = parentLightning.angle;
}
else
{
angle = Math.atan2(_endY - _startY, _endX - _startX);
}

//trace(angle);

dx = _endX - _startX;
dy = _endY - _startY;
len = Math.sqrt(dx * dx + dy * dy);

aOffsetsBd1[0].x -= _steps * _speed1;
aOffsetsBd1[0].y += _steps * _speed1;

aOffsetsBd2[0].x -= _steps * _speed2;
aOffsetsBd2[0].y += _steps * _speed2;

bd1.perlinNoise(_steps * _wavelength1, 0, 2, seed1, false, true, 0, true, aOffsetsBd1);
bd2.perlinNoise(_steps * _wavelength2, 0, 2, seed2, false, true, 0, true, aOffsetsBd2);

render();

var i:int = 0;
var n:int = vChildren.length;

for (i = 0; i < n; i++)
{
vChildren[i].update();
}
}

public function render():void
{
if ( !bIsChild )
{
_graphics.clear();
_graphics.lineStyle(_thickness, _color, _alpha);
}
else
{
//_graphics.lineStyle(_thickness, _color, Math.random() * _alpha);
_graphics.lineStyle(_thickness, _color, _alpha);
}
//_alpha = 0.25 + Math.random() * 0.75;
//_thickness = 0.5 + Math.random() * 2.5;

//_graphics.lineStyle(_thickness, _color, _alpha);

_graphics.moveTo(_startX, _startY);

var i:int = 0;
var c:Number = 0.0;
var cx:Number;
var cy:Number;
var c2:Number = 0.0;
var cx2:Number;
var cy2:Number;
var m:Number = 1.0;
var p:Point;

for (i = 0; i < _steps; i++)
{
c = (bd1.getPixel(i, 0) - 0x808080) / 0xFFFFFF * len * _amplitude1;
cx = Math.sin(angle) * c;
cy = Math.cos(angle) * c;

c2 = (bd2.getPixel(i, 0) - 0x808080) / 0xFFFFFF * len * _amplitude2;
cx2 = Math.sin(angle) * c2;
cy2 = Math.cos(angle) * c2;

m = Math.sin((Math.PI * (i / (_steps-1))));

cx *= m;
cy *= m;

cx2 *= m;
cy2 *= m;

cx = _startX + dx / (_steps - 1) * i + cx + cx2;
cy = _startY + dy / (_steps - 1) * i - cy - cy2;

p = vSteps[i];
p.x = cx;
p.y = cy;

//_graphics.lineStyle(_thickness, _color, m);

_graphics.lineTo(cx, cy);
}

if ( !bIsChild )
{
_graphics.endFill();
}

}

public function child_create():Lightning
{
var l:Lightning = new Lightning();
l.setAsChild(this);
vChildren.push(l);

return l;
}

public function setAsChild(parent:Lightning):void
{
bIsChild = true;
parentLightning = parent;
_graphics = parentLightning._graphics;
if( parentLightning.parentLightning ) _thickness = parentLightning._thickness * 0.5;
//_thickness = Math.random() * parentLightning._thickness;
_color = parentLightning._color;
//if( _thickness < 1 ) _alpha = parentLightning._alpha * 0.5;
if( parentLightning.parentLightning ) _alpha = parentLightning._alpha * 0.5;
//_alpha = parentLightning._alpha * Math.random();
//_amplitude1 = parentLightning._amplitude1 * 0.5;
//_amplitude2 = parentLightning._amplitude2 * (Math.random() * 2.0);
//_speed1 = parentLightning._speed1 * -1.0;
//_speed2 = parentLightning._speed2 * -1.0;
//_wavelength1 = parentLightning._wavelength1 * 0.5;
//_wavelength2 = parentLightning._wavelength2 * 0.5;
getNewStepsFromParent();
init();

timer.start();
}

//public function removeStepsFromVector():void
//{
//if ( !vSteps ) return;
//
//var i:int = 0;
//var n:int = vSteps.length;
//var index:int;
//
//for (i = 0; i < n; i++)
//{
//index = vStepsAll.indexOf(vSteps[i]);
//if ( index ) vStepsAll.splice(index, 1);
//}
//
//eManager.dispatchEvent(new Event(Event.REMOVED));
//}

public function get steps():int { return _steps; }

public function set steps(value:int):void
{
if ( _steps == value ) return;

//trace(_steps, value);
//if ( vSteps ) vSteps.splice(0, vSteps.length);
//removeStepsFromVector();
vSteps = new Vector.(value, true);

var i:int = 0;
var n:int = value;
var p:Point;

for (i = 0; i < n; i++)
{
p = new Point();
vSteps[i] = p;
//vStepsAll.push(p);
}

bd1 = new BitmapData(value, 1, false);
bd2 = new BitmapData(value, 1, false);

_steps = value;
}
}

}
2 Flares Twitter 0 Facebook 0 Google+ 2 Pin It Share 0 Email -- Filament.io 2 Flares ×

7 Comments

  1. Actionscripter

    Oh, Really nice code man! I can you make a tutorial how to make that glow effect?

  2. caballero

    I want to create this effect between two designated points in my flash movie. I have a firm grasp of flash and AS3, but I\’ve been looking to this code and this is a bit too much advanced for me. Is there a function in this class that could be called to do what I need, or is it much more complicated than that?

  3. simply update those variables:
    public var _startX:Number = 250.0;
    public var _startY:Number = 250.0;
    public var _endX:Number = 10.0;
    public var _endY:Number = 100.0;

    and call the update() function when you want to render it.

  4. lynx

    I cannot open the fla file in the arcive, unexpected file format, can you help? I really want to see how this works but I am not good with the action script.

  5. I’m using Flash CS4

Leave a Comment