Drawing Beautiful Components With Code – Made Easy
Most programmers have never touched design. If you have always wanted to beautiful graphics in your Flash projects but never knew how to use Adobe Photoshop or Illustrator, this tutorial is for you. Nice graphics can be created entirely with code. Drawing graphics for components with code even has a few advantages.
Prerequisites
This tutorial requires that you know how to perform basic tweens using Greensock’s TweenLite or TweenMax. You can download the latest version of TweenLite from the Greensock website. I will be using Flash CS5 Professional to complete this project. You may use any Flash IDE you’d like (such as FlashDevelop or FDT). It would also help to have a fair understanding of inheritance.
A Brief Introduction
Before we get started, let’s take a look at what we’ll be learning and why it’s important. When designing Flash applications there are many ways to generate graphics. You can use an external tool such as Adobe Illustrator or draw complex graphics with Adobe Photoshop. You can even use 3D utilities such as Cinema 4D to create eye-popping 3D bitmaps to import into Flash. These techniques are great for creating a well designed static application – static meaning not very dynamic, or in most cases, not dynamic at all.
If you use one of the above methods to generate graphics for Flash, you may find it a little difficult to resize the graphics without some loss of quality or, even worse, distortion. On the sunny side, you can import a vector file that was generated with Adobe Illustrator into your Flash application and scale the image infinitely without distorting the image – but when you want to change the overall ratio of the image, you will notice that your image becomes a little deformed.
What we are going to do today is learn how efficient it can be to draw our application’s graphics with code. We are going to create a beautiful animated button that is completely generated with ActionScript.
Step 1: Pros and Cons
Before we dig in, I’d like to discuss the importance of planning for your application and answering simple questions to decide whether to draw the graphics with code or by hand (e.g. with Adobe Photoshop). To help us answer the questions below, let’s first examine the pros and cons to drawing with code.
The Pros
- Portability: Graphics that are written in code are easy to import into other projects since they exist as AS files. No need to copy and paste an image into a new Flash project or import the image into the library. Just import the appropriate classes.
- Simplicity: Graphics drawn with code are done using vectors so they have a delicate and simplistic feel that is both nice to look at and light on the CPU.
- Consistency: When you’d like to resize your graphics, they will not distort (unless this is the effect you are going for) and they will maintain quality as the are scaled. Bitmap images, for example, become pixelated as they are made larger or zoomed into. Vector images don’t lose any quality or become pixelated. You can zoom into a vector image infinately and it will never lose quality. You can also be sure that your graphics still look amazing on screens with higher resolutions. As technology advances and new screen resolutions become available, your graphics are more likely to still be able to deliver a nice quality, as opposed to bitmap images that may not look so good.
- Dynamics: Along with being able to resize and scale your graphics, you can also re-color segments of an image or component with very few lines of code. Changing properties of a graphic has never been so easy. What’s that you say? You no longer want the frame of your textfield to have rounded edges? No problem here. I’ll just change one line of code… and we’re good to go!
- Precision: Although many graphics applications have features that simplify the positioning of graphical segments, you’ll find it easier to make modifications and position the contents of a graphic with code. You can easily center one segment inside another as the parent segment changes size or write an algorithm for handling how a segment will be displayed as it is resized. Hint, hint.
The Cons
- Detail: Vector graphics drawn with code can be very detailed, but too much detail can have a dramatic effect on the performance of our applications. Although it may be possible, you won’t find me attempting to draw a detailed Spongebob Squarepants with code. Something like that should be done using Adobe Illustrator. Also vectors may not be able to be as detailed as a complex graphic drawn in Adobe Photoshop. Vectors tend to contain a smaller file size than your typical bitmap image. Vectors are connected points and bitmaps are represented pixel by pixel. But if you converted a bitmap into a vector image, the new vector’s file size would turn out to be a lot larger than the original bitmap image. This said, you won’t be able to paint an intricate portrait with code. Use Adobe Photoshop instead.
- Limitations: There may be times where you’d like to use your graphics in different media. Sorry to say it but if you won’t be able to drop your dynamic graphics into that Java game you’ve been working on or that Objective-C application you just started. There are ways to render images using Flash but you will definately lose any dynamic capabilities provided by code.
- Difficulty: Not to say that you will find it ridiculously difficult to draw graphics with code but depending on what you are drawing, you will most likely find the entire process a bit more complicated than using a software that is intended for this purpose. Don’t expect drawing with code to be an easy task. It really all depends on the expected outcome.
You should now find the following questions pretty easy to answer. There are three questions you need to ask yourself before you start your project:
-
Does your application require graphics to be static or dynamic?
- Static
- Dynamic
-
Do the graphics or components in your application need to be exclusive to Flash or be consistent across multiple platforms?
- Flash Platform only
- Multiple Platforms
-
Is the graphic design of your application more detailed or quite simple?
- Relatively Simple (Basic Geometric Shapes)
- Moderate (Creative Designs with Simple Shapes)
- Intricate (Overly Complex Masterpieces)
Now examine your test results. If your test results closely resemble the results below, than the techniques we are getting ready to learn are for you?
- #2
- #1
- #1 or #2
If your results do not look like the results above, you may want to consider other methods. But for the rest of us, let’s proceed.
Step 2: Brainstorming
So here’s the scenario. A client has just asked us to create an animated button for their Flash site and AIR app. The button must be consistent with the science fiction theme of the client’s website. The button’s height can be a fixed size but the button must be able to accomodate for different widths. The button must have a toggle state that has a blatant difference between it’s active and inactive states. Finally the button must have an API that’s easy to work with.
Now that we know that we’ve got this themed component to create, we need to think about how we are going to go about creating it. Well, we know that we’re going to be drawing a lot of graphics with code so that the component will be easily and efficiently editable. Why not start off with a base class that will add high level functionality on top Flash’s drawing API?
We don’t want to draw graphics over and over again by writing the same lines of code so we’ll have a class with particular properties and methods specifically designed for reusing the code for us. We’ll extend this class to create the main shapes that we’ll need for our button.
Step 3: The Editable Shape Class
Now it’s time to get to it! Create a new class called EditableShape and add the following classes to the classpath.
import flash.display.Graphics; import flash.display.Sprite; import flash.events.Event; import flash.geom.Matrix; import flash.display.BitmapData; import flash.utils.getDefinitionByName; import flash.utils.getQualifiedClassName;
Now create the class declaration. The class should extend the flash.display.Sprite class.
public class EditableShape extends Sprite {
Create the following variables(properties) before the class constructor.
private var _fillGradientType:String; private var _fillSpreadMethod:String; private var _fillColors:Array; private var _fillAlphas:Array; private var _fillRatios:Array; private var _fillGradientWidth:Number; private var _fillGradientHeight:Number; private var _fillGradientRotation:Number; private var _fillTx:Number; private var _fillTy:Number; private var _lineGradientType:String; private var _lineSpreadMethod:String; private var _lineThickness:Number; private var _lineColors:Array; private var _lineAlphas:Array; private var _lineRatios:Array; private var _lineGradientWidth:Number; private var _lineGradientHeight:Number; private var _lineGradientRotation:Number; private var _lineTx:Number; private var _lineTy:Number; private var _width:Number; private var _height:Number; private var _matchGradientSize:Boolean; private var _bitmapData:BitmapData; private var _useBitmapFill:Boolean; private var _pixelHinting:Boolean; protected var fillGradientBox:Matrix; protected var lineGradientBox:Matrix;
Within the class constructor add the following code and I’ll explain what we just did.
super(); _width = 100; _height = 100; _matchGradientSize = true; _fillGradientType = "linear"; _fillSpreadMethod = "pad"; _fillColors = [ 0xFFFFFF, 0x000000 ]; _fillAlphas = [ 1, 1 ]; _fillRatios = [ 1, 255 ]; _fillGradientWidth = _width; _fillGradientHeight = _height; _fillGradientRotation = 0; _fillTx = 0; _fillTy = 0; _lineGradientType = "linear"; _lineSpreadMethod = "pad"; _lineThickness = .1; _lineColors = [ 0xFFFFFF, 0x000000 ]; _lineAlphas = [ 1, 1 ]; _lineRatios = [ 1, 255 ]; _lineGradientWidth = _width; _lineGradientHeight = _height; _lineGradientRotation = 0; _lineTx = 0; _lineTy = 0; _useBitmapFill = false; _pixelHinting = true; fillGradientBox = new Matrix(); lineGradientBox = new Matrix(); init();
The primary function of the constructor is to instantiate all of the properties that we have defined. We also gave each property a default value. You will be able to see what the result of these values are later. Each property that begins with an underscore will be given read/write access to outside code. The reasons these properties are not public properites is because we want to respond to the change in value of each particular property. We shall do this within the setter methods that we’ll create later.
Each property is based on particular value that must be passed into any of the graphics object methods. One example is the _fillGradientColors property. It is an array of uints that will be passed into the graphics.beginGradientFill method. Another example is the _pixelHinting property. It will be passed into the graphics.lineStyle method’s pixelHinting parameter.
The last line of the constuctor calls the init method. The init method is very simple. It calls the update method.
Step 4: The Update Method
The update method is probably the most important method within the EditableShape class. The main objective of this method is to clear any existing graphics and redraw them based upon the latest data. We will call this method everytime a property has changed so the changes can be seen immediately by the user if necassary. Create the update method.
protected function update():void {
if ( _matchGradientSize ) {
_lineGradientWidth = _width;
_lineGradientHeight = _height;
_fillGradientWidth = _width;
_fillGradientHeight = _height;
}
lineGradientBox.createGradientBox( lineGradientWidth, lineGradientHeight, toRadians( lineGradientRotation ), lineTx, lineTy );
fillGradientBox.createGradientBox( fillGradientWidth, fillGradientHeight, toRadians( fillGradientRotation ), fillTx, fillTy );
super.graphics.clear();
super.graphics.lineStyle( lineThickness, 0, 0, pixelHinting );
super.graphics.lineGradientStyle( lineGradientType, lineColors, lineAlphas, lineRatios, lineGradientBox, lineSpreadMethod );
if ( !_bitmapData || !_useBitmapFill ) super.graphics.beginGradientFill( fillGradientType, fillColors, fillAlphas, fillRatios, fillGradientBox, fillSpreadMethod )
else super.graphics.beginBitmapFill( _bitmapData );
draw();
}
The Matrix objects we created in the constructor are used to manipulate gradients. We needed two of them. One for the fill gradient and the other for the line gradient.
We will be overriding the get graphics method so that we can forbid access to this property to outside code. We don’t want any outside code tampering with our graphics at all. Because we are going to override the get graphics method, we will need to access the method from the subclass. This is accomplished through the super object. We will also create a protected method called getGraphics later on. This method is needed to give subclasses access to the graphics object without granting access to the object to outside code.
In conclusion, the update method re-initializes the graphics and it does this based on the current values of the properties within the EditableShape instance. The last line of code calls the draw method which is an abstract method in the EditableShape class. The method should be overriden by a subclass and is intended for drawing a particular shape using the graphics object within its implementation.
Step 5: Clones and Copies
Create the draw method.
protected function draw():void {
// Abstract
}
Again the draw method is just a simple abstract method that we can ignore for now.
We will need to copy the properties of one shape onto another. There may also be times when you’d like a complete clone of a particular shape. We’ll create two methods to accomplish this for us. Doing so will save us a lot of time in the long run. Instead of always re-writing code we’ll just call the appropriate method which copies or clones a shape. Create the copy and clone methods.
public function copy( shape:EditableShape ):void {
_width = shape.width;
_height = shape.height;
_matchGradientSize = shape.matchGradientSize;
_fillGradientType = shape.fillGradientType;
_fillSpreadMethod = shape.fillSpreadMethod;
_fillColors = shape.fillColors;
_fillAlphas = shape.fillAlphas;
_fillRatios = shape.fillRatios;
_fillGradientWidth = shape.fillGradientWidth;
_fillGradientHeight = shape.fillGradientHeight;
_fillGradientRotation = shape.fillGradientRotation;
_fillTx = shape.fillTx;
_fillTy = shape.fillTy;
_lineGradientType = shape.lineGradientType;
_lineSpreadMethod = shape.lineSpreadMethod;
_lineThickness = shape.lineThickness;
_lineColors = shape.lineColors;
_lineAlphas = shape.lineAlphas;
_lineRatios = shape.lineRatios;
_lineGradientWidth = shape.lineGradientWidth;
_lineGradientHeight = shape.lineGradientHeight;
_lineGradientRotation = shape.lineGradientRotation;
_lineTx = shape.lineTx;
_lineTy = shape.lineTy;
_useBitmapFill = shape.useBitmapFill;
if ( _bitmapData ) _bitmapData = shape.bitmapData.clone();
if ( filters ) filters = shape.filters;
alpha = shape.alpha;
update();
}
public function clone():EditableShape {
var c:Class = Class( getDefinitionByName( getQualifiedClassName( this ) ) );
var shape:EditableShape = new c();
shape.width = _width;
shape.height = _height;
shape.matchGradientSize = _matchGradientSize;
shape.fillGradientType = _fillGradientType;
shape.fillSpreadMethod = _fillSpreadMethod;
shape.fillColors = _fillColors;
shape.fillAlphas = _fillAlphas;
shape.fillRatios = _fillRatios;
shape.fillGradientWidth = _fillGradientWidth;
shape.fillGradientHeight = _fillGradientHeight;
shape.fillGradientRotation = _fillGradientRotation;
shape.fillTx = _fillTx;
shape.fillTy = _fillTy;
shape.lineGradientType = _lineGradientType;
shape.lineSpreadMethod = _lineSpreadMethod;
shape.lineThickness = _lineThickness;
shape.lineColors = _lineColors;
shape.lineAlphas = _lineAlphas;
shape.lineRatios = _lineRatios;
shape.lineGradientWidth = _lineGradientWidth;
shape.lineGradientHeight = _lineGradientHeight;
shape.lineGradientRotation = _lineGradientRotation;
shape.lineTx = _lineTx;
shape.lineTy = _lineTy;
shape.useBitmapFill = _useBitmapFill;
if ( _bitmapData ) shape.bitmapData = _bitmapData.clone();
shape.filters = filters;
shape.alpha = alpha;
return shape;
}
The copy method takes the shape parameter and sets all of the properties equal to that of the current EditableShape.
We will not be using the clone method in this tutorial but I just threw it in as an extra bonus. The method returns a cloned copy of the current EditableShape class.
Step 6: Finishing Up the EditableShape
The last thing we need to do is grant read/write access to all of the shape’s main properties. Write the following getter and setter methods within the EditableShape class.
public override function set width(value:Number):void {
_width = value;
update();
}
public override function get width():Number {
return _width;
}
public override function set height(value:Number):void {
_height = value;
update();
}
public override function get height():Number {
return _height;
}
public function set fillGradientType( value:String ):void {
switch ( value.toLowerCase() ) {
case "linear" :
case "radial" :
_fillGradientType = value.toLowerCase();
update();
break;
default :
//Do nothing
}
}
public function get fillGradientType():String {
return _fillGradientType;
}
public function set fillSpreadMethod(value:String):void {
switch ( value.toLowerCase() ) {
case "pad" :
case "reflect" :
case "repeat" :
_fillSpreadMethod = value.toLowerCase();
update();
default :
//Do nothing
}
}
public function get fillSpreadMethod():String {
return _fillSpreadMethod;
}
public function set fillColors(array:Array):void {
var a:Array = [];
for each( var color:uint in array ) {
if ( color is uint ) {
a.push( color );
}
}
_fillColors = a;
update();
}
public function get fillColors():Array {
return _fillColors;
}
public function set fillAlphas( array:Array ):void {
var a:Array = [];
for each( var nAlpha:Number in array ) {
if ( nAlpha is Number) {
a.push( nAlpha );
}
}
_fillAlphas = a;
update();
}
public function get fillAlphas():Array {
return _fillAlphas;
}
public function set fillRatios(array:Array):void {
var a:Array = [];
for each(var ratio:Number in array) {
if (ratio is int) {
a.push(ratio);
}
}
_fillRatios = a;
update();
}
public function get fillRatios():Array {
return _fillRatios;
}
public function set fillGradientWidth(value:Number):void {
_fillGradientWidth = value;
update();
}
public function get fillGradientWidth():Number {
return _fillGradientWidth;
}
public function set fillGradientHeight(value:Number):void {
_fillGradientHeight = value;
update();
}
public function get fillGradientHeight():Number {
return _fillGradientHeight;
}
public function set fillGradientRotation(degrees:Number):void {
_fillGradientRotation = degrees;
update();
}
public function get fillGradientRotation():Number {
return _fillGradientRotation;
}
public function set fillTx(value:Number):void {
_fillTx = value;
update();
}
public function get fillTx():Number {
return _fillTx;
}
public function set fillTy(value:Number):void {
_fillTy = value;
update();
}
public function get fillTy():Number {
return _fillTy;
}
public function set lineGradientType(value:String):void {
switch (value.toLowerCase()) {
case "linear" :
case "radial" :
_lineGradientType = value.toLowerCase();
update();
break;
default :
//Do nothing
}
}
public function get lineGradientType():String {
return _lineGradientType;
}
public function set lineSpreadMethod(value:String):void {
switch (value.toLowerCase()) {
case "pad" :
case "reflect" :
case "repeat" :
_lineSpreadMethod = value.toLowerCase();
update();
default :
//Do nothing
}
}
public function get lineSpreadMethod():String {
return _lineSpreadMethod;
}
public function set lineThickness(value:Number):void {
_lineThickness = value;
update();
}
public function get lineThickness():Number {
return _lineThickness;
}
public function set lineColors(array:Array):void {
var a:Array = [];
for each(var color:uint in array) {
if (color is uint) {
a.push(color);
}
}
_lineColors = array;
update();
}
public function get lineColors():Array {
return _lineColors;
}
public function set lineAlphas(array:Array):void {
var a:Array = [];
for each(var nAlpha:Number in array) {
if (nAlpha is Number) {
a.push(nAlpha);
}
}
_lineAlphas = a;
update();
}
public function get lineAlphas():Array {
return _lineAlphas;
}
public function set lineRatios(array:Array):void {
var a:Array = [];
for each(var ratio:Number in array) {
if (ratio is int) {
a.push(ratio);
}
}
_lineRatios = a;
update();
}
public function get lineRatios():Array {
return _lineRatios;
}
public function set lineGradientWidth(value:Number):void {
_lineGradientWidth = value;
update();
}
public function get lineGradientWidth():Number {
return _lineGradientWidth;
}
public function set lineGradientHeight(value:Number):void {
_lineGradientHeight = value;
update();
}
public function get lineGradientHeight():Number {
return _lineGradientHeight;
}
public function set lineGradientRotation(degrees:Number):void {
_lineGradientRotation = degrees;
update();
}
public function get lineGradientRotation():Number {
return _lineGradientRotation;
}
public function set lineTx(value:Number):void {
_lineTx = value;
update();
}
public function get lineTx():Number {
return _lineTx;
}
public function set lineTy(value:Number):void {
_lineTy = value;
update();
}
public function get lineTy():Number {
return _lineTy;
}
public function set matchGradientSize(value:Boolean):void {
_matchGradientSize = value;
update();
}
public function get matchGradientSize():Boolean {
return _matchGradientSize;
}
public function set bitmapData( value:BitmapData ):void {
_bitmapData = value;
update();
}
public function get bitmapData():BitmapData {
return _bitmapData;
}
public function set useBitmapFill( value:Boolean ):void {
_useBitmapFill = value;
update();
}
public function get useBitmapFill():Boolean {
return _useBitmapFill;
}
public function set firstFillColor( value:uint ):void {
_fillColors[ 0 ] = value;
update();
}
public function get firstFillColor():uint {
if ( _fillColors.length > 0 ) {
return _fillColors[ 0 ];
}
else {
return 0;
}
}
public function set pixelHinting( value:Boolean ):void {
_pixelHinting = value;
update();
}
public function get pixelHinting():Boolean {
return _pixelHinting;
}
public override function get graphics():Graphics {
return null;
}
protected function getGraphics():Graphics {
return super.graphics;
}
All of the getter methods just return the corresponding properties value. But for the setter methods we correct or filter out any unwanted input. We also call the update method so that there is an immediate reaction to the new value or values.
Step 7: Drawing Rectangles
The majority of our button will consist of rectangle shapes. Unless we want to bore our client’s users with a bunch of lame blocky shapes, we will want to be able to round the edges of our rectangles. The EditableShape class is meant to be extended, meaning that it should have subclasses that finalize the primary function of the class. The EditableShape class takes care of all of the hard work for us. All we have to do now is draw the shape within a subclass. Create a new class called RectangleShape which extends the EditableShape class. The class declaration should look as follows.
public class RectangleShape extends EditableShape {
Add the following private properties to the class directly after the class declaration.
private var _ellipseWidth:Number; private var _ellipseHeight:Number;
I mentioned before that we will be rounding the edges to our rectangles. As you have probably guessed already, we will be granting read/write access to these properties just as we did with all of the other inherited properties. This will allow us to react to any change the ellipseWidth and ellipseHeight properties immediately after they have been altered.
Reminder: This is accomplished by calling the update method.
Create the class constructor. Give the _ellipseWidth and _ellipseHeight properties a default value of 0. We don’t want the edges to be rounded by default.
public function RectangleShape() {
_ellipseWidth = 0;
_ellipseHeight = 0;
super();
}
You should remember the draw method from the EditableShape class was an abstract method. That is a method that has an empty implementation. We are going to override this method to give it the proper functionality. The methods name is self explanatory. The method will do the actual drawing of the shape.
Important: We call the base class’s constructor, super(), after we have assigned a value to the _ellipseWidth and _ellipseHeight properties. Remember the the constructor initializes the shape by calling the init method which then calls the update method which finally calls the draw method. You will see in a minute that the draw method requires these properties. Forgeting to call the constructor last will result in an argument error thrown by the drawRoundRect method of the graphics object.
protected override function draw():void {
getGraphics().drawRoundRect( 0, 0, width, height, _ellipseWidth, _ellipseHeight );
}
I have to apologize to all of you who were expecting ten or more lines of code here in the draw method. That’s not the case here. Remember that the base class(EditableShape) does most of the work for us. It handles colors, gradients, alphas, line properties and so on. All we needed to do is draw the shape and that’s what we’ve done. The graphics object can no longer be accessed by outside code since the property has been overriden so we had to access it through the protected method, getGraphics. Finally we call the drawRoundRect method on the graphics object and we pass in the appropriate parameters.
Step 8: Overriding the Copy and Clone Methods
There are two more methods we need to override before we can conclude this class. The first method is the copy method. This is the method from the EditableShape class that takes an EditableShape as a parameter and causes the properties of the parent shape onto the parameter shape. We need add some functionality to this method. We will be overriding this method but that doesn’t mean that we have to forget everything that the base class does with this method. We’ll use the super object to call the method from the base class so that it inherits its original functionality while adding new functionality to it.
public override function copy( shape:EditableShape ):void {
super.copy( shape );
if ( shape is RectangleShape ) {
var rect:RectangleShape = shape as RectangleShape;
ellipseWidth = rect.ellipseWidth;
ellipseHeight = rect.ellipseHeight;
}
}
Can you tell what just happened? If the parameter, shape, is a RectangleShape we also copy the ellipseWidth and ellipseHeight properties. If you have a collection of RectangleShapes, that you are using to draw a graphic with, and you’d like them all to have the same ellipseWidth and ellipseHeight properties, this will keep you from writing too many extra lines of code.
The second method we need to override is the clone method.
public override function clone():EditableShape {
var shape:RectangleShape = super.clone() as RectangleShape;
shape.ellipseWidth = _ellipseWidth;
shape.ellipseHeight = _ellipseHeight;
return shape;
}
The clone method also inherits the implementation of the base class. But with this implementation we have added the ellipseWidth and ellipseHeight properties.
Step 9: More Access
Write the following getter and setter methods:
public function set ellipseWidth( value:Number ):void {
if ( value < 0 ) value = 0;
_ellipseWidth = value;
update();
}
public function get ellipseWidth():Number {
return _ellipseWidth;
}
public function set ellipseHeight( value:Number ):void {
if ( value < 0 ) value = 0;
_ellipseHeight = value;
update();
}
public function get ellipseHeight():Number {
return _ellipseHeight;
}
We have not only granted the read/write access that is needed by outside code, but we have also made sure that the ellipseWidth and ellipseHeight are never below zero. There is also an immediate reaction to these properties being set or changed.
Note: If you do not want to correct any values for the ellipseWidth or ellispeHeight properties, you do not have to. There may be a time when you’d want the ellipseWidth or ellipseHeight to be less than zero.
Now that we have our first shape complete. Let’s take a quick look at what our shape looks like when we first create it. You will need to create a document class and create a new instance of the RectangleShape object within it. Add the new RectangleShape to the stage. Here’s my code:
stage.addChild( new RectangleShape() );
Let’s look at the result.
This is the what our shape looks like by default. Feel free to play around with it a bit so that you get a good feel for it. Try changing the colors or modifying the size.
Step 9: Drawing Circles
We are going to create a sort of bubbling effect for the over state of our button. This means that we will need bubbles. Create the EclipseShape class. Make sure that it extends the EditableShape class.
package {
public class EllipseShape extends EditableShape {
public function EllipseShape() {
super();
}
protected override function draw():void {
getGraphics().drawEllipse( 0, 0, width, height );
}
}
}
Once again the power of inheritance has allowed us to recycle old code instead of writing the same code over and over again. This class only contains inherited properties and methods. We had to override the draw method in order to create the actual eclipse shape though. The drawEclipse method uses the inherited width and height properties to draw our shape. And once again we access the graphics object using the protected getGraphics method.
We now have all of the shapes we need to draw our button.
Step 10: Starting the Button
Create a new class that extends flash.display.Sprite. You can call the class whatever you’d like. I’m going to use the name MyButton. Import the following classes.
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.filters.BlurFilter;
import flash.filters.GlowFilter;
import flash.events.MouseEvent;
import flash.filters.DropShadowFilter;
import flash.system.System;
import flash.filters.BevelFilter;
import com.greensock.TweenMax;
public class MyButton extends Sprite {
Define the following properties.
private var _width:Number; private var _active:Boolean; private var base:RectangleShape; private var toggleBase:RectangleShape; private var txt:TextField; private var effectTxt:TextField; private var toggleTxt:TextField; private var toggleGlass:RectangleShape; private var toggleGlassAlpha:Number; private var ref:RectangleShape; private var light:RectangleShape; private var lightAlpha:Number; private var glow:RectangleShape; private var effectMask:RectangleShape; private var txtMask:RectangleShape; private var effectTxtMask:RectangleShape; private var toggleTxtMask:RectangleShape; private var effectContainer:Sprite; private var circles:Array; private var tweens:Array; private var circleBlur:BlurFilter; private var _toggle:Boolean;
Just like before, any property that starts with an underscore is either expected to cause an immediate reaction when set or the property will be read-only.
The _width property will be used to override the width property of the base class and we redraw the shape everytime the width property is set.
The active property is going to be used to swap the toggle states of the button later.
You will notice several properties that are RectangleShapes, several properties that are TextFields, and a Sprite. We will see the role each of these properties play later.
We’ll be storing all of our bubbles in the circles Array. To improve performance we will need to pause various tweens. In order to do this we will need to store these tweens in an array so that they can be accessed whenever we need them.
Last we have the circleBlur property which is a BlurFilter that is to be applied to each bubble, and we have the _toggle property which affects the behavior of the button.
Create the following public constants.
public static const BUTTON_HEIGHT:Number = 40; public static const NUM_OF_CIRCLES:uint = 12;
The height of our button will not change so it’s appropriate to store the value of the button’s height in a class constant. The size of the button’s ellipse is relative to the button’s height. If the button becomes too large or too small, our button may lose the Pill Button look that we are going for. Also the number of bubbles is predetermined so we store this value in a class constant as well.
Step 11: The Base
When ever I am drawing graphics for a component like a text field or a button, I always, always, always… start with a base shape. This is a shape that acts as the foundation of the shape. It is the backbone of the component. How the graphic is structured is entirely dependent on how I construct the base.
You will notice a property that we have defined called toggleBase. I am not talking about this property. I am talking about the base property itself. This shape will define the outline of the button. Let’s create this property and initialize its values now. Add the following code to the constructor of the class that you have defined as your button.
public function MyButton() {
super();
buttonMode = true;
mouseChildren = false;
_width = 100;
base = new RectangleShape();
base.height = BUTTON_HEIGHT;
base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT;
base.fillGradientRotation = 90;
base.fillColors = [ 0xAAAAAA, 0x444444 ];
base.lineColors = [ 0x222222 ];
base.lineAlphas = [ .5 ];
base.lineRatios = [ 1 ];
addChild( base );
}
The default fillGradientRotation is 0. This value is in degrees but is converted into radians when passed into the gradient box(Matrix). Along with initializing the button’s size, we have to set the rotation of the gradient. In this case we want this value to be 90 degrees. We also set the fillColors (as an Array), and the lineColors.
It is important to remember that the default value for these properties is an Array that has a length of two. All of the Array properties(lineColor,lineAlpha, and lineRatios) must be the same length. If they aren’t, the graphics will not draw. This is why we set the lineAlphas and lineRatios properties.
If you test the Flash movie, you should get this.
This is the overrall shape we are going for, “The Gel Pill”. Let’s make it shine a little bit.
Step 12: The Reflection
We’ve got the main structure of the button down already but now we need to add the small thing that I love to preach about. We need lots and lots of depth. We can start by adding the reflection. When light hits a glossy surface, it reflects off of the surface. Instantiate and initialize the ref (short for reflection) property.
super(); buttonMode = true; mouseChildren = false; _width = 100; base = new RectangleShape(); base.height = BUTTON_HEIGHT; base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT; base.fillGradientRotation = 90; base.fillColors = [ 0xAAAAAA, 0x444444 ]; base.lineColors = [ 0x222222 ]; base.lineAlphas = [ .5 ]; base.lineRatios = [ 1 ]; ref = new RectangleShape(); ref.fillGradientRotation = 90; ref.ellipseHeight = ref.ellipseWidth = ref.height = BUTTON_HEIGHT / 2; ref.fillColors = [ 0xFFFFFF, 0xFFFFFF ]; ref.fillAlphas = [ 1, 0 ]; ref.lineColors = [ 0 ]; ref.lineAlphas = [ 0 ]; ref.lineRatios = [ 0 ]; ref.alpha = .5; ref.x = ref.ellipseHeight / 2; ref.width = _width - ref.height; addChild( base ); addChild( ref );
We have turned the alpha property down a little bit so the the reflection isn’t so intense but we have basically followed the same steps to draw the ref shape as we did with the base shape. And of course we have sized and positioned the reflection relative to the size of the button’s height. Let’s see what we have now.
Not too bad. Still needs a lot more depth though. We’ll add some more later.
Step 13: The Inner Light
Sometimes when you are creating art, you notice that it’s those tiny little details that make the biggest difference in the appearance of your work. The light property will add the tiny detail into our button that makes all of the difference. This property will also be used a little differently in our button’s over and out states, but for now it will make our button appear to have a semi-transparent surface.
Add the following code to your button’s constructor method.
light = new RectangleShape(); light.copy( ref ); light.x = ref.x; light.y = BUTTON_HEIGHT - light.height; light.width = _width - light.height; light.fillColors = [ 0xFFFFFF, 0xDDDDDD ]; light.filters = [ new GlowFilter( 0xFFFFFF, .8, 10, 10, 2, 3 ), new BlurFilter( 6, 6, 3 ) ]; light.alpha = lightAlpha = .3; addChild( light );
The constructor should now look like this:
public function MyButton() {
super();
buttonMode = true;
mouseChildren = false;
_width = 100;
base = new RectangleShape();
base.height = BUTTON_HEIGHT;
base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT;
base.fillGradientRotation = 90;
base.fillColors = [ 0xAAAAAA, 0x444444 ];
base.lineColors = [ 0x222222 ];
base.lineAlphas = [ .5 ];
base.lineRatios = [ 1 ];
ref = new RectangleShape();
ref.fillGradientRotation = 90;
ref.ellipseHeight = ref.ellipseWidth = ref.height = BUTTON_HEIGHT / 2;
ref.fillColors = [ 0xFFFFFF, 0xFFFFFF ];
ref.fillAlphas = [ 1, 0 ];
ref.lineColors = [ 0 ];
ref.lineAlphas = [ 0 ];
ref.lineRatios = [ 0 ];
ref.alpha = .5;
ref.x = ref.ellipseHeight / 2;
ref.width = _width - ref.height;
light = new RectangleShape();
light.copy( ref );
light.x = ref.x;
light.y = BUTTON_HEIGHT - light.height;
light.width = _width - light.height;
light.fillColors = [ 0xFFFFFF, 0xDDDDDD ];
light.filters = [ new GlowFilter( 0xFFFFFF, .8, 10, 10, 2, 3 ), new BlurFilter( 6, 6, 3 ) ];
light.alpha = lightAlpha = .3;
addChild( base );
addChild( light );
addChild( ref );
}
We have added a GlowFilter and a BlurFilter to this object so that we can achieve a specific look. We don’t want the shape to look like a shape at all but empty space within the button. Notice that we have used the copy method to mimic the properties of the ref object. There’s no need to write the same lines of code for the light object. Just mimic the ref property and make just a few modifications and you have yourself a new shape.
See how we are beginning to see more depth as our shape is being constructed? The pill doesn’t look fully opaque and it isn’t very transparent either. We want an “Out of This World” kind of look so just in between is perfect.
Step 14: Simple Text
Now that we have a good foundation down on the table, it’s appropriate to begin adding the first TextField to the button. Initialize the txt property. And create a mask that is the same size and ellipse as the base so that the text doesn’t appear outside of the button.
public function TutButton() {
super();
buttonMode = true;
mouseChildren = false;
_width = 100;
base = new RectangleShape();
base.height = BUTTON_HEIGHT;
base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT;
base.fillGradientRotation = 90;
base.fillColors = [ 0xAAAAAA, 0x444444 ];
base.lineColors = [ 0x222222 ];
base.lineAlphas = [ .5 ];
base.lineRatios = [ 1 ];
ref = new RectangleShape();
ref.fillGradientRotation = 90;
ref.ellipseHeight = ref.ellipseWidth = ref.height = BUTTON_HEIGHT / 2;
ref.fillColors = [ 0xFFFFFF, 0xFFFFFF ];
ref.fillAlphas = [ 1, 0 ];
ref.lineColors = [ 0 ];
ref.lineAlphas = [ 0 ];
ref.lineRatios = [ 0 ];
ref.alpha = .5;
ref.x = ref.ellipseHeight / 2;
ref.width = _width - ref.height;
light = new RectangleShape();
light.copy( ref );
light.x = ref.x;
light.y = BUTTON_HEIGHT - light.height;
light.width = _width - light.height;
light.fillColors = [ 0xFFFFFF, 0xDDDDDD ];
light.filters = [ new GlowFilter( 0xFFFFFF, .8, 10, 10, 2, 3 ), new BlurFilter( 6, 6, 3 ) ];
light.alpha = lightAlpha = .3;
txt = new TextField();
txt.selectable = false;
txt.type = "dynamic";
txt.wordWrap = false;
txt.multiline = false;
txt.autoSize = "left";
txt.defaultTextFormat = new TextFormat( null, 27 );
txt.textColor = 0x333333;
txt.text = "label";
txt.height = BUTTON_HEIGHT;
txtMask = new RectangleShape();
txtMask.copy( base );
txtMask.filters = [];
txt.mask = txtMask;
addChild( base );
addChild( light );
addChild( ref );
addChild( txt );
addChild( txtMask );
}
We have once again utilized the copy method by creating a copy of the base object. We could have very well have used the clone method also. The result would be the same. Also note that we have set the filters array of the txtMask object to a blank array. This is because when the copy method used the filters property is also inherited. We have just simply overriden this property. Let’s see where we’re at.
We will position all of the TextFields later on. For now our txt will just have to hang out on the left side of our button.
Step 15: Setting Up for the Over State
Our button will illuminate and display a sort of science fiction like animation sequence with bubbles when the mouse hovers over the button. Of course the entire sequence will be accomplished 100% with code.
But before we can do this we need to create the elements of the sequence. The first is the background object, or the glow property, of the over state. Let’s add the glow object now. Add the following code to the constructor method:
glow = new RectangleShape(); glow.copy( base ); glow.fillColors = [ 0xFFFFFF ]; glow.fillAlphas = [ 1 ]; glow.fillRatios = [ 0 ]; glow.lineColors = [ 0 ]; glow.lineAlphas = [ 0 ]; glow.lineRatios = [ 0 ]; glow.filters = [ new GlowFilter( 0x00BCE9, 1, 10, 10, 2, 3, false, false ), new GlowFilter( 0x00BCE9, 1, 20, 20, 2, 3, true ) ]; addChild( base ); addChild( light ); addChild( ref ); addChild( txt ); addChild( txtMask ); addChild( glow );
We will also need a container for the bubbles to be displayed in and a mask for that container so that the bubbles don’t float out of the button.
public function MyButton() {
super();
buttonMode = true;
mouseChildren = false;
_width = 100;
base = new RectangleShape();
base.height = BUTTON_HEIGHT;
base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT;
base.fillGradientRotation = 90;
base.fillColors = [ 0xAAAAAA, 0x444444 ];
base.lineColors = [ 0x222222 ];
base.lineAlphas = [ .5 ];
base.lineRatios = [ 1 ];
ref = new RectangleShape();
ref.fillGradientRotation = 90;
ref.ellipseHeight = ref.ellipseWidth = ref.height = BUTTON_HEIGHT / 2;
ref.fillColors = [ 0xFFFFFF, 0xFFFFFF ];
ref.fillAlphas = [ 1, 0 ];
ref.lineColors = [ 0 ];
ref.lineAlphas = [ 0 ];
ref.lineRatios = [ 0 ];
ref.alpha = .5;
ref.x = ref.ellipseHeight / 2;
ref.width = _width - ref.height;
light = new RectangleShape();
light.copy( ref );
light.x = ref.x;
light.y = BUTTON_HEIGHT - light.height;
light.width = _width - light.height;
light.fillColors = [ 0xFFFFFF, 0xDDDDDD ];
light.filters = [ new GlowFilter( 0xFFFFFF, .8, 10, 10, 2, 3 ), new BlurFilter( 6, 6, 3 ) ];
light.alpha = lightAlpha = .3;
txt = new TextField();
txt.selectable = false;
txt.type = "dynamic";
txt.wordWrap = false;
txt.multiline = false;
txt.autoSize = "left";
txt.defaultTextFormat = new TextFormat( null, 27 );
txt.textColor = 0x333333;
txt.text = "label";
txt.height = BUTTON_HEIGHT;
txtMask = new RectangleShape();
txtMask.copy( base );
txtMask.filters = [];
txt.mask = txtMask;
glow = new RectangleShape();
glow.copy( base );
glow.fillColors = [ 0xFFFFFF ];
glow.fillAlphas = [ 1 ];
glow.fillRatios = [ 0 ];
glow.lineColors = [ 0 ];
glow.lineAlphas = [ 0 ];
glow.lineRatios = [ 0 ];
glow.filters = [ new GlowFilter( 0x00BCE9, 1, 10, 10, 2, 3, false, false ), new GlowFilter( 0x00BCE9, 1, 20, 20, 2, 3, true ) ];
effectContainer = new Sprite();
effectContainer.cacheAsBitmap = true;
effectMask = new RectangleShape();
effectMask.copy( base );
effectMask.filters = [];
effectContainer.mask = effectMask;
addChild( base );
addChild( light );
addChild( ref );
addChild( txt );
addChild( txtMask );
addChild( glow );
addChild( effectContainer );
addChild( effectMask );
}
Step 16: Bubbles
Now we are going to blow a few bubbles. Add the following lines of code to the constructor method:
circleBlur = new BlurFilter( 5, 5, 3 ); circles = []; tweens = [];
The circleBlur is a BlurFilter that will be applied to all bubbles. This will give each bubble the specific science like look that we’re going for. We also need to create the arrays that will store all of the bubbles and the tweens that will animate each bubble.
We need a method that will generate all of the bubbles we need.
private function createEffect():void {
for ( var i:int = 0; i < NUM_OF_CIRCLES; i++ ) {
var circ:EllipseShape = new EllipseShape();
resetCircle( circ );
}
stopEffect();
}
Use a loop to iterate a block of code that creates a new EllipseShape object, or in this case a bubble, and run the resetCircle method on the bubble. We haven’t created this method yet but it re-initializes the parameter bubble. In the loop, the method simply initializes each bubble since they haven’t been initialized to begin with. Finally the stopEffect method is called. This method pauses the bubble’s animation sequence. We’ll write this method in a bit.
For now, create the following methods. (Also now is a good time to import com.greensock.TweenMax).
private static function randomNumber( min:int = 0, max:int = 10 ):int {
return Math.round( Math.random() * ( max - min ) + min );
}
private function resetCircle( circle:EllipseShape ):void {
circle.width = circle.height = randomNumber( 10, 20 );
effectContainer.addChild( circle );
circle.cacheAsBitmap = true;
circle.x = randomNumber( 0, _width );
circle.y = BUTTON_HEIGHT;
circle.filters = [ circleBlur ];
if ( circles.indexOf( circle ) == -1 ) {
circles.push( circle );
circle.fillColors = [ 0x00BCE9 ];
circle.fillAlphas = [ 1 ];
circle.fillRatios = [ 1 ];
circle.lineAlphas = [ 0 ];
circle.lineRatios = [ 0 ];
circle.lineColors = [ 0 ];
circle.alpha = .6;
}
var tween:TweenMax = TweenMax.to( circle, randomNumber( 1, 6 ) * .5, { y:-BUTTON_HEIGHT, onComplete:doComplete } );
tweens.push( tween );
function doComplete():void {
resetCircle( circle );
removeTween( tween );
}
}
private function removeTween( tween:TweenMax ):void {
var a:Array = [];
for each( var t:TweenMax in tweens ) {
if ( t != tween ) a.push( t );
}
tweens = null;
System.gc();
tweens = a;
}
The randomNumber method returns a random number based on the parameters passed into the method. The first parameter specifies a the minimum value that can be generated and the second parameter specifies the maximum value that can be generated. We need this method to generate a random location and size for a bubble when we reset a bubble.
This brings me to the resetCircle method. I said before that this method re-initializes the specified bubble. To be more specific, the method recycles a bubble and makes it appear like a brand new bubble that has just been created when in reality it is just another old bubble.
The to method from the TweenMax class returns the tween that the bubble contains. We push this bubble into the tweens array. The bubble starts below the button and rises above the button. When the tween is finished it is no longer needed. The tween needs to be removed (using the removeTween method) and the bubble needs to be reset or recycled. The anonymous function doComplete does just that.
Before we can see what our bubbles look like we need to create two methods that will control playback of the bubbles’ animation sequence. Besides if we added a call to the createEffect method in the constructor and tested our movie now, the bubbles would be all there but we wouldn’t see them because they would be beneath the button. Remember that each bubble is added to the effectContainer in the resetCircle method and that the effectContainer is masked so that the bubbles aren’t seen outside of the button.
(Plus, if you tested the movie now you’d just get a compile-time error for not implementing the stopEffect method that is called when we create our bubbles.)
private function playEffect():void {
for each( var tween:TweenMax in tweens ) {
tween.play();
}
}
private function stopEffect():void {
for each( var tween:TweenMax in tweens ) {
tween.pause();
}
}
Loop through the tweens array to play each tween and to pause each tween when needed. Very simple.
Now add a call to the createEffect method and the playEffect method within the constructor. Test your movie. You should now have a beautiful display of bubbles. When you are finished remove the playEffect method from the constructor that you just added but keep the createEffect method.
Step 17: Glowing Text
Let’s add the glowing TextField to the button. We’re using a seperate TextField, effectTxt instead of txt, so that we can do a fading transition into the next state. We need an additional TextField for this. We also need another mask for our TextField just as we did with the first one.
public function MyButton() {
super();
buttonMode = true;
mouseChildren = false;
_width = 100;
base = new RectangleShape();
base.height = BUTTON_HEIGHT;
base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT;
base.fillGradientRotation = 90;
base.fillColors = [ 0xAAAAAA, 0x444444 ];
base.lineColors = [ 0x222222 ];
base.lineAlphas = [ .5 ];
base.lineRatios = [ 1 ];
ref = new RectangleShape();
ref.fillGradientRotation = 90;
ref.ellipseHeight = ref.ellipseWidth = ref.height = BUTTON_HEIGHT / 2;
ref.fillColors = [ 0xFFFFFF, 0xFFFFFF ];
ref.fillAlphas = [ 1, 0 ];
ref.lineColors = [ 0 ];
ref.lineAlphas = [ 0 ];
ref.lineRatios = [ 0 ];
ref.alpha = .5;
ref.x = ref.ellipseHeight / 2;
ref.width = _width - ref.height;
light = new RectangleShape();
light.copy( ref );
light.x = ref.x;
light.y = BUTTON_HEIGHT - light.height;
light.width = _width - light.height;
light.fillColors = [ 0xFFFFFF, 0xDDDDDD ];
light.filters = [ new GlowFilter( 0xFFFFFF, .8, 10, 10, 2, 3 ), new BlurFilter( 6, 6, 3 ) ];
light.alpha = lightAlpha = .3;
txt = new TextField();
txt.selectable = false;
txt.type = "dynamic";
txt.wordWrap = false;
txt.multiline = false;
txt.autoSize = "left";
txt.defaultTextFormat = new TextFormat( null, 27 );
txt.textColor = 0x333333;
txt.text = "label";
txt.height = BUTTON_HEIGHT;
txtMask = new RectangleShape();
txtMask.copy( base );
txtMask.filters = [];
txt.mask = txtMask;
glow = new RectangleShape();
glow.copy( base );
glow.fillColors = [ 0xFFFFFF ];
glow.fillAlphas = [ 1 ];
glow.fillRatios = [ 0 ];
glow.lineColors = [ 0 ];
glow.lineAlphas = [ 0 ];
glow.lineRatios = [ 0 ];
glow.filters = [ new GlowFilter( 0x00BCE9, 1, 10, 10, 2, 3, false, false ), new GlowFilter( 0x00BCE9, 1, 20, 20, 2, 3, true ) ];
effectContainer = new Sprite();
effectContainer.cacheAsBitmap = true;
effectMask = new RectangleShape();
effectMask.copy( base );
effectMask.filters = [];
effectContainer.mask = effectMask;
effectTxt = new TextField();
effectTxt.selectable = false;
effectTxt.type = "dynamic";
effectTxt.wordWrap = false;
effectTxt.multiline = false;
effectTxt.autoSize = "left";
effectTxt.defaultTextFormat = new TextFormat( null, 27 );
effectTxt.textColor = 0x00BCE9;
effectTxt.text = "label";
effectTxt.filters = [ new GlowFilter( 0x00BCE9, 1, 16, 16, 2, 3 ) ];
effectTxtMask = new RectangleShape();
effectTxtMask.copy( base );
effectTxtMask.filters = [];
effectTxt.mask = effectTxtMask;
circleBlur = new BlurFilter( 5, 5, 3 );
circles = [];
tweens = [];
addChild( base );
addChild( light );
addChild( ref );
addChild( txt );
addChild( txtMask );
addChild( glow );
addChild( effectContainer );
addChild( effectMask );
addChild( effectTxt );
addChild( effectTxtMask );
createEffect();
}
Just another TextField with a glow filter. Again we will position the text fields later when we create the update method. Now you should have something that looks like this.
Now that all of the elements for the over state have been generated, we only have one more task to complete. Next we position the contents of the button.
Step 18: Positioning
Encapsulated in our button class is a function that positions the button’s contents based on the current value of the _width property. The method I am referring to is the update method. Call the update method on the last line of code of the constructor method. Then create the update method.
addChild( effectTxt );
addChild( effectTxtMask );
createEffect();
update();
}
protected function update():void {
base.width = _width;
glow.width = _width;
ref.width = _width - ref.height;
light.width = _width - light.height;
txt.x = ( _width - txt.width ) / 2;
txt.y = ( BUTTON_HEIGHT - txt.height ) / 2;
effectTxt.x = txt.x;
effectTxt.y = txt.y;
effectMask.width = _width;
txtMask.width = _width;
effectTxtMask.width = _width;
}
Resize particalar objects and reposition others. Test the movie:
We now have centered text. Set the alpha property of all of the effect base objects(effectContainer, effectTxt and glow) to zero. Re-test the movie and now you should see the original state of the button.
Step 19: The Over State
At last we finally get to the ever-so-exciting “over” state. Okay let’s go! Add the following lines of code at the end of the constructor method.
addEventListener( "rollOver", overState ); addEventListener( "rollOut", outState );
Now create the overState and outState event handler methods.
private function overState( e:MouseEvent ):void {
playEffect();
TweenMax.to( light, 3, { alpha:1 } );
TweenMax.to( glow, 1, { alpha:1 } );
TweenMax.to( effectTxt, 1, { alpha:1 } );
TweenMax.to( effectContainer, 1, { alpha:1 } );
}
private function outState( e:MouseEvent ):void {
TweenMax.to( light, 3, { alpha:lightAlpha } );
TweenMax.to( glow, 1, { alpha:0 } );
TweenMax.to( effectTxt, 1, { alpha:0 } );
TweenMax.to( effectContainer, 1, { alpha:0, onComplete:stopEffect } );
//upState();
}
The overState method plays the bubbles animation and performs a fade in transition of the effect whenever the mouse hovers over the button.
The outState method does the complete opposite: it returns the button back to its original state when the mouse hovers off of the button, lets the light property linger for a second to give the button a sense of losing power or losing illumination.
Comment out the upState method so that you can test the movie without any errors. We’ll create this method soon.
Important: Remember to uncomment out the call to the upState method within the outState method after we have created that method.
Step 20: Up and Down States
Add the following code at the end of the constructor method.
addEventListener( "mouseDown", downState ); addEventListener( "mouseUp", upState );
Create the following methods.
private function downState( e:MouseEvent ):void {
filters = [ new GlowFilter( 0 ) ];
}
private function upState( e:MouseEvent = null ):void {
filters = [];
}
The downState method displays a background glow around the button when the mouse button is down and the upState button removes that glow return the button back to it’s initial state. The upState method’s e parameter is set to null by default. This allows the method to be called manually and not require the dispatching of a MouseEvent. We’ll test to see where were at after the next step.
Reminder: Uncomment the call to the upState method from the outState method.
Step 21: Toggle Button
Add the missing code to the constructor method.
public function MyButton() {
super();
buttonMode = true;
mouseChildren = false;
_width = 100;
base = new RectangleShape();
base.height = BUTTON_HEIGHT;
base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT;
base.fillGradientRotation = 90;
base.fillColors = [ 0xAAAAAA, 0x444444 ];
base.lineColors = [ 0x222222 ];
base.lineAlphas = [ .5 ];
base.lineRatios = [ 1 ];
ref = new RectangleShape();
ref.fillGradientRotation = 90;
ref.ellipseHeight = ref.ellipseWidth = ref.height = BUTTON_HEIGHT / 2;
ref.fillColors = [ 0xFFFFFF, 0xFFFFFF ];
ref.fillAlphas = [ 1, 0 ];
ref.lineColors = [ 0 ];
ref.lineAlphas = [ 0 ];
ref.lineRatios = [ 0 ];
ref.alpha = .5;
ref.x = ref.ellipseHeight / 2;
ref.width = _width - ref.height;
light = new RectangleShape();
light.copy( ref );
light.x = ref.x;
light.y = BUTTON_HEIGHT - light.height;
light.width = _width - light.height;
light.fillColors = [ 0xFFFFFF, 0xDDDDDD ];
light.filters = [ new GlowFilter( 0xFFFFFF, .8, 10, 10, 2, 3 ), new BlurFilter( 6, 6, 3 ) ];
light.alpha = lightAlpha = .3;
txt = new TextField();
txt.selectable = false;
txt.type = "dynamic";
txt.wordWrap = false;
txt.multiline = false;
txt.autoSize = "left";
txt.defaultTextFormat = new TextFormat( null, 27 );
txt.textColor = 0x333333;
txt.text = "label";
txt.height = BUTTON_HEIGHT;
txtMask = new RectangleShape();
txtMask.copy( base );
txtMask.filters = [];
txt.mask = txtMask;
glow = new RectangleShape();
glow.copy( base );
glow.fillColors = [ 0xFFFFFF ];
glow.fillAlphas = [ 1 ];
glow.fillRatios = [ 0 ];
glow.lineColors = [ 0 ];
glow.lineAlphas = [ 0 ];
glow.lineRatios = [ 0 ];
glow.filters = [ new GlowFilter( 0x00BCE9, 1, 10, 10, 2, 3, false, false ), new GlowFilter( 0x00BCE9, 1, 20, 20, 2, 3, true ) ];
glow.alpha = 0;
effectContainer = new Sprite();
effectContainer.cacheAsBitmap = true;
effectContainer.alpha = 0;
effectMask = new RectangleShape();
effectMask.copy( base );
effectMask.filters = [];
effectContainer.mask = effectMask;
effectTxt = new TextField();
effectTxt.selectable = false;
effectTxt.type = "dynamic";
effectTxt.wordWrap = false;
effectTxt.multiline = false;
effectTxt.autoSize = "left";
effectTxt.defaultTextFormat = new TextFormat( null, 27 );
effectTxt.textColor = 0x00BCE9;
effectTxt.text = "label";
effectTxt.filters = [ new GlowFilter( 0x00BCE9, 1, 16, 16, 2, 3 ) ];
effectTxt.alpha = 0;
effectTxtMask = new RectangleShape();
effectTxtMask.copy( base );
effectTxtMask.filters = [];
effectTxt.mask = effectTxtMask;
toggleBase = new RectangleShape();
toggleBase.height = BUTTON_HEIGHT;
toggleBase.ellipseHeight = toggleBase.ellipseWidth = BUTTON_HEIGHT;
toggleBase.fillGradientRotation = 90;
toggleBase.fillColors = [ 0x00CDFF, 0x00A9D2 ];
toggleBase.lineColors = [ 0x00A9D2 ];
toggleBase.lineAlphas = [ .5 ];
toggleBase.lineRatios = [ 1 ];
toggleBase.visible = false;
toggleBase.alpha = 0;
toggleBase.filters = [ new BevelFilter( 5, 45, 0xFFFFFF, 1, 0, .6, 18, 18, 1, 3, "inner" ), new GlowFilter( 0x00BCE9, .8, 6, 6, 1, 3 ) ];
toggleGlass = new RectangleShape();
toggleGlass.copy( toggleBase );
toggleGlass.fillColors = [ 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF ];
toggleGlass.fillAlphas = [ 0, .3, .2, .3 ];
toggleGlass.fillRatios = [ 1, 127, 127, 255 ];
toggleGlass.filters = [ new DropShadowFilter( 0, 45, 0xFFFFFF, .8, 10, 10, 2, 3, true ) ];
toggleGlass.visible = false;
toggleTxt = new TextField();
toggleTxt.selectable = false;
toggleTxt.type = "dynamic";
toggleTxt.wordWrap = false;
toggleTxt.multiline = false;
toggleTxt.autoSize = "left";
toggleTxt.defaultTextFormat = new TextFormat( null, 27 );
toggleTxt.textColor = 0xFFFFFF;
toggleTxt.text = "label";
toggleTxt.filters = [ new BevelFilter( 1, 45, 0, .4, 0xFFFFFF, 1, 2, 2, 2, 3, "inner" ), new GlowFilter( 0xFFFFFF, 1, 16, 16, 2, 3 ) ];
toggleTxt.alpha = 0;
toggleTxt.visible = false;
toggleTxtMask = new RectangleShape();
toggleTxtMask.copy( base );
toggleTxtMask.filters = [];
toggleTxt.mask = toggleTxtMask;
circleBlur = new BlurFilter( 5, 5, 3 );
circles = [];
tweens = [];
addChild( base );
addChild( light );
addChild( ref );
addChild( txt );
addChild( txtMask );
addChild( glow );
addChild( effectContainer );
addChild( effectMask );
addChild( effectTxt );
addChild( effectTxtMask );
addChild( toggleBase );
addChild( toggleGlass );
addChild( toggleTxt );
addChild( toggleTxtMask );
createEffect();
update();
addEventListener( "rollOver", overState );
addEventListener( "rollOut", outState );
addEventListener( "mouseDown", downState );
addEventListener( "mouseUp", upState );
}
We have just created the all of the objects that we need for what I call the active state. When the button's _toggle property is set to true, the button behaves differently. We'll see how in a moment. First add the following lines of code to the update method.
toggleBase.width = _width; toggleTxtMask.width = _width; toggleGlass.width = _width; toggleTxt.x = txt.x; toggleTxt.y = txt.y;
Now create two new methods. The goActive method and the goInactive method.
private function goActive():void {
if ( !_active ) {
_active = true;
TweenMax.allTo( [ toggleBase, toggleGlass, toggleTxt ], .4, { autoAlpha:1 } );
TweenMax.to( glow, 1, { alpha:1 } );
}
}
private function goInactive():void {
if ( _active ) {
_active = false;
TweenMax.allTo( [ toggleBase, toggleGlass, toggleTxt ], .4, { autoAlpha:0 } );
}
}
Let's take a quick look at each method. The goActive method animates to the active state only if the button is inactive. It then set's the _active property to true. The goInactive method does the complete opposite. If the button is active, it returns to an inactive state and sets the _active property to false.
Now for the final step in creating toggle behavior for our button. Add this code to the upState method.
if ( e && _toggle ) {
if ( _active ) {
goInactive();
}
else {
goActive();
}
}
The code above checks to see if the method has been called do to an event being dispatched (we don't want this block of code to execute otherwise) and it checks to see if the button's toggle property has been enabled. Then the button travels to the appropriate state based on its current state. In other words, it toggles.
Set the _toggle property to true somewhere in the constructor and test the movie.
We now have a beautiful button. There's just one last problem. Our button isn't editable - well, not from outside of itself anyway. So, next were going to create the class's getters and setters.
Step 23: Edit This
First let's override the width property to give access to our custom _width property.
public override function set width( value:Number ):void {
if ( value < 0 ) value = 0;
_width = value;
update();
}
public override function get width():Number {
return _width;
}
Don't allow the value to be lower than zero. Set the _width property and call the update method.
Now override the height property.
public override function set height( value:Number ):void {
throw new Error( "Property 'height' has been overriden and is now read-only." );
}
public override function get height():Number {
return BUTTON_HEIGHT;
}
Trying to set the height property just throws an error because this property now has a fixed value which is the BUTTON_HEIGHT constant.
Create the following getter and setter methods.
public function set label( value:String ):void {
txt.text = value;
effectTxt.text = value;
toggleTxt.text = value;
update();
}
public function get label():String {
return txt.text;
}
public function set active( value:Boolean ):void {
if ( value ) {
goActive();
}
else {
goInactive();
}
}
public function get active():Boolean {
return _active;
}
public function set toggle( value:Boolean ):void {
_toggle = value;
if ( !value && active ) goInactive();
}
public function get toggle():Boolean {
return _toggle;
}
When the label is set we immediatly react to the change by setting the text values of all of the TextField objects and we position them by calling the update method. The active property changes states based on the new value. When we set the toggle property, if it's new value is false we check to see if the button is active and if it is we switch it to its original state.
Trying setting the width property to 300 and the label to "Push Me", and making the button an active toggle button.
Step 24: More Depth
I said earlier that we would add more depth to the button. I also said that the tiny details make a large difference. Well this can be accomplished very quickly and easily by applying a couple of filters. Add these two lines of code anywhere in the constructor method. Then test the movie.
base.filters = [ new DropShadowFilter( 5, 45, 0, .6, 6, 6, 1, 3 ), new BevelFilter( 0, 0, 0xFFFFFF, .5, 0, .5, 10, 10, 1, 3 ) ]; txt.filters = [ new DropShadowFilter( 3, 45, 0, 1, 6, 6, 1, 3 ) ];
Let's see what we have now. The button on top is the original and the button on the button is the new and improved button with the above filters.
Wow! Look at that button pop out. Now you can see how important it is to use filters when drawing graphics to add in shadows and other little details that make your graphics that much better.
Extras
There are a couple of features within the EditableShape class that we didn't have time to use. One feature is the clone method. It returns a clone of the parent shape. Another is the useBitmapFill property. You must specify a valid BitmapData object by setting the bitmapData property and you have to set useBitmapFill to true.
Here's our button with a bitmap fill applied to the base property.
I also didn't discuss the matchGradientSize property which is true by default. If set to false the width and height of the fill and line gradients will not mimic the size of the actual shape. You can get create by deactivating this property. You can even use TweenLite (or TweenMax) to animate these and most of the properties in the EditableShape class.
Conclusion
Well our button is complete and our client is happy. We have only scratched the surface. There's a lot more that you can do with Flash's drawing API. I challenge you to make a CustomShape class that extends EditableShape. The class should use the lineTo methods of the graphics object to draw irregular shapes. Have fun with it. Thanks for tuning in. We've learned a lot. See you next time.
View full post on Activetuts+

Most programmers have never touched design. If you have always wanted to beautiful graphics in your Flash projects but never knew how to use Adobe Photoshop or Illustrator, this tutorial is for you. Nice graphics can be created entirely with code. Drawing graphics for components with code even has a few advantages.
Prerequisites
This tutorial requires that you know how to perform basic tweens using Greensock’s TweenLite or TweenMax. You can download the latest version of TweenLite from the Greensock website. I will be using Flash CS5 Professional to complete this project. You may use any Flash IDE you’d like (such as FlashDevelop or FDT). It would also help to have a fair understanding of inheritance.
A Brief Introduction
Before we get started, let’s take a look at what we’ll be learning and why it’s important. When designing Flash applications there are many ways to generate graphics. You can use an external tool such as Adobe Illustrator or draw complex graphics with Adobe Photoshop. You can even use 3D utilities such as Cinema 4D to create eye-popping 3D bitmaps to import into Flash. These techniques are great for creating a well designed static application – static meaning not very dynamic, or in most cases, not dynamic at all.
If you use one of the above methods to generate graphics for Flash, you may find it a little difficult to resize the graphics without some loss of quality or, even worse, distortion. On the sunny side, you can import a vector file that was generated with Adobe Illustrator into your Flash application and scale the image infinitely without distorting the image – but when you want to change the overall ratio of the image, you will notice that your image becomes a little deformed.
What we are going to do today is learn how efficient it can be to draw our application’s graphics with code. We are going to create a beautiful animated button that is completely generated with ActionScript.
Step 1: Pros and Cons
Before we dig in, I’d like to discuss the importance of planning for your application and answering simple questions to decide whether to draw the graphics with code or by hand (e.g. with Adobe Photoshop). To help us answer the questions below, let’s first examine the pros and cons to drawing with code.
The Pros
The Cons
You should now find the following questions pretty easy to answer. There are three questions you need to ask yourself before you start your project:
Does your application require graphics to be static or dynamic?
Do the graphics or components in your application need to be exclusive to Flash or be consistent across multiple platforms?
Is the graphic design of your application more detailed or quite simple?
Now examine your test results. If your test results closely resemble the results below, than the techniques we are getting ready to learn are for you?
If your results do not look like the results above, you may want to consider other methods. But for the rest of us, let’s proceed.
Step 2: Brainstorming
So here’s the scenario. A client has just asked us to create an animated button for their Flash site and AIR app. The button must be consistent with the science fiction theme of the client’s website. The button’s height can be a fixed size but the button must be able to accomodate for different widths. The button must have a toggle state that has a blatant difference between it’s active and inactive states. Finally the button must have an API that’s easy to work with.
Now that we know that we’ve got this themed component to create, we need to think about how we are going to go about creating it. Well, we know that we’re going to be drawing a lot of graphics with code so that the component will be easily and efficiently editable. Why not start off with a base class that will add high level functionality on top Flash’s drawing API?
We don’t want to draw graphics over and over again by writing the same lines of code so we’ll have a class with particular properties and methods specifically designed for reusing the code for us. We’ll extend this class to create the main shapes that we’ll need for our button.
Step 3: The Editable Shape Class
Now it’s time to get to it! Create a new class called
EditableShapeand add the following classes to the classpath.Now create the class declaration. The class should extend the
flash.display.Spriteclass.public class EditableShape extends Sprite {Create the following variables(properties) before the class constructor.
Within the class constructor add the following code and I’ll explain what we just did.
The primary function of the constructor is to instantiate all of the properties that we have defined. We also gave each property a default value. You will be able to see what the result of these values are later. Each property that begins with an underscore will be given read/write access to outside code. The reasons these properties are not public properites is because we want to respond to the change in value of each particular property. We shall do this within the setter methods that we’ll create later.
Each property is based on particular value that must be passed into any of the
graphicsobject methods. One example is the_fillGradientColorsproperty. It is an array ofuintsthat will be passed into thegraphics.beginGradientFillmethod. Another example is the_pixelHintingproperty. It will be passed into thegraphics.lineStylemethod’spixelHintingparameter.The last line of the constuctor calls the
initmethod. Theinitmethod is very simple. It calls theupdatemethod.Step 4: The Update Method
The
updatemethod is probably the most important method within theEditableShapeclass. The main objective of this method is to clear any existing graphics and redraw them based upon the latest data. We will call this method everytime a property has changed so the changes can be seen immediately by the user if necassary. Create theupdatemethod.protected function update():void { if ( _matchGradientSize ) { _lineGradientWidth = _width; _lineGradientHeight = _height; _fillGradientWidth = _width; _fillGradientHeight = _height; } lineGradientBox.createGradientBox( lineGradientWidth, lineGradientHeight, toRadians( lineGradientRotation ), lineTx, lineTy ); fillGradientBox.createGradientBox( fillGradientWidth, fillGradientHeight, toRadians( fillGradientRotation ), fillTx, fillTy ); super.graphics.clear(); super.graphics.lineStyle( lineThickness, 0, 0, pixelHinting ); super.graphics.lineGradientStyle( lineGradientType, lineColors, lineAlphas, lineRatios, lineGradientBox, lineSpreadMethod ); if ( !_bitmapData || !_useBitmapFill ) super.graphics.beginGradientFill( fillGradientType, fillColors, fillAlphas, fillRatios, fillGradientBox, fillSpreadMethod ) else super.graphics.beginBitmapFill( _bitmapData ); draw(); }The
Matrixobjects we created in the constructor are used to manipulate gradients. We needed two of them. One for the fill gradient and the other for the line gradient.We will be overriding the
get graphicsmethod so that we can forbid access to this property to outside code. We don’t want any outside code tampering with our graphics at all. Because we are going to override theget graphicsmethod, we will need to access the method from the subclass. This is accomplished through thesuperobject. We will also create a protected method calledgetGraphicslater on. This method is needed to give subclasses access to thegraphicsobject without granting access to the object to outside code.In conclusion, the update method re-initializes the graphics and it does this based on the current values of the properties within the
EditableShapeinstance. The last line of code calls thedrawmethod which is an abstract method in theEditableShapeclass. The method should be overriden by a subclass and is intended for drawing a particular shape using thegraphicsobject within its implementation.Step 5: Clones and Copies
Create the draw method.
protected function draw():void { // Abstract }Again the
drawmethod is just a simple abstract method that we can ignore for now.We will need to copy the properties of one shape onto another. There may also be times when you’d like a complete clone of a particular shape. We’ll create two methods to accomplish this for us. Doing so will save us a lot of time in the long run. Instead of always re-writing code we’ll just call the appropriate method which copies or clones a shape. Create the
copyandclonemethods.public function copy( shape:EditableShape ):void { _width = shape.width; _height = shape.height; _matchGradientSize = shape.matchGradientSize; _fillGradientType = shape.fillGradientType; _fillSpreadMethod = shape.fillSpreadMethod; _fillColors = shape.fillColors; _fillAlphas = shape.fillAlphas; _fillRatios = shape.fillRatios; _fillGradientWidth = shape.fillGradientWidth; _fillGradientHeight = shape.fillGradientHeight; _fillGradientRotation = shape.fillGradientRotation; _fillTx = shape.fillTx; _fillTy = shape.fillTy; _lineGradientType = shape.lineGradientType; _lineSpreadMethod = shape.lineSpreadMethod; _lineThickness = shape.lineThickness; _lineColors = shape.lineColors; _lineAlphas = shape.lineAlphas; _lineRatios = shape.lineRatios; _lineGradientWidth = shape.lineGradientWidth; _lineGradientHeight = shape.lineGradientHeight; _lineGradientRotation = shape.lineGradientRotation; _lineTx = shape.lineTx; _lineTy = shape.lineTy; _useBitmapFill = shape.useBitmapFill; if ( _bitmapData ) _bitmapData = shape.bitmapData.clone(); if ( filters ) filters = shape.filters; alpha = shape.alpha; update(); } public function clone():EditableShape { var c:Class = Class( getDefinitionByName( getQualifiedClassName( this ) ) ); var shape:EditableShape = new c(); shape.width = _width; shape.height = _height; shape.matchGradientSize = _matchGradientSize; shape.fillGradientType = _fillGradientType; shape.fillSpreadMethod = _fillSpreadMethod; shape.fillColors = _fillColors; shape.fillAlphas = _fillAlphas; shape.fillRatios = _fillRatios; shape.fillGradientWidth = _fillGradientWidth; shape.fillGradientHeight = _fillGradientHeight; shape.fillGradientRotation = _fillGradientRotation; shape.fillTx = _fillTx; shape.fillTy = _fillTy; shape.lineGradientType = _lineGradientType; shape.lineSpreadMethod = _lineSpreadMethod; shape.lineThickness = _lineThickness; shape.lineColors = _lineColors; shape.lineAlphas = _lineAlphas; shape.lineRatios = _lineRatios; shape.lineGradientWidth = _lineGradientWidth; shape.lineGradientHeight = _lineGradientHeight; shape.lineGradientRotation = _lineGradientRotation; shape.lineTx = _lineTx; shape.lineTy = _lineTy; shape.useBitmapFill = _useBitmapFill; if ( _bitmapData ) shape.bitmapData = _bitmapData.clone(); shape.filters = filters; shape.alpha = alpha; return shape; }The
copymethod takes theshapeparameter and sets all of the properties equal to that of the currentEditableShape.We will not be using the
clonemethod in this tutorial but I just threw it in as an extra bonus. The method returns a cloned copy of the currentEditableShapeclass.Step 6: Finishing Up the EditableShape
The last thing we need to do is grant read/write access to all of the shape’s main properties. Write the following getter and setter methods within the
EditableShapeclass.public override function set width(value:Number):void { _width = value; update(); } public override function get width():Number { return _width; } public override function set height(value:Number):void { _height = value; update(); } public override function get height():Number { return _height; } public function set fillGradientType( value:String ):void { switch ( value.toLowerCase() ) { case "linear" : case "radial" : _fillGradientType = value.toLowerCase(); update(); break; default : //Do nothing } } public function get fillGradientType():String { return _fillGradientType; } public function set fillSpreadMethod(value:String):void { switch ( value.toLowerCase() ) { case "pad" : case "reflect" : case "repeat" : _fillSpreadMethod = value.toLowerCase(); update(); default : //Do nothing } } public function get fillSpreadMethod():String { return _fillSpreadMethod; } public function set fillColors(array:Array):void { var a:Array = []; for each( var color:uint in array ) { if ( color is uint ) { a.push( color ); } } _fillColors = a; update(); } public function get fillColors():Array { return _fillColors; } public function set fillAlphas( array:Array ):void { var a:Array = []; for each( var nAlpha:Number in array ) { if ( nAlpha is Number) { a.push( nAlpha ); } } _fillAlphas = a; update(); } public function get fillAlphas():Array { return _fillAlphas; } public function set fillRatios(array:Array):void { var a:Array = []; for each(var ratio:Number in array) { if (ratio is int) { a.push(ratio); } } _fillRatios = a; update(); } public function get fillRatios():Array { return _fillRatios; } public function set fillGradientWidth(value:Number):void { _fillGradientWidth = value; update(); } public function get fillGradientWidth():Number { return _fillGradientWidth; } public function set fillGradientHeight(value:Number):void { _fillGradientHeight = value; update(); } public function get fillGradientHeight():Number { return _fillGradientHeight; } public function set fillGradientRotation(degrees:Number):void { _fillGradientRotation = degrees; update(); } public function get fillGradientRotation():Number { return _fillGradientRotation; } public function set fillTx(value:Number):void { _fillTx = value; update(); } public function get fillTx():Number { return _fillTx; } public function set fillTy(value:Number):void { _fillTy = value; update(); } public function get fillTy():Number { return _fillTy; } public function set lineGradientType(value:String):void { switch (value.toLowerCase()) { case "linear" : case "radial" : _lineGradientType = value.toLowerCase(); update(); break; default : //Do nothing } } public function get lineGradientType():String { return _lineGradientType; } public function set lineSpreadMethod(value:String):void { switch (value.toLowerCase()) { case "pad" : case "reflect" : case "repeat" : _lineSpreadMethod = value.toLowerCase(); update(); default : //Do nothing } } public function get lineSpreadMethod():String { return _lineSpreadMethod; } public function set lineThickness(value:Number):void { _lineThickness = value; update(); } public function get lineThickness():Number { return _lineThickness; } public function set lineColors(array:Array):void { var a:Array = []; for each(var color:uint in array) { if (color is uint) { a.push(color); } } _lineColors = array; update(); } public function get lineColors():Array { return _lineColors; } public function set lineAlphas(array:Array):void { var a:Array = []; for each(var nAlpha:Number in array) { if (nAlpha is Number) { a.push(nAlpha); } } _lineAlphas = a; update(); } public function get lineAlphas():Array { return _lineAlphas; } public function set lineRatios(array:Array):void { var a:Array = []; for each(var ratio:Number in array) { if (ratio is int) { a.push(ratio); } } _lineRatios = a; update(); } public function get lineRatios():Array { return _lineRatios; } public function set lineGradientWidth(value:Number):void { _lineGradientWidth = value; update(); } public function get lineGradientWidth():Number { return _lineGradientWidth; } public function set lineGradientHeight(value:Number):void { _lineGradientHeight = value; update(); } public function get lineGradientHeight():Number { return _lineGradientHeight; } public function set lineGradientRotation(degrees:Number):void { _lineGradientRotation = degrees; update(); } public function get lineGradientRotation():Number { return _lineGradientRotation; } public function set lineTx(value:Number):void { _lineTx = value; update(); } public function get lineTx():Number { return _lineTx; } public function set lineTy(value:Number):void { _lineTy = value; update(); } public function get lineTy():Number { return _lineTy; } public function set matchGradientSize(value:Boolean):void { _matchGradientSize = value; update(); } public function get matchGradientSize():Boolean { return _matchGradientSize; } public function set bitmapData( value:BitmapData ):void { _bitmapData = value; update(); } public function get bitmapData():BitmapData { return _bitmapData; } public function set useBitmapFill( value:Boolean ):void { _useBitmapFill = value; update(); } public function get useBitmapFill():Boolean { return _useBitmapFill; } public function set firstFillColor( value:uint ):void { _fillColors[ 0 ] = value; update(); } public function get firstFillColor():uint { if ( _fillColors.length > 0 ) { return _fillColors[ 0 ]; } else { return 0; } } public function set pixelHinting( value:Boolean ):void { _pixelHinting = value; update(); } public function get pixelHinting():Boolean { return _pixelHinting; } public override function get graphics():Graphics { return null; } protected function getGraphics():Graphics { return super.graphics; }All of the getter methods just return the corresponding properties value. But for the setter methods we correct or filter out any unwanted input. We also call the
updatemethod so that there is an immediate reaction to the new value or values.Step 7: Drawing Rectangles
The majority of our button will consist of rectangle shapes. Unless we want to bore our client’s users with a bunch of lame blocky shapes, we will want to be able to round the edges of our rectangles. The
EditableShapeclass is meant to be extended, meaning that it should have subclasses that finalize the primary function of the class. TheEditableShapeclass takes care of all of the hard work for us. All we have to do now is draw the shape within a subclass. Create a new class calledRectangleShapewhich extends theEditableShapeclass. The class declaration should look as follows.public class RectangleShape extends EditableShape {Add the following private properties to the class directly after the class declaration.
I mentioned before that we will be rounding the edges to our rectangles. As you have probably guessed already, we will be granting read/write access to these properties just as we did with all of the other inherited properties. This will allow us to react to any change the
ellipseWidthandellipseHeightproperties immediately after they have been altered.Reminder: This is accomplished by calling the
updatemethod.Create the class constructor. Give the
_ellipseWidthand_ellipseHeightproperties a default value of0. We don’t want the edges to be rounded by default.public function RectangleShape() { _ellipseWidth = 0; _ellipseHeight = 0; super(); }You should remember the
drawmethod from theEditableShapeclass was an abstract method. That is a method that has an empty implementation. We are going to override this method to give it the proper functionality. The methods name is self explanatory. The method will do the actual drawing of the shape.Important: We call the base class’s constructor,
super(), after we have assigned a value to the_ellipseWidthand_ellipseHeightproperties. Remember the the constructor initializes the shape by calling theinitmethod which then calls theupdatemethod which finally calls thedrawmethod. You will see in a minute that thedrawmethod requires these properties. Forgeting to call the constructor last will result in an argument error thrown by thedrawRoundRectmethod of thegraphicsobject.protected override function draw():void { getGraphics().drawRoundRect( 0, 0, width, height, _ellipseWidth, _ellipseHeight ); }I have to apologize to all of you who were expecting ten or more lines of code here in the
drawmethod. That’s not the case here. Remember that the base class(EditableShape) does most of the work for us. It handles colors, gradients, alphas, line properties and so on. All we needed to do is draw the shape and that’s what we’ve done. Thegraphicsobject can no longer be accessed by outside code since the property has been overriden so we had to access it through the protected method,getGraphics. Finally we call thedrawRoundRectmethod on thegraphicsobject and we pass in the appropriate parameters.Step 8: Overriding the Copy and Clone Methods
There are two more methods we need to override before we can conclude this class. The first method is the
copymethod. This is the method from theEditableShapeclass that takes anEditableShapeas a parameter and causes the properties of the parent shape onto the parameter shape. We need add some functionality to this method. We will be overriding this method but that doesn’t mean that we have to forget everything that the base class does with this method. We’ll use thesuperobject to call the method from the base class so that it inherits its original functionality while adding new functionality to it.public override function copy( shape:EditableShape ):void { super.copy( shape ); if ( shape is RectangleShape ) { var rect:RectangleShape = shape as RectangleShape; ellipseWidth = rect.ellipseWidth; ellipseHeight = rect.ellipseHeight; } }Can you tell what just happened? If the parameter,
shape, is aRectangleShapewe also copy theellipseWidthandellipseHeightproperties. If you have a collection ofRectangleShapes, that you are using to draw a graphic with, and you’d like them all to have the sameellipseWidthandellipseHeightproperties, this will keep you from writing too many extra lines of code.The second method we need to override is the
clonemethod.public override function clone():EditableShape { var shape:RectangleShape = super.clone() as RectangleShape; shape.ellipseWidth = _ellipseWidth; shape.ellipseHeight = _ellipseHeight; return shape; }The
clonemethod also inherits the implementation of the base class. But with this implementation we have added theellipseWidthandellipseHeightproperties.Step 9: More Access
Write the following getter and setter methods:
public function set ellipseWidth( value:Number ):void { if ( value < 0 ) value = 0; _ellipseWidth = value; update(); } public function get ellipseWidth():Number { return _ellipseWidth; } public function set ellipseHeight( value:Number ):void { if ( value < 0 ) value = 0; _ellipseHeight = value; update(); } public function get ellipseHeight():Number { return _ellipseHeight; }We have not only granted the read/write access that is needed by outside code, but we have also made sure that the
ellipseWidthandellipseHeightare never below zero. There is also an immediate reaction to these properties being set or changed.Note: If you do not want to correct any values for the
ellipseWidthorellispeHeightproperties, you do not have to. There may be a time when you’d want theellipseWidthorellipseHeightto be less than zero.Now that we have our first shape complete. Let’s take a quick look at what our shape looks like when we first create it. You will need to create a document class and create a new instance of the
RectangleShapeobject within it. Add the newRectangleShapeto thestage. Here’s my code:Let’s look at the result.
This is the what our shape looks like by default. Feel free to play around with it a bit so that you get a good feel for it. Try changing the colors or modifying the size.
Step 9: Drawing Circles
We are going to create a sort of bubbling effect for the over state of our button. This means that we will need bubbles. Create the
EclipseShapeclass. Make sure that it extends theEditableShapeclass.package { public class EllipseShape extends EditableShape { public function EllipseShape() { super(); } protected override function draw():void { getGraphics().drawEllipse( 0, 0, width, height ); } } }Once again the power of inheritance has allowed us to recycle old code instead of writing the same code over and over again. This class only contains inherited properties and methods. We had to override the
drawmethod in order to create the actual eclipse shape though. ThedrawEclipsemethod uses the inheritedwidthandheightproperties to draw our shape. And once again we access thegraphicsobject using the protectedgetGraphicsmethod.We now have all of the shapes we need to draw our button.
Step 10: Starting the Button
Create a new class that extends
flash.display.Sprite. You can call the class whatever you’d like. I’m going to use the nameMyButton. Import the following classes.import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFormat; import flash.filters.BlurFilter; import flash.filters.GlowFilter; import flash.events.MouseEvent; import flash.filters.DropShadowFilter; import flash.system.System; import flash.filters.BevelFilter; import com.greensock.TweenMax; public class MyButton extends Sprite {Define the following properties.
Just like before, any property that starts with an underscore is either expected to cause an immediate reaction when set or the property will be read-only.
The
_widthproperty will be used to override thewidthproperty of the base class and we redraw the shape everytime thewidthproperty is set.The
activeproperty is going to be used to swap the toggle states of the button later.You will notice several properties that are
RectangleShapes, several properties that areTextFields, and aSprite. We will see the role each of these properties play later.We’ll be storing all of our bubbles in the
circlesArray. To improve performance we will need to pause various tweens. In order to do this we will need to store these tweens in an array so that they can be accessed whenever we need them.Last we have the
circleBlurproperty which is aBlurFilterthat is to be applied to each bubble, and we have the_toggleproperty which affects the behavior of the button.Create the following public constants.
The height of our button will not change so it’s appropriate to store the value of the button’s height in a class constant. The size of the button’s ellipse is relative to the button’s height. If the button becomes too large or too small, our button may lose the Pill Button look that we are going for. Also the number of bubbles is predetermined so we store this value in a class constant as well.
Step 11: The Base
When ever I am drawing graphics for a component like a text field or a button, I always, always, always… start with a base shape. This is a shape that acts as the foundation of the shape. It is the backbone of the component. How the graphic is structured is entirely dependent on how I construct the base.
You will notice a property that we have defined called
toggleBase. I am not talking about this property. I am talking about thebaseproperty itself. This shape will define the outline of the button. Let’s create this property and initialize its values now. Add the following code to the constructor of the class that you have defined as your button.public function MyButton() { super(); buttonMode = true; mouseChildren = false; _width = 100; base = new RectangleShape(); base.height = BUTTON_HEIGHT; base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT; base.fillGradientRotation = 90; base.fillColors = [ 0xAAAAAA, 0x444444 ]; base.lineColors = [ 0x222222 ]; base.lineAlphas = [ .5 ]; base.lineRatios = [ 1 ]; addChild( base ); }The default
fillGradientRotationis 0. This value is in degrees but is converted into radians when passed into the gradient box(Matrix). Along with initializing the button’s size, we have to set the rotation of the gradient. In this case we want this value to be 90 degrees. We also set thefillColors(as anArray), and thelineColors.It is important to remember that the default value for these properties is an
Arraythat has alengthof two. All of theArrayproperties(lineColor,lineAlpha, andlineRatios) must be the samelength. If they aren’t, the graphics will not draw. This is why we set thelineAlphasandlineRatiosproperties.If you test the Flash movie, you should get this.
This is the overrall shape we are going for, “The Gel Pill”. Let’s make it shine a little bit.
Step 12: The Reflection
We’ve got the main structure of the button down already but now we need to add the small thing that I love to preach about. We need lots and lots of depth. We can start by adding the reflection. When light hits a glossy surface, it reflects off of the surface. Instantiate and initialize the
ref(short for reflection) property.We have turned the
alphaproperty down a little bit so the the reflection isn’t so intense but we have basically followed the same steps to draw therefshape as we did with thebaseshape. And of course we have sized and positioned the reflection relative to the size of the button’sheight. Let’s see what we have now.Not too bad. Still needs a lot more depth though. We’ll add some more later.
Step 13: The Inner Light
Sometimes when you are creating art, you notice that it’s those tiny little details that make the biggest difference in the appearance of your work. The
lightproperty will add the tiny detail into our button that makes all of the difference. This property will also be used a little differently in our button’s over and out states, but for now it will make our button appear to have a semi-transparent surface.Add the following code to your button’s constructor method.
The constructor should now look like this:
public function MyButton() { super(); buttonMode = true; mouseChildren = false; _width = 100; base = new RectangleShape(); base.height = BUTTON_HEIGHT; base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT; base.fillGradientRotation = 90; base.fillColors = [ 0xAAAAAA, 0x444444 ]; base.lineColors = [ 0x222222 ]; base.lineAlphas = [ .5 ]; base.lineRatios = [ 1 ]; ref = new RectangleShape(); ref.fillGradientRotation = 90; ref.ellipseHeight = ref.ellipseWidth = ref.height = BUTTON_HEIGHT / 2; ref.fillColors = [ 0xFFFFFF, 0xFFFFFF ]; ref.fillAlphas = [ 1, 0 ]; ref.lineColors = [ 0 ]; ref.lineAlphas = [ 0 ]; ref.lineRatios = [ 0 ]; ref.alpha = .5; ref.x = ref.ellipseHeight / 2; ref.width = _width - ref.height; light = new RectangleShape(); light.copy( ref ); light.x = ref.x; light.y = BUTTON_HEIGHT - light.height; light.width = _width - light.height; light.fillColors = [ 0xFFFFFF, 0xDDDDDD ]; light.filters = [ new GlowFilter( 0xFFFFFF, .8, 10, 10, 2, 3 ), new BlurFilter( 6, 6, 3 ) ]; light.alpha = lightAlpha = .3; addChild( base ); addChild( light ); addChild( ref ); }We have added a
GlowFilterand aBlurFilterto this object so that we can achieve a specific look. We don’t want the shape to look like a shape at all but empty space within the button. Notice that we have used thecopymethod to mimic the properties of therefobject. There’s no need to write the same lines of code for thelightobject. Just mimic therefproperty and make just a few modifications and you have yourself a new shape.See how we are beginning to see more depth as our shape is being constructed? The pill doesn’t look fully opaque and it isn’t very transparent either. We want an “Out of This World” kind of look so just in between is perfect.
Step 14: Simple Text
Now that we have a good foundation down on the table, it’s appropriate to begin adding the first
TextFieldto the button. Initialize thetxtproperty. And create a mask that is the same size and ellipse as thebaseso that the text doesn’t appear outside of the button.public function TutButton() { super(); buttonMode = true; mouseChildren = false; _width = 100; base = new RectangleShape(); base.height = BUTTON_HEIGHT; base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT; base.fillGradientRotation = 90; base.fillColors = [ 0xAAAAAA, 0x444444 ]; base.lineColors = [ 0x222222 ]; base.lineAlphas = [ .5 ]; base.lineRatios = [ 1 ]; ref = new RectangleShape(); ref.fillGradientRotation = 90; ref.ellipseHeight = ref.ellipseWidth = ref.height = BUTTON_HEIGHT / 2; ref.fillColors = [ 0xFFFFFF, 0xFFFFFF ]; ref.fillAlphas = [ 1, 0 ]; ref.lineColors = [ 0 ]; ref.lineAlphas = [ 0 ]; ref.lineRatios = [ 0 ]; ref.alpha = .5; ref.x = ref.ellipseHeight / 2; ref.width = _width - ref.height; light = new RectangleShape(); light.copy( ref ); light.x = ref.x; light.y = BUTTON_HEIGHT - light.height; light.width = _width - light.height; light.fillColors = [ 0xFFFFFF, 0xDDDDDD ]; light.filters = [ new GlowFilter( 0xFFFFFF, .8, 10, 10, 2, 3 ), new BlurFilter( 6, 6, 3 ) ]; light.alpha = lightAlpha = .3; txt = new TextField(); txt.selectable = false; txt.type = "dynamic"; txt.wordWrap = false; txt.multiline = false; txt.autoSize = "left"; txt.defaultTextFormat = new TextFormat( null, 27 ); txt.textColor = 0x333333; txt.text = "label"; txt.height = BUTTON_HEIGHT; txtMask = new RectangleShape(); txtMask.copy( base ); txtMask.filters = []; txt.mask = txtMask; addChild( base ); addChild( light ); addChild( ref ); addChild( txt ); addChild( txtMask ); }We have once again utilized the
copymethod by creating a copy of the base object. We could have very well have used theclonemethod also. The result would be the same. Also note that we have set thefiltersarray of thetxtMaskobject to a blank array. This is because when thecopymethod used thefiltersproperty is also inherited. We have just simply overriden this property. Let’s see where we’re at.We will position all of the
TextFieldslater on. For now ourtxtwill just have to hang out on the left side of our button.Step 15: Setting Up for the Over State
Our button will illuminate and display a sort of science fiction like animation sequence with bubbles when the mouse hovers over the button. Of course the entire sequence will be accomplished 100% with code.
But before we can do this we need to create the elements of the sequence. The first is the background object, or the
glowproperty, of the over state. Let’s add the glow object now. Add the following code to the constructor method:We will also need a container for the bubbles to be displayed in and a mask for that container so that the bubbles don’t float out of the button.
public function MyButton() { super(); buttonMode = true; mouseChildren = false; _width = 100; base = new RectangleShape(); base.height = BUTTON_HEIGHT; base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT; base.fillGradientRotation = 90; base.fillColors = [ 0xAAAAAA, 0x444444 ]; base.lineColors = [ 0x222222 ]; base.lineAlphas = [ .5 ]; base.lineRatios = [ 1 ]; ref = new RectangleShape(); ref.fillGradientRotation = 90; ref.ellipseHeight = ref.ellipseWidth = ref.height = BUTTON_HEIGHT / 2; ref.fillColors = [ 0xFFFFFF, 0xFFFFFF ]; ref.fillAlphas = [ 1, 0 ]; ref.lineColors = [ 0 ]; ref.lineAlphas = [ 0 ]; ref.lineRatios = [ 0 ]; ref.alpha = .5; ref.x = ref.ellipseHeight / 2; ref.width = _width - ref.height; light = new RectangleShape(); light.copy( ref ); light.x = ref.x; light.y = BUTTON_HEIGHT - light.height; light.width = _width - light.height; light.fillColors = [ 0xFFFFFF, 0xDDDDDD ]; light.filters = [ new GlowFilter( 0xFFFFFF, .8, 10, 10, 2, 3 ), new BlurFilter( 6, 6, 3 ) ]; light.alpha = lightAlpha = .3; txt = new TextField(); txt.selectable = false; txt.type = "dynamic"; txt.wordWrap = false; txt.multiline = false; txt.autoSize = "left"; txt.defaultTextFormat = new TextFormat( null, 27 ); txt.textColor = 0x333333; txt.text = "label"; txt.height = BUTTON_HEIGHT; txtMask = new RectangleShape(); txtMask.copy( base ); txtMask.filters = []; txt.mask = txtMask; glow = new RectangleShape(); glow.copy( base ); glow.fillColors = [ 0xFFFFFF ]; glow.fillAlphas = [ 1 ]; glow.fillRatios = [ 0 ]; glow.lineColors = [ 0 ]; glow.lineAlphas = [ 0 ]; glow.lineRatios = [ 0 ]; glow.filters = [ new GlowFilter( 0x00BCE9, 1, 10, 10, 2, 3, false, false ), new GlowFilter( 0x00BCE9, 1, 20, 20, 2, 3, true ) ]; effectContainer = new Sprite(); effectContainer.cacheAsBitmap = true; effectMask = new RectangleShape(); effectMask.copy( base ); effectMask.filters = []; effectContainer.mask = effectMask; addChild( base ); addChild( light ); addChild( ref ); addChild( txt ); addChild( txtMask ); addChild( glow ); addChild( effectContainer ); addChild( effectMask ); }Step 16: Bubbles
Now we are going to blow a few bubbles. Add the following lines of code to the constructor method:
The
circleBluris aBlurFilterthat will be applied to all bubbles. This will give each bubble the specific science like look that we’re going for. We also need to create the arrays that will store all of the bubbles and the tweens that will animate each bubble.We need a method that will generate all of the bubbles we need.
private function createEffect():void { for ( var i:int = 0; i < NUM_OF_CIRCLES; i++ ) { var circ:EllipseShape = new EllipseShape(); resetCircle( circ ); } stopEffect(); }Use a loop to iterate a block of code that creates a new
EllipseShapeobject, or in this case a bubble, and run theresetCirclemethod on the bubble. We haven’t created this method yet but it re-initializes the parameter bubble. In the loop, the method simply initializes each bubble since they haven’t been initialized to begin with. Finally thestopEffectmethod is called. This method pauses the bubble’s animation sequence. We’ll write this method in a bit.For now, create the following methods. (Also now is a good time to import
com.greensock.TweenMax).private static function randomNumber( min:int = 0, max:int = 10 ):int { return Math.round( Math.random() * ( max - min ) + min ); } private function resetCircle( circle:EllipseShape ):void { circle.width = circle.height = randomNumber( 10, 20 ); effectContainer.addChild( circle ); circle.cacheAsBitmap = true; circle.x = randomNumber( 0, _width ); circle.y = BUTTON_HEIGHT; circle.filters = [ circleBlur ]; if ( circles.indexOf( circle ) == -1 ) { circles.push( circle ); circle.fillColors = [ 0x00BCE9 ]; circle.fillAlphas = [ 1 ]; circle.fillRatios = [ 1 ]; circle.lineAlphas = [ 0 ]; circle.lineRatios = [ 0 ]; circle.lineColors = [ 0 ]; circle.alpha = .6; } var tween:TweenMax = TweenMax.to( circle, randomNumber( 1, 6 ) * .5, { y:-BUTTON_HEIGHT, onComplete:doComplete } ); tweens.push( tween ); function doComplete():void { resetCircle( circle ); removeTween( tween ); } } private function removeTween( tween:TweenMax ):void { var a:Array = []; for each( var t:TweenMax in tweens ) { if ( t != tween ) a.push( t ); } tweens = null; System.gc(); tweens = a; }The
randomNumbermethod returns a random number based on the parameters passed into the method. The first parameter specifies a the minimum value that can be generated and the second parameter specifies the maximum value that can be generated. We need this method to generate a random location and size for a bubble when we reset a bubble.This brings me to the
resetCirclemethod. I said before that this method re-initializes the specified bubble. To be more specific, the method recycles a bubble and makes it appear like a brand new bubble that has just been created when in reality it is just another old bubble.The
tomethod from theTweenMaxclass returns the tween that the bubble contains. We push this bubble into thetweensarray. The bubble starts below the button and rises above the button. When the tween is finished it is no longer needed. The tween needs to be removed (using theremoveTweenmethod) and the bubble needs to be reset or recycled. The anonymous functiondoCompletedoes just that.Before we can see what our bubbles look like we need to create two methods that will control playback of the bubbles’ animation sequence. Besides if we added a call to the
createEffectmethod in the constructor and tested our movie now, the bubbles would be all there but we wouldn’t see them because they would be beneath the button. Remember that each bubble is added to theeffectContainerin theresetCirclemethod and that theeffectContaineris masked so that the bubbles aren’t seen outside of the button.(Plus, if you tested the movie now you’d just get a compile-time error for not implementing the
stopEffectmethod that is called when we create our bubbles.)private function playEffect():void { for each( var tween:TweenMax in tweens ) { tween.play(); } } private function stopEffect():void { for each( var tween:TweenMax in tweens ) { tween.pause(); } }Loop through the
tweensarray to play each tween and to pause each tween when needed. Very simple.Now add a call to the
createEffectmethod and theplayEffectmethod within the constructor. Test your movie. You should now have a beautiful display of bubbles. When you are finished remove theplayEffectmethod from the constructor that you just added but keep thecreateEffectmethod.Step 17: Glowing Text
Let’s add the glowing
TextFieldto the button. We’re using a seperateTextField,effectTxtinstead oftxt, so that we can do a fading transition into the next state. We need an additionalTextFieldfor this. We also need another mask for ourTextFieldjust as we did with the first one.public function MyButton() { super(); buttonMode = true; mouseChildren = false; _width = 100; base = new RectangleShape(); base.height = BUTTON_HEIGHT; base.ellipseHeight = base.ellipseWidth = BUTTON_HEIGHT; base.fillGradientRotation = 90; base.fillColors = [ 0xAAAAAA, 0x444444 ]; base.lineColors = [ 0x222222 ]; base.lineAlphas = [ .5 ]; base.lineRatios = [ 1 ]; ref = new RectangleShape(); ref.fillGradientRotation = 90; ref.ellipseHeight = ref.ellipseWidth = ref.height = BUTTON_HEIGHT / 2; ref.fillColors = [ 0xFFFFFF, 0xFFFFFF ]; ref.fillAlphas = [ 1, 0 ]; ref.lineColors = [ 0 ]; ref.lineAlphas = [ 0 ]; ref.lineRatios = [ 0 ]; ref.alpha = .5; ref.x = ref.ellipseHeight / 2; ref.width = _width - ref.height; light = new RectangleShape(); light.copy( ref ); light.x = ref.x; light.y = BUTTON_HEIGHT - light.height; light.width = _width - light.height; light.fillColors = [ 0xFFFFFF, 0xDDDDDD ]; light.filters = [ new GlowFilter( 0xFFFFFF, .8, 10, 10, 2, 3 ), new BlurFilter( 6, 6, 3 ) ]; light.alpha = lightAlpha = .3; txt = new TextField(); txt.selectable = false; txt.type = "dynamic"; txt.wordWrap = false; txt.multiline = false; txt.autoSize = "left"; txt.defaultTextFormat = new TextFormat( null, 27 ); txt.textColor = 0x333333; txt.text = "label"; txt.height = BUTTON_HEIGHT; txtMask = new RectangleShape(); txtMask.copy( base ); txtMask.filters = []; txt.mask = txtMask; glow = new RectangleShape(); glow.copy( base ); glow.fillColors = [ 0xFFFFFF ]; glow.fillAlphas = [ 1 ]; glow.fillRatios = [ 0 ]; glow.lineColors = [ 0 ]; glow.lineAlphas = [ 0 ]; glow.lineRatios = [ 0 ]; glow.filters = [ new GlowFilter( 0x00BCE9, 1, 10, 10, 2, 3, false, false ), new GlowFilter( 0x00BCE9, 1, 20, 20, 2, 3, true ) ]; effectContainer = new Sprite(); effectContainer.cacheAsBitmap = true; effectMask = new RectangleShape(); effectMask.copy( base ); effectMask.filters = []; effectContainer.mask = effectMask; effectTxt = new TextField(); effectTxt.selectable = false; effectTxt.type = "dynamic"; effectTxt.wordWrap = false; effectTxt.multiline = false; effectTxt.autoSize = "left"; effectTxt.defaultTextFormat = new TextFormat( null, 27 ); effectTxt.textColor = 0x00BCE9; effectTxt.text = "label"; effectTxt.filters = [ new GlowFilter( 0x00BCE9, 1, 16, 16, 2, 3 ) ]; effectTxtMask = new RectangleShape(); effectTxtMask.copy( base ); effectTxtMask.filters = []; effectTxt.mask = effectTxtMask; circleBlur = new BlurFilter( 5, 5, 3 ); circles = []; tweens = []; addChild( base ); addChild( light ); addChild( ref ); addChild( txt ); addChild( txtMask ); addChild( glow ); addChild( effectContainer ); addChild( effectMask ); addChild( effectTxt ); addChild( effectTxtMask ); createEffect(); }Just another
TextFieldwith a glow filter. Again we will position the text fields later when we create theupdatemethod. Now you should have something that looks like this.Now that all of the elements for the over state have been generated, we only have one more task to complete. Next we position the contents of the button.
Step 18: Positioning
Encapsulated in our button class is a function that positions the button’s contents based on the current value of the
_widthproperty. The method I am referring to is theupdatemethod. Call theupdatemethod on the last line of code of the constructor method. Then create theupdatemethod.addChild( effectTxt ); addChild( effectTxtMask ); createEffect(); update(); } protected function update():void { base.width = _width; glow.width = _width; ref.width = _width - ref.height; light.width = _width - light.height; txt.x = ( _width - txt.width ) / 2; txt.y = ( BUTTON_HEIGHT - txt.height ) / 2; effectTxt.x = txt.x; effectTxt.y = txt.y; effectMask.width = _width; txtMask.width = _width; effectTxtMask.width = _width; }Resize particalar objects and reposition others. Test the movie:
We now have centered text. Set the
alphaproperty of all of the effect base objects(effectContainer,effectTxtandglow) to zero. Re-test the movie and now you should see the original state of the button.As you may know, the Tuts+ network is accompanied by an online educational members-only site called Tuts+ Premium. We’re very excited to announce that Tuts+ Premium has received a huge upgrade, including a new library of courses, 27 top-selling educational eBooks, member forums, and a completely redesigned UI. You can check out all the changes at tutsplus.com (I recommend taking the tour) or read on to learn about what you will get from Tuts+ Premium.
Library of Courses
You’ve seen Sessions and series at Activetuts+ and on the other Tuts+ sites; the new Tuts+ Premium introduces courses.
Would you like to learn how to design your first website in 30 days? Or get to grips with the languages of the web, HTML & CSS? Perhaps you’re already familiar with that, and you’re more interested in understanding the essentials of CSS3, or maybe you’d like to study something completely unrelated to web development, like using Photoshop to create concept art. Our new Tuts+ Premium courses will show you how. We’ll be adding new courses several times per month.
45 Exclusive Flash and Browser App Tutorials
Tuts+ Premium includes 45 (and counting) in-depth tutorials on browser app and game development, covering the same kinds of topics you’re used to from Activetuts+, but in much more detail:
…and there’s plenty more, on animation, game engine development, 3D user interfaces, and special effects.
Every tutorial now has a free preview, so you can get a taste of what’s in store before you buy.
My Thoughts
Earlier this year, I got to manage the awesome team that migrated the tutorials from the old Tuts+ Premium system to this new one, so I’m very familiar with both systems and the content. I think it’s fair to say that the old system… had some flaws. It could be difficult to find tutorials unless you were looking for one specifically, and having to download and extract a ZIP file (which in some cases could be over 1GB) just to access the tut was a huge pain.
The new Premium is better in so many ways. You can filter tutorials by topic, by difficulty, and by completion time – and search across the entire library – so it’s much easier to browse for and discover content. Reading tutorials and watching screencasts is much easier too; they’re all accessible in your browser, with an interface similar to the regular Tuts+ sites.
And I must confess: before doing the migration, I didn’t realise how much great content there is on Premium. I don’t just mean the Flash and RIA Premium tutorials – although that selection is excellent, and becoming even better as we add more and more (you should see what we have planned!) – I mean all the content from the other Tuts+ sites that I wasn’t even aware of.
For example, Aetuts+ has some excellent videos about animation, like The 12 Basic Principles of Animation, that are relevant to anyone doing animation, game design, or even flashy UI design. Cgtuts+ has some incredible series on modeling 3D objects, like this next-gen armoured car, which will be useful to anyone creating assets for a Unity project. And if you want to learn JavaScript, Nettuts+ has you covered.
I’ve highlighted tutorials that are related to the topics we cover on Activetuts+, but if you want to branch out and learn something new, there’s plenty of opportunity to do that as well. Illustration, photography, digital audio, mobile app development… oh, and you have to check out Ed Lopez’s amazing digital painting tutorials.
The new extras – forums, ebooks, courses – are all great, but to me, the appeal of Tuts+ Premium lies in its tutorials. Now, at last, those tutorials are easy to find and easy to read (or watch, for screencasts). That alone is worth the $19 a month entry price.
Top-selling Educational eBooks
Tuts+ Premium memberships now include 27 top-selling eBooks worth more than $440, including:
$29Free for Tuts+ Premium members$24Free for Tuts+ Premium members$29Free for Tuts+ Premium members$8Free for Tuts+ Premium members$39Free for Tuts+ Premium members$9Free for Tuts+ Premium membersBeautiful Redesign
The new Tuts+ Premium was designed by Orman Clark and Envato designer Jacob Zinman-Jeanes. And, thank you to Simplifilm, who make a mean product demo video!
Take a Tour of Tuts+ Premium
Twice a month, we revisit some of our readers’ favorite posts from throughout the history of Activetuts+. This tutorial was first published in Dec, 2010.
In this tutorial you’ll build an extreme particle system whilst learning how to squeeze more efficient goodness out of the Flash Player than you ever thought possible!
Final Result
Here are a couple of examples of what we’ll be working towards:
EPILEPSY WARNING:
Please don’t view this demo if you are likely to suffer from epileptic attacks or loss of consciousness, particularly when looking at some types of strong flashing lights, rapid succession of images, simple geometric shapes, flashes or explosions.
Introduction: What Will You Learn?
Many users, from beginners to advanced, can still be seen using less than efficient Actionscript 3.0. In all likelyhood, this is because the efficient ways are made to sound a bit more difficult and aimed at highly advanced users. This tutorial will show you that these methods can be used by everyone, and they’re more useful than you may think!
The aim of this tutorial is to let you tackle those tasks that require working with a lot of data, very fast, with ease.
Helpful Hint: This tutorial will feature a lot of coding so I recommend using a more user friendly coding interface. I recommend FlashDevelop, it has some of the best code hinting around and best of all, it’s completely free! But I’m sure most of you will just copy and paste if do anything at all
Step 1 Check out That FPS!
At the heart of making all things efficient in the Flash Player is the FPS (Frames Per Second) of your SWF. The proffered target of your SWF can be set in the Flash Professional interface or, a new feature in Actionscript 3.0, you can change the FPS of the stage at runtime.
However, this will only ever get and set the target FPS (what the Flash Player will attempt to play at), which makes it pretty useless for preformance testing. You might think that the lovely folks over at Adobe would made a nice neat way to find you real FPS but, nope. You’ve got to do the math for yourself. Let’s take a look.
FPS can be defined as the time difference between the current frame and the last.
So all we need is some way to track the time difference from this frame to the previous frame of the SWF. This uses the document class feature. If you are unsure how to use this, check out this Quick Tip on how to use a document class.
package { //imports import flash.events.Event; import flash.utils.getTimer; import flash.display.MovieClip; public class FPSCalculator extends MovieClip { //variable to hold the current time private var currentTime:int = 0; public function FPSCalculator() { //add the enter frame listener, this is fired when the SWF updates to a new frame stage.addEventListener(Event.ENTER_FRAME, onFrameLoop); } private function onFrameLoop (evt:Event):void{ //for the sanity of the fellow developers, try to put each task into a seperate function. //this makes it infinitely easier to read for them and yourself on a large project or when you come back to and old one //since the getTimer() function returns the played time in milliseconds and we want FPSecond, we divide it into 1000 var fps:Number = (1000 / timeDifference); trace(fps); } //this is a get function so it can be referenced just like a variable, without the brackets on the end like a normal function private function get timeDifference ():int{ //the getTimer() function returns the total played time of the SWF in milliseconds var totalPlayedTime:int = getTimer(); //The difference in time from the previous frame to this frame will to calculated here var timeDifference:int = (totalPlayedTime - currentTime); //The currentTime is set to the total played time so it is ready for the next frame currentTime = getTimer(); //return the difference in time return timeDifference } } }We will use this function as the benchmark for comparing the efficiency of different methods from here on. As you might have noticed, we use the function to calculate the time difference, not the FPS. This is because tracing the time difference is actually much more useful and easier to read when we get to the speed tests. Calculating the FPS only becomes useful when we are putting everything together at the end.
Tip #1 Arrays vs. Vectors
Have you ever needed to store a whole bunch of numbers, strings or objects in a list? Of course you have! But the question is – have you been doing it right?
If the first thing you think think of when making a list in AS3 is an Array, then this tip is for you. A Vector is exactly the same as an ordinary Array except for one fact, it is a typed Array. This means that you can only populate it with one type of item. For instance, you can put a number and a string in the same Array, but not in a Vector.
Let’s take a look at the only difference between an Array and a Vector, declaring the Vector.
private function Arrayvs.VectorDifferences ():void{ //delare the array and the vector, this is the only difference between the two var myArray:Array = new Array(); var myVector:Vector.<String> = new Vector.<String>; //populate them in the same way myArray.push("this", "is", "an", "Array"); myVector.push("this", "is", "a", "Vector"); //call elements and length in the same way trace( myArray[myArray.length - 1] ); //Array trace( myVector[myVector.length - 1] ); //Vector //The following creates an error - "Access of possibly undefined property x through a reference with static type String." trace( myVector[myVector.length - 1].x ); //The Flash Player casts the movieclip as a string and a string does not have an 'x' value. }As seen above, declaring the vector is the only difference between the two types. To have the Vector hold another object, just replace
Stringwith your object. For holding MovieClips for example:How does this make it more useful? If all the elements in a Vector are the same type then the Flash Player can zip through them much much quicker because it knows what’s coming up and doesn’t have to test the next element every time (this is true even when declaring a variable type, so never leave it out!).
How big is the difference? Surprisingly big actually! Let’s take a look at our first speed test.
Speed Test #1 Array vs. Vector
package { //imports import flash.utils.getTimer; import flash.display.MovieClip; public class Arrayvs.Vector extends MovieClip { private var currentTime:int = 0; //we want to read and write into the array and vector 10,000,000 times. //this will provide a good indication of the speed difference //dont worry, it wont crash you computer but the FLash Player will pause for about 3 to 5 seconds private var n:int = 10000000 //Declare the Array and Vector private var myArray:Array = new Array(); private var myVector:Vector.<int> = new Vector.<int>; public function Arrayvs.Vector() { //time test for writing to the array and vector write(); trace("----"); //time test for reading from the array and vector read(); } private function write ():void{ trace("Writing Times"); timeDifference //for n times, push i into the array for(var i:int = 0; i < n; i++){ myArray.push(i); } //trace the time taken trace("Array: " + timeDifference + "ms"); //for n times push j into the vector for(var j:int = 0; j < n; j++){ myVector.push(j); } //trace the time taken trace("Vector: " + timeDifference + "ms"); } private function read():void{ var num:int = 0; trace("Reading Times"); timeDifference //for n times, set num to the corresponding array value for(var i:int = 0; i < n; i++){ num = myArray[i]; } //trace the time taken trace("Array: "+ timeDifference + "ms"); //for n times, set num to the corresponding vector value for(var j:int = 0; j < n; j++){ num = myVector[j]; } //trace the time taken trace("Vector: " + timeDifference + "ms"); } private function get timeDifference ():int{ var totalPlayedTime:int = getTimer(); var timeDifference:int = (totalPlayedTime - currentTime); currentTime = getTimer(); return timeDifference } private function Arrayvs.VectorDifferences ():void{ //delare the array and the vector, this is the only difference between the two var myArray:Array = new Array(); var myVector:Vector.<String> = new Vector.<String>; //populate them in the same way myArray.push("this", "is", "an", "Array"); myVector.push("this", "is", "a", "Vector"); //call elements and length in the same way trace( myArray[myArray.length - 1] ); //Array trace( myVector[myVector.length - 1] ); //Vector //pushing in a non-string value myVector.push(new MovieClip()); //The following creates an error - "Access of possibly undefined property x through a reference with static type String." //trace( myVector[myVector.length - 1].x ); //The Flash Player casts the movieclip as a string and a string does not have an 'x' value. } } }This outputs the following:
You shouldn’t get exactly the same results as what I have here of course, you will almost never get the same set of values yourself even as this depends on how much effort your CPU can push into this at runtime.
The first pair of values comes from writing into the array and vector respectively. We can see that the Vector shaved off almost 0.6 seconds, quiet a substancial amount if we need to do something similar 24 (standard FPS for flash movies) times a second. After all, 1/24th of a second is just over 0.04 seconds.
An even bigger percentage difference can be found when you are reading from an Array vs. from a Vector, and this is luckily what you will need to do most of the time every frame.
Hopefully, after reading this section you should be comfortable with using Vectors in your projects for that extra kick of efficiency.
Tip #2 Event Listeners
Like all things in Flash, there’s more than one way to solve a problem, Event Listeners are no exception. In this tutorial we will look two methods of attaching the
Event.ENTER_FRAMElistener to your particles.Method #1 One Listener per Particle
The idea behind this approach is that you attach a listener to each of your particles and direct them to a set function. This isn’t ideal for what we have in mind but remember a particle, in terms of programming, doesn’t have to be a single dot. For instance, this method might be preferred when going between Flash based webpages or objects that are treated differently in the listener function. Let’s take a look.
private function createPages():void{ //create 10 webpages for(var i:int = 0; i < 10; i++){ //create a new webpage as a movieclip var webpage:MovieClip = new MovieClip(); //add the listener webpage.addEventListener(Event.ENTER_FRAME, onWebpageLoop); } } private function onWebpageLoop (evt:Event):void{ //all webpages call this function every frame //evt.target is the webpage }Method #2 One Listener to Rule Them All…
The second method is the one that we will be using and the preferred method when dealing with a lot of similar objects. The idea here is to attach a single listener to only one object – usually the stage – which then loops through each of the particles and tells each one what to do on each frame. This method is slightly more complex as we need some way to reference the particles, so we put them into a Vector. Let’s take a look.
private function createPages2():void{ //create 10 webpages for(var i:int = 0; i < 10; i++){ //add a movieclip to the webpagesHolder //reducing the number of variables and steps used increases the speed webpagesHolder.push(new MovieClip()); } //add the listener stage.addEventListener(Event.ENTER_FRAME, onStageLoop); } private function onStageLoop (evt:Event):void{ //called only once when the stage changes frame for(var i:int = 0; i < 10; i++){ //webpageHolder[i] is the webpage } }Speed Test #2 Methods of Using Event Listeners
Unfortunately there is no hard and fast way to accurately check the difference in speed between these two methods because of the inconsistencies of the calling process while using method one (at least not an accurate method that won’t require its own complete tutorial!)
Take my word for it, using method two is far better for use in particle systems because of two major factors:
After reading this section you should now know how to comfortably keep track of and reference many particles. Remember, method one for objects that should treat their listener function differently and method two for many objects that all should be treated in exactly the same or a very similar way in their listener function.
Tip #3 Building Your Particle
The next step towards our complete particle system is building the right particle for ourselves. It might so happen that you’ll need a full MovieClip for each particle, to make use of frames and such, but for us, a MovieClip is a huge overkill. All our particle is is a placeholder for a bunch of values that need to be kept together and relate to each other. This allows us to drastically reduce the size of the class used.
The following is the basic class we can use for our particle.
package { //imports import flash.geom.Vector3D; //notice that the class extends nothing because there is no need public class Particle { //define the Vector3D objects to hold the position and velocity values private var pos:Vector3D = new Vector3D(0, 0, 0); private var vel:Vector3D = new Vector3D(0, 0, 0); public function Particle(stageRect:Rectangle) { } public function update ():void{ //update the position according to the velocity in that direction pos.x += vel.x; pos.y += vel.y; } //the getter methods that will be used to read the position of the particle public function get x ():Number{ return pos.x } public function get y ():Number{ return pos.y } } }Notice that it does not have any base class (i.e. it does not
extendanything) and therefore it is ‘born’ without properties that you might use regularly, for instance the ‘x’ and ‘y’ values. To correct this, we use build our own getter methods to read these values. These values are then passed into a Vector3D object. A Vector3D object is basically a Vector which holds three variables and an optional fourth variable. The difference is that you can reference these values as ‘x’, ‘y’, ‘z’ and ‘w’ respectively, The ‘w’, which is optional, could be used to old a rotation value for example. This makes this type of holder perfect for what we need.(We could even create these properties as public Number variables within the class directly, without using any Vector3D objects at all… but let’s stick with what we’ve got.)
But how exactly does creating our own class help? Let’s make a quick memory test to find out! Remember to save this, your document class and the Particle class in the same destination.
package { //imports import flash.sampler.getSize; import flash.display.MovieClip; public class BuildingYourParticle extends MovieClip { public function BuildingYourParticle() { //simulate 100,000 of the respective object var n:int = 100000 //define the object var p:Particle = new Particle(); var m:MovieClip = new MovieClip(); //use the getSize() method the find the memory used for n of m and p. Convert them to megabyte format var pSizeTotal:Number = (getSize(p) * n) / (1024 * 1024); var mSizeTotal:Number = (getSize(m) * n) / (1024 * 1024); //trace the respective sizes to two decimal places trace("Particle Memory: " + pSizeTotal.toFixed(2) + "mb"); trace("MovieClip Memory: " + mSizeTotal.toFixed(2) + "mb"); } } }This Outputs something like the following:
Speed Test #3 Our Particle vs. Movieclips
As you can see, there is a massive difference between the size of our class and the movieclip. Freeing up all this space allows more space on your RAM and so it allows the whole Flash Player to run quiet a bit faster. How much faster? Let’s take a look at that too! Much of this code is the same as our Array vs. Vector Speed test.
package { //imports import flash.utils.getTimer; import flash.display.MovieClip; public class ParticleSpeedTest extends MovieClip { private var n:int = 100000 private var currentTime:int = 0; //Declare the respective holders private var particleHolder:Vector.<Particle> = new Vector.<Particle>; private var movieclipHolder:Vector.<MovieClip> = new Vector.<MovieClip>; public function ParticleSpeedTest():void { //time test for writing a new particle or movieclip write(); trace("----"); //time test for reading from a particle or movieclip read(); } private function write ():void{ trace("Writing Times"); timeDifference //for n times, push a new particle into the vector for(var i:int = 0; i < n; i++){ particleHolder.push(new Particle()); } //trace the time taken trace("Particle: " + timeDifference + "ms"); //for n times push a new movieclip into the vector for(var j:int = 0; j < n; j++){ movieclipHolder.push(new MovieClip()); } //trace the time taken trace("MovieClip: " + timeDifference + "ms"); } private function read():void{ var num:int = 0; trace("Reading Times"); timeDifference //for n times, set num to the corresponding particle's 'x' value for(var i:int = 0; i < n; i++){ num = particleHolder[i].x; } //trace the time taken trace("Particle: "+ timeDifference + "ms"); //for n times, set num to the corresponding movieclip's 'x' value for(var j:int = 0; j < n; j++){ num = movieclipHolder[j].x; } //trace the time taken trace("MovieClip: " + timeDifference + "ms"); } private function get timeDifference ():int{ var totalPlayedTime:int = getTimer(); var timeDifference:int = (totalPlayedTime - currentTime); currentTime = getTimer(); return timeDifference } } }This should output something similar to the following:
As you can see from these results the real difference is in the writing times; this is because the Flash Player has to draw on more resources (the base classes of the MovieClip and each subsequent object) to complete this. In our main project this isn’t any real issue since we only need to create new Particles at the beginning but we will see an example later on when pushing new Particles in all the time becomes necessary. The difference in reading times is almost neglectable since both classes use their getters for the ‘x’ property in the same way.
After reading this section you should be comfortable with building your base class for your particle’s needs.
Tip #4 Introduction to Bitmaps
Bitmaps, and their partner in crime BitmapData, are usually some of the two most confusing steps for a beginner, mainly because what they are generally used for is higher level stuff. Here I will give a short introduction on some of the basic and most used methods regarding the Bitmap and BitmapData classes.
What are They?
In the quickest explanation possible:
Basically, the Bitmap class displays what the BitmapData class tells it to. They go hand in hand practically always.
Drawing with Bitmaps
The BitmapData class does not have a
graphicsproperty of its own, yet it remains one of the most important classes for Flash graphics! How? It draws the shapes of other classes. Let’s take a look at how to draw a simple circle using Bitmaps and BitmapData.public function SimpleCircle():void { //define the radius of the circle var radius:int = 30; //draw a circle the normal way. Notice that you do not add the shape that is to be drawn to the display list var circleShape:MovieClip = new MovieClip(); circleShape.graphics.beginFill(0x555555, 1); circleShape.graphics.drawCircle(radius, radius, radius); circleShape.graphics.endFill(); //create the bitmap and add it to the display list var bmd:BitmapData = new BitmapData(radius * 2, radius * 2, true); var bm:Bitmap = new Bitmap(bmd); stage.addChild(bm); //draw the shape bmd.draw(circleShape); }This results in a simple grey circle touching the top left corners of the stage when run. The draw() method is simply a snapshot of the movieclip at that time so in theory we could move the circleShape object around an continue to draw it to give the effect of many circles. This is how a lot of drawing within bitmaps is done.
The setPixel() Method
Because at the base of all graphics are raw pixels, this method becomes very important for creating effects using the Bitmap and BitmapData classes. It allows you to change to color of a pixel inside the BitmapData area. Heres how that’s done.
package { //imports import flash.events.Event; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.MovieClip; public class SetPixelMethod extends MovieClip { //define variables private var n:int = 50; private var w:int; private var h:int; private var bm:Bitmap private var bmd:BitmapData; public function SetPixelMethod ():void { w = stage.stageWidth; h = stage.stageHeight; //create the bitmap data the width and height of the stage that is not transparent and grey in color bmd = new BitmapData(w, h, false, 0x222222); bm = new Bitmap(bmd); addChild(bm); addEventListener(Event.ENTER_FRAME, onFrameLoop); } private function onFrameLoop (evt:Event):void { for (var i:int = 0; i < n; i++ ) { //randomly pick the x,y coordinates to set the new pixel color var px:int = Math.random() * bmd.width; var py:int = Math.random() * bmd.height; //give the pixel a random color var pc:uint = Math.random() * 0xffffff; //set the pixel at (px,py) to that color bmd.setPixel(px, py, pc); } } } }You should end up with something that looks like this, with more dots appearing on every frame:
This is the graphics style we will use for our system. Don’t worry if it looks kinda terrible right now, at the end you’ll see how we can make these look much better with a whole bunch of effects.
The lock() and unlock() methods
These methods are some of the least used but most helpful regarding bitmap. In fact I would have never known about there existence if it wasn’t for a great little site called WonderFl where members regularly boast massive particle systems, the truth is that these were the inspiration of this tutorial!
You might be hard pushed to find a similar set of methods to use together, this is how they work:
public function LockUnlockMethods ():void { //Draw a basic circle, same in steps before this var radius:int = 30; var circleShape:MovieClip = new MovieClip(); circleShape.graphics.beginFill(0x555555, 1); circleShape.graphics.drawCircle(radius, radius, radius); circleShape.graphics.endFill(); //create the bitmap/bitmapdata var bmd:BitmapData = new BitmapData(radius * 2, radius * 2, true); var bm:Bitmap = new Bitmap(bmd); stage.addChild(bm); //lock the bitmap bmd.lock(); //draw resources bmd.draw(circleShape); //unlock and update the bitmap bmd.unlock(); }In essence, you wrap the lock() and unlock() methods around the point in which your code is changing the appearance of a Bitmap. While for the situation shown above they aren’t all that useful, in a large scale system with many thousands of changes to the bitmap they speed up the process by a long shot. This is because the Bitmap and BitmapData classes like to have a lot going on at once, the more changes you can cram into a single step the better they become! These methods are great for that as they put on hold all changes made to the locked bitmap until it is unlocked, and this means it isn’t re-rendered during that time, which speeds up the process.
Clearing Your Bitmap
The BitmapData class doesn’t offer a clear cut method to create wipe its data, so here are two common ways to do so. We can reuse all the the code from learning about the setPixel method.
package { //imports import flash.geom.Point; import flash.events.Event; import flash.geom.Rectangle; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.MovieClip; import flash.filters.BlurFilter; public class ClearBitmap extends MovieClip { //define variables private var n:int = 50; private var w:int; private var h:int; private var bm:Bitmap private var bmd:BitmapData; private var clearRect:Rectangle; public function ClearBitmap ():void { w = stage.stageWidth; h = stage.stageHeight; bmd = new BitmapData(w, h, false, 0x222222); bm = new Bitmap(bmd); addChild(bm); addEventListener(Event.ENTER_FRAME, onFrameLoop); //create the bitmap data the width and height of the stage that is not transparent and grey in color clearRect = new Rectangle(0, 0, w, h); } private function onFrameLoop (evt:Event):void { //use one of these methods to clear your bitmap //bmd.fillRect(clearRect, 0x222222); bmd.applyFilter(bmd, bmd.rect, new Point(0, 0), new BlurFilter(1.1, 1.1, 2)); for (var i:int = 0; i < n; i++ ) { var px:int = Math.random() * bmd.width; var py:int = Math.random() * bmd.height; var pc:uint = Math.random() * 0xffffff; bmd.setPixel(px, py, pc); } } } }If you use the clearRect method, it simply draws a completely new grey rectangle over everything else. This isn’t as careless method as it sounds at first because remember all you are ever doing with a Bitmap is changing the color of a set of pixels; overlapping items makes no difference what so ever.
The second option blurs out the dots by merging them into the background. We will use this later to create more interesting effects. You should note that this method requires a significantly longer compute time and should be avoided if efficiency is your only concern. We will leave this until the last bit of the tutorial where we are not aiming for maximum efficiency.
Speed Test #4 Bitmap Drawing vs. MovieClips Drawing
This is our final speed test and here we will test the speed difference between drawing multiple shapes using the Bitmap classes and the MovieClip class. Here is is:
CAUTION: The regular createMovieClipCircles() method is so inefficient that you should not let it run for more than a few seconds. The Flash Player will continue to slow down until it eventually grinds to a halt.
package { //imports import flash.events.Event; import flash.display.Bitmap; import flash.utils.getTimer; import flash.display.BitmapData; import flash.display.MovieClip; public class Bitmapvs.MovieClip extends MovieClip { //define variables private var w:int; private var h:int; private var r:int = 5; private var n:int = 500; private var currentTime:int = 0; private var bm:Bitmap; private var bmd:BitmapData; private var bmShape:MovieClip = new MovieClip(); public function Bitmapvs.MovieClip ():void { w = stage.stageWidth; h = stage.stageHeight; bmd = new BitmapData(w, h, true, 0); bm = new Bitmap(bmd); addChild(bm); addEventListener(Event.ENTER_FRAME, onFrameLoop); } private function onFrameLoop (evt:Event):void { timeDifference; //use one of these functions at a time //this one is to create points using the movieclip method //createMovieClipCircles(); //this one is to create points using the bitmap method createBitmapCircles(); } private function createMovieClipCircles ():void { //this will create a new movieclip for each circle for (var i:int = 0; i < n; i ++) { var m:MovieClip = new MovieClip(); drawCircle(m); addChild(m); } //trace the fps trace(1000 / timeDifference); } private function createBitmapCircles ():void { //this will draw the same movieclip in different places over and over bmd.lock(); for (var j:int = 0; j < n; j ++) { drawCircle(bmShape); bmd.draw(bmShape); } bmd.unlock(); //trace the fps trace(1000 / timeDifference); } private function drawCircle (m:MovieClip):void { //create the same function to draw circles for both to keep it fair and organised m.graphics.clear(); m.graphics.beginFill(Math.random() * 0xffffff); m.graphics.drawCircle(Math.random() * w, Math.random() * h, r); m.graphics.endFill(); } private function get timeDifference ():int{ var totalPlayedTime:int = getTimer(); var timeDifference:int = (totalPlayedTime - currentTime); currentTime = getTimer(); return timeDifference } } }What you should find after using both methods is that:
The createMovieClipCircles method is so inefficient because it needs to add each circle to the display list which leaves the Flash Player struggling to hold the weight of them all. This is why we must use the Bitmap and BitmapData classes in our particle system.
From reading this section you should now be familiar with many of the methods we can use from the Bitmap and BitmapData classes to build our particle system. I have covered much of what we need to know to build such a system so now I think its time we dive in!
Building the System
Our system won’t be pretty, but dang it will be fast! It will be composed of two classes, the Particle class we made earlier and the controller class that keeps check of everything. Our aim is to build a system that will brush off 100k particles, laugh at 150k particles and take on 200k comfortably. Of course this depends on your system, but mine is about six years old and hasn’t blown up yet so I’m sure most of you will be okay.
First we will start with the basic Particle class, much of the code that you will see will be the same as in the steps previously described.
package { //imports - the less the better import flash.geom.Vector3D; import flash.geom.Rectangle; public class Particle { //define the position and velocity Vector3D objects private var pos:Vector3D = new Vector3D(0,0); //the velocity of the particle is between +3 and -3 for x and y private var vel:Vector3D = new Vector3D(rand(3), rand(3)); //define the bounds and default color of the particle private var bounds:Rectangle; private var color:uint = 0x555555 public function Particle(stageRect:Rectangle) { //the bounding area of the stage is passed into the constructor //we do not need to pass the entire instance of the stage as this will require more memory bounds = stageRect; //spawn the particle at a random point within the bounds pos.x = Math.random() * bounds.width; pos.y = Math.random() * bounds.height; //give a handful a different color so that we can see particles moving more easily if(Math.random() < 0.005) color = 0xFFFFFF } private function rand(n:int):Number{ //this function returns a random number between -n and n return n - (n * 2 * Math.random()) } public function update ():void{ //add the respective velocities to the position pos.x += vel.x; pos.y += vel.y; //check if the particle is outside the bounds of the rectangle checkBounds(); } private function checkBounds():void{ //this function simply checks the x,y position values and if they are //bigger or greater than the bounds reverse the respective velocity (direction) is reversed if(pos.x < 0 || pos.x > bounds.width) vel.x *= -1; if(pos.y < 0 || pos.y > bounds.height) vel.y *= -1; } //the get methods for the color and x,y values of the particle public function get c ():uint{ return color } public function get x ():Number{ return pos.x } public function get y ():Number{ return pos.y } } }In keeping with OOP traditions, our particle is as encapsulated (self-contained) as possible. It defines its own x- and y-positions to keep clutter and unneeded variables from the controller class and only needs the update() method to be called for it to be ready for the next frame.
Next is the Controller class. This class is the brains of the operation doing all the looping and drawing on each frame.
package { //imports import flash.events.Event; import flash.display.Bitmap; import flash.display.BitmapData; import flash.geom.Rectangle; import flash.utils.getTimer; import flash.display.MovieClip; public class Controller extends MovieClip { //define variables private var w:int; private var h:int; private var bm:Bitmap; private var bmd:BitmapData; private var clearRect:Rectangle; private var currentTime:int = 0; private var holder:Vector.<Particle> = new Vector.<Particle>; //most machines should be fine with 150,000 particles. Even my Nexus One can handle it! private var n:int = 150000; public function Controller():void { w = stage.stageWidth; h = stage.stageHeight; //create the blank rectangle we will use to clear the bitmapdata clearRect = new Rectangle(0, 0, w, h); //create the bitmap and bitmapdata bmd = new BitmapData(w, h, false, 0); bm = new Bitmap(bmd); addChild(bm); //populate the holder with n number of particles for(var i:int = 0; i < n; i++ ){ //notice no temporary variable was used to store the particle before pushing it to the Vector //this only uses unneeded memmory holder.push(new Particle(stage.getRect(this))); } //add listener for every frame addEventListener(Event.ENTER_FRAME, onFrameLoop); } private function onFrameLoop (evt:Event):void{ var p:Particle; //lock the bitmap and clear it before drawing bmd.lock(); bmd.fillRect(clearRect,0); //for n times get the respective particle in the holder and set the //corresponding pixel at p.x and p.y to the particles color for(var i:int = 0; i < n; i++ ){ //notice a temporary variable was used here because otherwise the object //would need to be read 3 times from the Vector - much slower p = holder[i]; bmd.setPixel(p.x, p.y, p.c); //update the particle's position p.update(); } //update the bitmap bmd.unlock(); //trace the FPS trace(1000 / timeDifference); } private function get timeDifference ():int { var totalPlayedTime:int = getTimer(); var timeDifference:int = (totalPlayedTime - currentTime); currentTime = getTimer(); return timeDifference } } }Thats it! You have what you saw in the first demo.
EPILEPSY WARNING:
Please don’t view this demo if you are likely to suffer from epileptic attacks or loss of consciousness, particularly when looking at some types of strong flashing lights, rapid succession of images, simple geometric shapes, flashes or explosions.
Unfortunately there is a problem. If you run this code inside the Flash Professional IDE it will never run at 24/24 FPS. This is because when you run it here, the Flash IDE tries to connect with Flash Player to read many different things off it, this is how you get your error reports when something goes funny. However, luckily for you, your clients probably never have to see this side of things and on its own, the Flash Player works beautifully, running easily at 24/24 FPS. Its just slightly more difficult to read the FPS. I recommend making a small dynamic text field and output what we are should be tracing to that, this is outside the scope of this tutorial and is something that is pretty straightforward so I can leave that with you.
So now that you have made your particle system, wouldn’t it be nice to show it off to your friends and clients? Coming from experience all you will get from showing this one is some weird glances with some squeezed praise on the side from all of those but seasoned programmers. Let’s make something pretty.
Over 9000?! Playing it Safe With so Many Particles.
Before moving on any further I recommend tinkering around a bit with the above particle system and see how far you can push it. On my less than average system I can go to about 200,000 at 24/24 FPS and at 250,000 its at about 18/24 FPS, just as a reference. Get a grip for how far you can push your own system and heck, even boast about it in the comments!
Let’s look at some of the things you should avoid when playing around with systems such as this.
Trace statements. One of the most useful things in a Flash Developer’s arsenal is actually a big task for Flash Player. Once per frame is okay, but make sure you don’t shove it into one of your particles when you have 200k of them running. This will simply instantly crash the Flash IDE and you’l spend the next couple of minutes pressing all the exit buttons. A good way to test something in a particle is just to drop the number of them to something between one and ten.
Everything matters when you’re doing something 200,000 times 24 times a second so be sure to keep looking through your code and never make any big changes without putting the number of particles down to a single number.
Making Something Pretty – A Basic Waterfall Effect
This will be a very basic example of building something that looks mildly attractive.
This is the updated Particle class which is used to create the waterfall:
package { import flash.geom.Vector3D; import flash.geom.Rectangle; public class Particle { private var pos:Vector3D = new Vector3D(0,0); private var vel:Vector3D = new Vector3D(rand(3), rand(3)); private var bounds:Rectangle; private var color:uint = 0x00FFFF public function Particle(stageRect:Rectangle) { bounds = stageRect; //spawn the particle from the top left corner pos.x = 0; pos.y = 0; } private function rand(n:int):Number{ //this function returns a random number between 0 and n return Math.random() * n } public function update ():void{ pos.x += vel.x; pos.y += vel.y; //add a small amount to the y velocity to simulate gravity vel.y += 0.1; //check if the particle is outside the bounds of the rectangle checkBounds(); } private function checkBounds():void{ //this time we need to decrease velocities in both directions to make sure they are all eventually removed if(pos.x < 0 || pos.x > bounds.width){ vel.x *= -0.8; vel.y *= 0.8; } if(pos.y < 0 || pos.y > bounds.height){ vel.x *= 0.9; vel.y *= -Math.random() * 0.8; } } //the get methods for the color and x,y values of the particle public function get c ():uint{ return color } public function get x ():Number{ return pos.x } public function get y ():Number{ return pos.y } //returns true if the particle does not have a lot of 'energy' left, false otherwise public function get remove ():Boolean { if(Math.abs(vel.y) + Math.abs(vel.x) < 0.1){ return true; } return false; } } }As you can see there need to be no major changes to the Particle class because it will always stay as a just a placeholder for a group of related numbers and funcions.
Next is the Controller class. Again, there is no need for any major changes. Let’s take a look:
package { //imports import flash.geom.Point; import flash.events.Event; import flash.display.Bitmap; import flash.display.BitmapData; import flash.geom.Rectangle; import flash.utils.getTimer; import flash.display.MovieClip; import flash.filters.BlurFilter; import flash.geom.ColorTransform; public class Controller extends MovieClip { //define variables private var w:int; private var h:int; private var bm:Bitmap; private var bmd:BitmapData; private var clearRect:Rectangle; private var currentTime:int = 0; private var holder:Vector.<Particle> = new Vector.<Particle>; //define the blur and color transformers private var blurFade:BlurFilter = new BlurFilter(4, 4, 1); private var colorFade:ColorTransform = new ColorTransform(0.7,0.7,0.999); //the number of particles pushed into the holder every frame private var n:int = 50; public function Controller():void { w = stage.stageWidth; h = stage.stageHeight; bmd = new BitmapData(w, h, false, 0); bm = new Bitmap(bmd); addChild(bm); addEventListener(Event.ENTER_FRAME, onFrameLoop); } private function onFrameLoop (evt:Event):void{ //push new particles in n times every frame for(var i:int = 0; i < n; i++ ){ holder.push(new Particle(stage.getRect(this))); } bmd.lock() //apply the BlurFilter which blurs the colors together and fades the particles out bmd.applyFilter(bmd, bmd.rect, new Point(0,0), blurFade); //apply the color transformation to give the particle trails a blue tint bmd.colorTransform(bmd.rect, colorFade); for(var j:int = 0; j < holder.length; j++ ){ var p:Particle = holder[j]; //check if the particle has a resonable amount of energy left through its getter method //if not remove it from the holder vector if(p.remove){ holder.splice(j,1); j--; p = null; } else{ bmd.setPixel(p.x, p.y, p.c); p.update(); } } bmd.unlock(); //trace the FPS //trace(1000 / timeDifference); } private function get timeDifference ():int { var totalPlayedTime:int = getTimer(); var timeDifference:int = (totalPlayedTime - currentTime); currentTime = getTimer(); return timeDifference } } }You should end up with something like this. If you’ve just found this, you might want to hit refresh in your browser to see the start of the waterfall to better understand how the particles move.
Needless to say that you can run and run with the basic ideas here and great some amazing effects, a lot of which pop up on WonderFl quite often, or even to create this kind of effect. (shameless self plug
) A very ‘cool’ thing to try out is to have the particles interacting with the mouse in some way, the effect can be ne beautiful when done correctly. The perlinNoise() method of the BitmapData class might also be worth mentioning here as it sometimes used in particle systems to create a flowing effect, which can also be very beautiful.
Summary
I hope after reading this that you can take away a number of things from how to get the most out of Flash Player in your code to an introduction to building particle effects. Most of all, I hope I ignited a little flame of curiosity somewhere and gave you a new set of boundaries for Flash itself. Hopefully, an ‘extreme’ particle system won’t seem too extreme anymore
It’s rare, but it happens. Sometimes you absolutely need to extend a class, and that class does not already extend
EventDispatcher. At the same time, you absolutely need your class to be anEventDispatcher. Because ActionScript 3 does not allow multiple inheritance, how do you reconcile this?In other situations, perhaps you would like to create a class that has a bunch of static methods on it, and isn’t really meant to be instantiated. You would like to be able dispatch events from this class as well, but you can extend
EventDispatcherbecause that enables instance methods, not class methods.Let’s take a look at how to achieve these goals.
Step 1: How To Do It
The solution in both cases is to instantiate, hang onto, and use an
EventDispatcherobject. That is, anew EventDispatcher()object, not necessarily any of its subclasses. In the case of needing to extend something else, you can also have your class implement theIEventDispatcherinterface.The process goes something like this:
Implement
IEventDispatcher(not for the static class).package { import flash.events.*; public class ArrayDispatcher extends Array implements IEventDispatcher { public function ArrayDispatcher() { } } }Create a new
EventDispatcherobject, and store it in a property.private var _dispatcher:EventDispatcher public function ArrayDispatcher() { _dispatcher = new EventDispatcher(); }Implement the methods defined by
IEventDispatcher, and simply wrap around the matching method on yourEventDispatcherinstance.public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void { _dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); } public function dispatchEvent(event:Event):Boolean { return _dispatcher.dispatchEvent(event); } public function hasEventListener(type:String):Boolean { return _dispatcher.hasEventListener(type); } public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { _dispatcher.removeEventListener(type, listener, useCapture); } public function willTrigger(type:String):Boolean { return _dispatcher.willTrigger(type); }Step 2: A Static
EventDispatcherFor reference, implementing this in a static class might look like this:
package { import flash.events.*; public class StaticDispatcher { private static var _dispatcher:EventDispatcher = new EventDispatcher(); public static function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void { _dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); } public static function dispatchEvent(event:Event):Boolean { return _dispatcher.dispatchEvent(event); } public static function hasEventListener(type:String):Boolean { return _dispatcher.hasEventListener(type); } public static function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { _dispatcher.removeEventListener(type, listener, useCapture); } public static function willTrigger(type:String):Boolean { return _dispatcher.willTrigger(type); } } }Note that there is no
extendsorimplements, all members are nowstatic, and the_dispatcherproperty is now created directly on the property, instead of in the constructor (because there is no constructor).That’s All
Although quick to explain, this is definitely an advanced technique, and you probably won’t require it if you’re at a point where dispatching your own events is still a new concept. But file this away somewhere in your mind so you can come back to this example when you need it.
We’ve looked previously at adding our own tools to Unity’s editor; now, in this short tutorial, I’ll introduce you to handling the assets by script in Unity. We’ll manage paths, create prefab files, generate a texture and save it to an image. Finally we’ll also create a material file that uses the generated image, and all this will be done by code.
Final Result Preview
Let’s take a look at the final result we will be working towards:
Step 1: Set Up the Project
Create an empty project; we won’t be using anything fancy here so we shouldn’t bother to import anything at all. Once that’s done, create an editor script. Unity will let us use its editor classes only if we place our script in a folder named Editor. Since that doesn’t exist in our project yet, we need to create it.
Now let’s create a script inside it.
Step 2: Add a MenuItem
Let’s clean up our script. Aside from the basic functionality, we also want to be able to use the editor classes. We need to be
using UnityEditorand our script’s class should extend theEditorclass instead ofMonoBehaviourlike normal game objects do.using UnityEngine; using System.Collections; using UnityEditor; public class Examples : Editor { }In our first function we’ll be working with prefabs, let’s call it a
PrefabRoutine.public class Examples : Editor { void PrefabRoutine() { } }To easly execute this function from the editor, let’s add it as a
MenuItem.public class Examples : Editor { [MenuItem ("Examples/Prefab Routine")] void PrefabRoutine() { } }Aside from letting the unity know that we want this function to be executable from the Examples->Prefab Routine”, we also need to make this function static.
public class Examples : Editor { [MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { } }If you go back to the editor now (and refresh the menu), you’ll notice that there’s a new menu named Examples there.
If you select the Prefab Routine nothing will happen since our function is empty.
Step 3: Create a Folder
To shape our project the way we want we need to know how to create folders so we can move stuff around. Creating a folder from the script is very straightforward, all we need to do is to let unity know where the folder should be placed. To create a folder we need to use
AssetDatabaseclass.[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { AssetDatabase.CreateFolder("Assets", "Prefab Folder"); }“Assets” is the name of the parent folder of the directory we want to create. In our case it’s the main project folder where all our assets are imported/created.
Note that you can also use the .NET
Directoryclass. This will also let you delete, move or access the directories’ files. To use this class you need to beusing System.IO.Each time you select the Prefab Routine from the editor, a new folder should be created and be visible in the project view.
Step 4: Create a Prefab
To create a prefab we need to call
EditorUtility.CreateEmptyPrefab(). The function takes the prefab’s path as an argument.[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { AssetDatabase.CreateFolder("Assets", "Prefab Folder"); Object prefab = EditorUtility.CreateEmptyPrefab("Assets/Prefab Folder/obj.prefab"); }Don’t forget about the extension! Also, after we create the file we need to call
AssetDatabase.Refresh(), so the unity is able to see it.[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { AssetDatabase.CreateFolder("Assets", "Prefab Folder"); Object prefab = EditorUtility.CreateEmptyPrefab("Assets/Prefab Folder/obj.prefab"); AssetDatabase.Refresh(); }If we leave a constant path as an argument, each time we select our routine a new empty prefab will replace the old one. Let’s assign each prefab to separate folder to counter that. To do this we need to save the most recently created folder to a string so we can use it as a path argument later. The
CreateFolderfunction returns aGUID, which basically is the file’s (or directory’s) ID. There’s a function that retrieves the path if we submit this ID. It’s calledGUIDToAssetPath; let’s use it to get our folder’s path.[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder")); Object prefab = EditorUtility.CreateEmptyPrefab("Assets/Prefab Folder/obj.prefab"); AssetDatabase.Refresh(); }Now let’s use the
pathto direct the prefabs we are going to create to the most recently created folder.[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder")); Object prefab = EditorUtility.CreateEmptyPrefab(path + "/obj.prefab"); AssetDatabase.Refresh(); }You can test whether the created empty prefabs are packed in folders now.
Step 5: Set the Prefab
If you create a prefab then you probably don’t want to leave it empty because in that case it’s pretty much useless. Let’s set our prefab if there’s any game object selected while our routine is executing. We’ll the prefab to the selected object. To get the currently selected object we can use the
Selectionclass which has a reference to it. To set the prefab we need to callReplacePrefab().[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder")); Object prefab = EditorUtility.CreateEmptyPrefab(path + "/obj.prefab"); AssetDatabase.Refresh(); if (Selection.activeObject) EditorUtility.ReplacePrefab(Selection.activeGameObject, prefab); }If you run the routine with any game object selected now then you’ll notice that the created prefab is automatically set.
That’s it, we have created a custom routine for prefab creation, it’s not very useful but you should be able to know how to do that now if there will be a need for such a thing in your project.
At the end I also want to mention that
AssetDatabasealso lets you move assets around, move them to trash or delete them by callingAssetDatabase.MoveAsset(), AssetDatabase.MoveAssetToTrash()andAssetDatabase.DeleteAsset()respectively. The rest of the functionality can be found at theAssetDatabasescript reference page.Step 6: Add Another Menu Item
Let’s go to another example, this time we’ll create a texture and a material programmatically. Let’s call this menu item Material Routine.
[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder")); Object prefab = EditorUtility.CreateEmptyPrefab(path + "/obj.prefab"); AssetDatabase.Refresh(); if (Selection.activeObject) EditorUtility.ReplacePrefab(Selection.activeGameObject, prefab); } [MenuItem ("Examples/Material Routine")] static void MaterialRoutine() { }Now we have two items to choose from in the Examples menu.
Step 7: Create a Texture
Let’s create a
Texture2Dand set its size to(256, 256)for this example.[MenuItem ("Examples/Material Routine")] static void MaterialRoutine() { Texture2D tex = new Texture2D(256, 256); }Now we shouldn’t let all those pixels go to waste, so let’s set the texture’s pixels according to some kind of thought-up formula. For that we’ll need two
forloops to go through every pixel. To set the each pixel’s color we need to callSetPixel()which takes the position of the pixel on a texture and its color as the arguments.[MenuItem ("Examples/Material Routine")] static void MaterialRoutine() { Texture2D tex = new Texture2D(256, 256); for (int y = 0; y < 256; ++y) { for (int x = 0; x < 256; ++x) tex.SetPixel(x, y, new Color()); } }To assign the color we’ll use the
Mathf.Sin()function. TheColorclass can be initialized with three floats, corresponding to the red, green and blue color components, respectively. The max allowed value is1and min is0, so theSin()function suits our needs perfectly.for (int y = 0; y < 256; ++y) { for (int x = 0; x < 256; ++x) tex.SetPixel(Mathf.Sin(x*y), Mathf.Sin(x*y), Mathf.Sin(x*y))); }It doesn’t matter what we submit to the
Sin()function, but to get something more interesting we should give a value that changes for each pixel.Step 8: Create an Image
Now let’s create an image from the texture we just created. Since we’ll be writing to a file in binary mode, we need to be
using System.IO, so let’s add it to the top of our script.To save our texture as a PNG image we first need to call
EncodeToPNG()which will return an array of bytes that the PNG file consists of.for (int y = 0; y < 256; ++y) { for (int x = 0; x < 256; ++x) tex.SetPixel(x, y, new Color(Mathf.Sin(x*y), Mathf.Sin(x*y), Mathf.Sin(x*y))); } byte[] pngData = tex.EncodeToPNG();Now that we’ve got our
pngDatawe can write it to a file and create a PNG image in this way.byte[] pngData = tex.EncodeToPNG(); if(pngData != null) File.WriteAllBytes("Assets/texture.png", pngData);Since we create the file at a constant path, each time we’ll run
MaterialRoutine(), the texture will get overwritten.And since we’ve got our image, we don’t need the generated texture anymore as it won’t be referencing an image anyway. Let’s destroy it.
byte[] pngData = tex.EncodeToPNG(); if(pngData != null) File.WriteAllBytes("Assets/texture.png", pngData); DestroyImmediate(tex);Also, we need to let Unity update the project view and file references; to do that we need to call
AssetDatabase.Refresh().byte[] pngData = tex.EncodeToPNG(); if(pngData != null) File.WriteAllBytes("Assets/texture.png", pngData); DestroyImmediate(tex); AssetDatabase.Refresh();Let’s test whether the texture gets created when we execute our routine.
Step 9: Create a Material
We’ve got an image and now we can create a material that uses it as a texture. Let’s create a
new Material.AssetDatabase.Refresh(); new Material(Shader.Find("Diffuse"));The created material will use a Diffuse shader. To save this material to the file, we can call
AssetDatabase.CreateAsset(). This function takes an asset as the first argument, and the path as the second one.AssetDatabase.Refresh(); AssetDatabase.CreateAsset(new Material(Shader.Find("Diffuse")), "Assets/New Material.mat");If you run our routine now, you’ll see that the material is created.
As you can see everything is correct, its name is New Material and it uses Diffuse shader, but there’s no texture assigned to it.
Step 10: Assign the Texture
First we need to get a reference to the material we just created. We can get that by calling
AssetDatabase.LoadAssetAtPath()which loads the asset and returns its reference.AssetDatabase.CreateAsset(new Material(Shader.Find("Diffuse")), "Assets/New Material.mat"); Material material = (Material) (AssetDatabase.LoadAssetAtPath("Assets/New Material.mat",typeof(Material)));Now let’s assign our generated texture as the main texture of the material. We can get the texture reference of the generated texture in the same way we got the material reference.
Material material = (Material) (AssetDatabase.LoadAssetAtPath("Assets/New Material.mat",typeof(Material))); material.mainTexture = (Texture2D) (AssetDatabase.LoadAssetAtPath("Assets/texture.png", typeof(Texture2D)));To see the results, run the Material Routine.
As you can see, the material has the texture assigned now.
Conclusion
That’s the end of the introduction to manage your assets using scripts. If you want to expand your knowladge on the topic you can visit the Unity Editor Classes reference page, particularly the AssetDatabase script reference is worth looking into. If you need to work at a low level, you should also read the docs on System.IO to get more information on its classes and how you can use them. Thanks for your time!
In the previous video I showed you how to implement basic playback controls. Today I am going to talk about using labels to mark and navigate to precise locations in a TimelineLite. Labels in TimelineLite work similarly to how frame labels work in Flash IDE timelines. I’ll be showing you multiple ways to add labels and some clever ways of using them. We’ll also take a little look at some features exclusive to TimelineMax.
TimelineLite in Action
Let’s take a look at the example we’ll be building in the video:
You can find all the files used to create the SWF above in the source files for this tutorial.
Watch the Screencast
Don’t like ads? Download the screencast, or subscribe to Activetuts+ screencasts via iTunes!
Adding Labels to a TimelineLite
There are two methods that you can use for adding labels to a TimelineLite
addLabel(label:String, time:Number):voidAdds a label at a particular time. It is most common to pass in the current duration of the timeline as the time.
tl.append( TweenMax.to( align_mc, 1, { x:endX } ) ); tl.append( TweenMax.to( align_mc, .2, { autoAlpha:0 } ) ); // add a label named transform immediately after the previous tween is finished. tl.addLabel("transform", tl.duration) tl.append( TweenMax.to( transform_mc, 1, { y:endY } ) );insert(tween:TweenCore, timeOrLabel:* = 0):TweenCoreWhen using
insert()to insert a tween, the tween will be inserted at the time or label specified in the second parameter. If you insert at a label that doesn’t exist yet, it will automatically place that label at the end of the timeline and then insert the tween. This technique makesinsert()act like anappend()with the added value of creating a label.tl.append( TweenMax.to( align_mc, 1, { x:endX } ) ); tl.append( TweenMax.to( align_mc, .2, { autoAlpha:0 } ) ); //insert a tween and the transform label immediately after the previous tween is finished. tl.insert( TweenMax.to( transform_mc, 1, { y:endY } ), "transform" );Navigating to Labels
TimelineLite’s intuitive
gotoAndPlay()andgotoAndStop()methods work exactly as those same methods of the MovieClip object. Although this video focuses on using gotoAndPlay with a label, you can also pass in a time as well.//jump to the color label and play tl.gotoAndPlay("color"); //jump 1 second into the timeline and stop tl.gotoAndStop(1);TimelineMax gives us the unique ability to play to a particular label with the
tweenTo()method. If MovieClips had such a method it would be calledplayTo(). At the end of this series I will be showing you how to add an ease to atweenTo()as well as a number of other tricks.Due to the introductory nature of this series there are some label-related features of TimelineLite/Max that I did not get to cover. Advanced users may want to read up on the optional
suppressEventsparameter that can be used withgotoAndPlay()andgotoAndStop()in the documentation.TimelineMax’s Label Helpers
TimelineMax has a number of features for figuring out the names of labels based on their relation to the current position of the playhead or a specified time. These properties and methods make it possible to dynamically calculate what the nearest label is in any direction.
Property
currentLabel– The closest label that is at or before the current time.Methods
getLabelBefore(time:Number)– Returns the previous label (if any) that occurs before the time parameter. If you do not pass a time in, the currentTime will be used.getLabelAfter(time:Number)– Returns the next label (if any) that occurs AFTER the time parameter. If you do not pass a time in, thecurrentTimewill be used.Conclusion
Due to the powerful properties and methods of TimelineLite/Max it is extremely easy to navigate and control your script-based timelines. The label-related features that we have discussed today really just scratch the surface of what can be done. Once you get a handle on these basic techniques you will find yourself creating timelines with more and more dynamic features. Suppose you want to prevent a user from clicking the “library” button once they are in the library section. You could simply add logic to the button’s click handler that considers the following:
Feel free to convert that to ActionScript if you would like some extra credit
In the next tutorial I am going to be showing advanced techniques for adding tons of tweens to a timeline with very little code and extreme precision.
If you have any questions or comments on this tutorial simply post a comment below.
Thanks for watching!
The shell game is a cliché of street corners in black and white movies and modern cities: the hustler puts a pea under one of three shells, and swaps the shells around rapidly, challenging you to pick the shell with the pea underneath. The punter will typically get it correct… right up to the point where big money is at stake, or when the punter isn’t one of the hustler’s cronies.
In this tutorial, exclusive to Premium members, you’ll learn how to create your own version of the shell game – except without the cheating!
Preview
Let’s take a look at the final result we will be working towards:
The tutorial covers both the design and the coding of the game, and uses TweenMax to make the shells move along a curved path.
Active Premium Membership
We run a Premium membership system which periodically gives members access to extra tutorials, like this one! You’ll also get access to Psd Premium, Vector Premium, Audio Premium, Net Premium, Ae Premium, Cg Premium, Photo Premium, and the new Mobile Premium too. If you’re a Premium member, you can log in and download the tutorial. If you’re not a member, you can of course join today!
Also, don’t forget to follow @envatoactive on twitter and grab the Activetuts+ RSS Feed to stay up to date with the latest tutorials and articles.
We’re excited to let you know that now, in addition to Twitter and Facebook, you can get involved with the Activetuts+ community over at Google+! We’ll be using Google+ to let you know about our latest tutorials, competitions, and Tuts+ news. Read on to find out more…
What to Expect on Google+
Through Google+, we’ll be publishing links to our new tutorials and articles, industry news, graphics and examples from our community, and lots more! It’s going to be an extension of the Activetuts+ site and community, in the same way we treat Twitter and Facebook.
Don’t have a Google+ account? You can head over and create one here, or find out a little more about how it works.
Add Activetuts+ to Your Circles
Just use the button above to add Activetuts+ to your circles, so you don’t miss out on any of our new content. We look forward to seeing you on Google+!
It’s that time of month again! If you’re a fan of the Activetuts+ Facebook page, you can now access a new bonus tutorial. In this month’s Facebook Fan Bonus, you’ll learn how to create a dynamic “Accordion” menu.
Final Result Preview
Here’s a look at the type of menu you’ll learn to build in the tutorial:
Obviously, in your own game, you’ll add other elements to the menu’s different screens.
As Tyler Seitz, the author, describes it:
Download This Fan Bonus Now!
All you have to do is Like us…
Not On Facebook?
Don’t worry, the tutorial will be posted on Activetuts+ in a month’s time!
Today, you will learn how to remove the background color from a sprite sheet using AS3, and blit the result to a bitmap canvas. Read on to learn more!
Final Result Preview
Let’s take a look at the final result we will be working towards:
Step 1: Drawing the Spritesheet
So, it is time to draw your spritesheet. Open up your favourite ‘pixel-art’ program and create an image of 128×128 and give it a background colour of ‘#e731f2′ which is a nice purple colour!
This is my amazing artwork:
Save your image somewhere organised and let us continue!
Step 2: Importing the Sprite Sheet
Now, I’m call this a sprite sheet even though it is just one image. A ‘sprite sheet’ usually consists of more than one sprite but we can imagine we have more, right?
Anyway, if you are using Flash CS4 or higher, simply import your image via File | Import | Import to Library:
If you are using any other AS3 IDE, I have included the SWC file so you should probably skip this step. If you wish to embed your own images, I’m sure that your IDE of choice will have this feature.
Step 3: Exporting the Sprite Sheet
We have now got our sprite sheet in the Library; we should really make it into a
Class.Right-click the image in the library and select ‘Properties’. Give the image the following properties:
Hit OK. If you get a warning, just ignore it; it does not matter here.
Step 4: Creating the Document Class
If you’re not familiar with the concept of a document class, check out this Quick Tip before reading further.
Create a new ‘ActionScript 3.0 Class’ and give it the name ‘Main’. When the file has been created, save it as ‘Main.as’.
This code should be placed in our new Class:
package { import flash.display.MovieClip; public class Main extends MovieClip { public function Main() { // constructor code } } }We are not done yet, however! If you are using the Flash IDE, navigate to the ‘Properties Panel’ and set the ‘Document Class’ to ‘Main’. If you are wondering what that does, it means that when your application/game is run by the Flash Player,
Main.aswill be the class that’s linked to the SWF itself. Cool, huh?Run the program; if you get no errors then you should be good to go!
Step 5: Creating the Canvas
Before we do any blitting, we will first need to make a canvas to blit onto. If you are unsure of the term Blitting or would like to learn more about it, please take a look at this tutorial.
Now, we will declare a new Bitmap variable, to which we will blit (copy) the image.
After we have done this, we will add a
functioncalledInitialize()which will allow us to set everything up neatly:public function Main() { Initialize(); }Let us create the function now:
private function Initialize():void { canvas = new Bitmap( new BitmapData( 550, 400, false, 0x000000 ) ); //Sets the Canvas to Black. stage.addChild( canvas ); //Adds the canvas to the stage. }We are still not finished however, as we still have to add the
imports:Run the program; if it has a black background, it worked!
Step 6: Initializing the SpriteSheet
Firstly, we will need to make a new variable of type
SpriteSheet– which was the Class for the image we imported earlier, remember?We shall then initialize it:
private function Initialize():void { canvas = new Bitmap( new BitmapData( 550, 400, false, 0x000000 ) ); //Sets the Canvas to Black. spriteSheet = new SpriteSheet(); //Sets spriteSheet to hold an instance of the image that we made. stage.addChild( canvas ); //Adds the canvas to the stage. }Run the program and you should see nothing; let’s fix that right away!
Step 7: Updating the Program
Now we need to add an
ENTER_FRAMEevent. This will allow the program to update 24 times a second (24 FPS) in our case.In the
Main()function, add the following line:public function Main() { Initialize(); stage.addEventListener( Event.ENTER_FRAME, Update ); }Now we need to make the
Update()function. Add this function after the other functions:private function Update(e:Event):void { }Don’t forget the
imports:Now we are ready to do some blitting!
Step 8: Blitting
Here comes the interesting part!
Alright, so what we want to do is:
In the update function, type the following code:
private function Update(e:Event):void { canvas.bitmapData.lock(); canvas.bitmapData.fillRect( new Rectangle( 0,0,stage.width, stage.height ), 0x000000 ); canvas.bitmapData.copyPixels( spriteSheet, new Rectangle( 0,0,128,128 ), new Point( 100, 100 ) ); canvas.bitmapData.unlock(); }If you run this, you will get your image on the canvas! However, this is not just what we are aiming for as we wish to remove that background colour from the image.
I shall explain some of the code above first:
canvas.bitmapData.lock();– This line optimizes the blitting and it is a good habit to type it most of the time!canvas.bitmapData.fillRect();– This line clears the canvas by filling it with a Black colour.canvas.bitmapData.copyPixels();– Not very useful in our situation but copies all the pixels from part of an image.canvas.bitmapData.unlock();– This works withlock()to optimize the process.Now you should have this on the screen…
Yes, I know, you are probably right. I think we should get rid of the purple too…
Step 9: Removing the Colour
Finally, it’s time to remove the purple colour!
What we want to do is check through every pixel; if the pixel is purple, we simply do not copy it to the canvas. To do this, we will make our own function.
Change
Update()to the following:private function Update(e:Event):void { canvas.bitmapData.lock(); canvas.bitmapData.fillRect( new Rectangle( 0,0,stage.width, stage.height ), 0x000000 ); CustomBlitting( spriteSheet, new Rectangle( 0,0,128,128 ), new Point( 100, 100 ), 0xE730F2 ); canvas.bitmapData.unlock(); }Our new function (
CustomBlitting(), which we have not written yet) takes most of the parameters that copyPixels does, along with an extra one: the colour we wish to remove.Time to write the function. This code may look complicated if you have never done a
nested for-loopbefore. The way this loop works is basically:private function CustomBlitting( src:BitmapData, srcRect:Rectangle, destPoint:Point, color:uint ):void { for( var i:int = 0; i < srcRect.height; i++ ) { for( var j:int = 0; j < srcRect.width; j++ ) { var currentPixel:uint = src.getPixel( srcRect.x + j, srcRect.y + i ); if( currentPixel != color ) { canvas.bitmapData.setPixel( destPoint.x + j, destPoint.y + i, currentPixel ); } } } }Let me explain the getPixel and setPixel, although they should probably be self-explanatory:
getPixel( x, y );– This returns the colour of a pixel at the X,Y location.setPixel( x, y, color );– This sets the colour of a pixel tocolorat the X,Y location of the canvas.Now if you run the program, it works!
Step 10: Challenges
I only have one challenge for you to do for this tutorial:
Accept an Array of colours as a parameter and remove any colours from the image that are in the array.
Good luck!
Conclusion
I hope you have enjoyed this tutorial and have learnt something new today. If you’d like to show me your SWF with the completed challenges, leave a comment below!