You’ll typically find it fairly straightforward to choose which version of ActionScript to use when you start a new project. You usually decide based on which version of the Flash Player you’re trying to target, the need for enhanced performance, or a specific feature you wish to use. Deciding what to do with existing projects, however, is another matter. Often you need to determine whether or not it’s worth the time and effort to migrate to ActionScript 3.0 from a prior version of the language, or just to start over.
You can’t easily determine what to do; each project’s characteristics probably significantly affect your decision. The clarity of your existing code, the extent to which you use particular features, project size, and your comfort level, among other factors, help you decide.
This chapter walks you through a small-scale, manufactured example of a migration from ActionScript 2.0 to ActionScript 3.0. Read the text linearly, as code isn’t always explained in detail twice. It is very important to understand that this example isn’t a demonstration of real-world best practices at work. This example is significantly constrained, and attempts to insert as many migration issues as possible into its tiny footprint. Throughout the example, what may appear to be odd choices, poorly optimized code, or even mistakes, have been intentionally injected into the code to either set up a migration task or bring the two versions into a parallel structure.
For example, objects are added out of order intentionally to demonstrate depth management, different methods of providing or checking default values have been used, objects are created in a variety of ways, both component and custom buttons are used, function/method placement isn’t consolidated, and so on. The project demonstrates both timeline- and class-based coding, but is by no means an example of good object oriented programming practices. The class and timeline are coupled too tightly, to mention one example.
If you remember that this scenario is artificial, designed to illustrate migration issues, it may help you form a plan or checklist of sorts to help when it comes time to update your next legacy project.
This representative example creates a particle system that performs a few basic tasks. At startup, it plays an ambient audio loop. It then attempts to draw a blue square particle upon each enter frame event, based on a simple conditional. If successful, the particle moves away from the center of the stage, rotating and fading to transparency. Each time a particle is created, its name is added to a text field.
Finally, two buttons add functionality to the system. The first opens a web page. The second selects the previously created particle, replaces its content with a larger red square, and plays a new random sound. The altered particle affects its associated sound, panning based on the particle’s position, and fading based on the particle’s alpha.
Both examples require a custom button with a linkage of “BtnLink” and a Button component. The ActionScript 2.0 FLA file also requires an empty movie clip called “Particles.” These assets are provided in the downloadable source files (see Preface).
To start, the complete ActionScript 2.0 code will be presented, with numbered lines. The project features a single class used by a brief frame script in the main timeline. This structure helps demonstrate migration issues related to the use of classes, as well as general syntax. Discussion of each script follows, with a general explanation following the ActionScript 2.0 code, and migration comments following the ActionScript 3.0 code.
The main timeline builds the user interface, and creates an
instance of the Particles
class by
adding a movie clip to which the class is linked. The class does all the
work with the particles, and will be explained in a moment.
1 import mx.controls.Button; 2 3 if (!imgURL) { 4 var imgURL:String = "bg.jpg"; 5 } 6 7 var txtFrmt:TextFormat = new TextFormat(); 8 txtFrmt.align = "right"; 9 var txtFld:TextField = this.createTextField("particleInfo", 1, 380, ¬ 10, 100, 380); 10 txtFld.setNewTextFormat(txtFrmt); 11 12 bg = this.createEmptyMovieClip("bckgrnd", 2); 13 bg.loadMovie(imgURL); 14 bg.swapDepths(txtFld); 15 16 var snd:Sound = new Sound(); 17 snd.loadSound("../audio/bass_back.mp3"); 18 snd.onLoad = function(success:Boolean):Void { 19 if (success) { 20 snd.setVolume(10); 21 snd.start(0, 100); 22 } 23 }; 24 25 var particles:MovieClip = attachMovie("Particles", "particles", 3); 26 27 var controls:MovieClip = this.createEmptyMovieClip("btns", 4); 28 controls._y = 360; 29 30 31 var linkBtn:MovieClip = controls.attachMovie("BtnLink","link", 5); 32 linkBtn._x = 20; 33 linkBtn.siteLink = "http://www.learningactionscript3.com/"; 34 linkBtn.onRelease = function():Void { 35 getURL(this.siteLink, "_blank"); 36 }; 37 38 var changePBtn:Button = controls.createClassObject(mx.controls.Button, ¬ "chng", 6, {label:"Change"}); 39 changePBtn.move(changePBtn.x + 120, 0); 40 function changeParticle():Void { 41 var p:MovieClip = particles["particle" + (particles.count - 1)]; 42 p.clear(); 43 p.beginFill(0xFF0000); 44 makeRect(p, -20, -20, 40, 40); 45 p.endFill(); 46 particles.particleSound(p); 47 } 48 changePBtn.addEventListener("click", changeParticle); 49 50 function makeRect(mc:MovieClip, xp:Number, yp:Number, w:Number, ¬ h:Number):Void { 51 mc.moveTo(xp, yp); 52 mc.lineTo(xp + w, yp); 53 mc.lineTo(xp + w, yp + h); 54 mc.lineTo(xp, yp + h); 55 mc.lineTo(xp, yp); 56 }
The following includes discussions of ActionScript 2.0 syntax, but also a basic explanation of the project—much of which applies to both ActionScript 2.0 and 3.0 versions.
Import
Line 1 imports the Button
component class to make it accessible to the compiler.
FlashVars
Lines 3 through 5 check for the presence of a variable
called imgURL,
and then
initialize it to the URL of a background image in case no value
for the variable is found. This step lets you pass a path to a
background image into the project through the HTML host file. If
this feature isn’t used, the hard-coded background image isn’t
displayed.
TextFormat
Lines 7 and 8 create a simple TextFormat
instance to right-justify
text in a field.
TextField
Lines 9 and 10 dynamically initialize a TextField
. Line 9 creates the field and
gives it an instance name of particleInfo
, places the field at a
depth of level 1, places the field at point (380, 10), and sizes
the field to a width of 100 pixels and height of 380 pixels. Line
10 applies the previously created TextFormat
to the field.
ActionScript 2.0 requires that you set a level for every asset added to your project. Asset levels are hard-coded in lines 9, 12, 25, 27, 31, and 38. This step requires either careful preplanning or arbitrary level assignment (buttons between 100 and 200, movie clips between 300 and 400, and so on) and a good memory.
You can determine the next available level for symbol
instances like movie clips, using the getNextHighestDepth()
method. However,
significant problems arise when adding components (which occurs
later in the script) because the getNextHighestDepth()
method can return
errant levels beginning with 1048576. Not only does this wreak
havoc with your level management, it’s outside the valid level
range, making it impossible to remove assets dynamically.
To get around this problem, you can adopt the significantly
more confusing approach of using the DepthManager
class, created for managing
depths of Version 2 Components, generally making things more
difficult.
Line 14 swaps the depths of the text field and background image, making the background image the bottom-most asset. The text field and background were added in reverse order to demonstrate this feature.
ActionScript 2.0 allows sloppy variable use such as not
declaring or typing the variable bg
in line 12.
Lines 12 and 13 display a background image. Line 12 creates an empty movie clip, and sets its depth to level 2. Line 13 loads the image.
Lines 16 through 23 play a background sound. Line 16 creates
an instance of the Sound
class.
Line 17 loads the sound.
Lines 18 through 23 create an event handler to process, in this case, a load complete event. Upon load completion, it sets the volume of the sound to 10 percent, so you can clearly hear other sounds atop this one, and plays the sound from the beginning, looping 100 times for longer play time. Another event handler appears in lines 34 through 36.
Line 18 uses Void
to tell
the compiler that no data’s returned from the function. This
action occurs again on lines 34 and 40.
Line 20 manipulates a percent scale, and uses values between 0 and 100.
Line 25 creates an instance of a Library-based movie clip
with a linkage name of Particles
. It gives the clip an instance
name of particles
, and sets its
depth to level 3. Note here that the Particles
class responsible for particle
control can only easily be integrated into the project by
associating the class with a movie clip. This quality means that
you have to have at least an empty movie clip in your Library,
already set up with the appropriate class name.
Alternatives to this approach include manipulating the movie clip prototype, which is a bit messy and very difficult to bring forward into ActionScript 3.0, or switching to object-oriented techniques and using composition. Both approaches are significantly more involved than relying on a Library symbol.
Lines 27 and 31 also dynamically create movie clip instances. Line 27 creates an empty container (positioned in line 28) to hold buttons, and line 31 creates the first of two buttons, the functionally of which is discussed next. Both lines hard code depths, to 4 and 5, respectively.
Finally, line 38 also dynamically creates a movie clip equivalent, in this case a component. This step is relevant because, in the space of a dozen or so lines of code, you see three separate ways, each with unique characteristics, to place visible content on the stage.
Line 28 demonstrates that the movie clip’s _y
property, like most properties in
ActionScript 2.0, is preceded by an underscore.
Lines 31 through 36 add a Library-based custom button with a
linkage name of BtnLink
to the
project. Line 31 places it into the controls
movie clip, gives it an instance name of link
, and sets its depth to level
5.
Line 32 positions the x location of the button. The y location remains 0, and appears to be 360 because the button is in the controls movie clip—which, itself, appears at a y location of 360.
Lines 34 through 36 add a mouse release event handler to the button.
Line 33 dynamically creates a property called siteLink
, and then populates it with a
string. This step’s really nothing more than a variable, but here
it’s an example of dynamic versus sealed classes. In ActionScript
2.0, you could add properties to instances of most classes, even
though this practice wasn’t recommended. In ActionScript 3.0,
however, most classes are sealed, meaning you can’t dynamically
alter them in this way. You can use this approach only with select
dynamic classes in ActionScript 3.0.
The siteLink
property is
being added to the button, which you can’t do in ActionScript 3.0.
The ActionScript 3.0 discusses why this example
was included, and also discusses another more directly analogous
example.
Line 35 opens a URL in a new window, getting the URL from
the siteLink
property of the
button to which the event handler is attached.
Lines 38 through 48 add and empower a Button component. (As with a custom asset, a Button component must be in your Library.) Two different kinds of user-clickable objects demonstrate both the use of movie clips and the use of components.
Line 38 adds the button to the controls
container movie clip, gives it
an instance name of chng
, sets
it depth to level 6, and gives it a label of “Change.” Line 39
uses the button’s move()
method
to set it to an x position of 120, leaving the y position at 0, to
be affected by the position of the parent container. The
extraneous use of the x
property (rather than setting the value to 120 directly) is to
show that ActionScript 2.0 v2 components use properties without
underscores, contrary to other properties.
Lines 40 through 48 apply an event listener to the button.
Lines 40 through 47 define the function triggered by the button’s
click
event, set in line
48.
Lines 41 through 45 are discussed in the following
paragraphs, and line 46 triggers the particleSound()
method of the selected
particle in the Particles
class.
Line 41 creates a reference to the previously created
particle by accessing the movie clips within the particles
movie clip and finding the
particle by instance name. The instance name is built with the
string "particle"
and the
number of the current particle minus 1.
Lines 42 through 45 clear the contents of the particle, create a red fill, call a function that draws a centered, 40 × 40 pixel rectangle in the selected particle, and close the fill.
Lines 50 through 56 use the lineTo()
and moveTo()
methods to draw a rectangle, as
the drawRect()
method doesn’t
exist in ActionScript 2.0.
The Particles
class creates
each particle, establishes its behavior, and ultimately removes it from
the project.
1 import mx.utils.Delegate; 2 3 class Particles extends MovieClip { 4 5 private var _count:Number; 6 private var _soundNum:Number = 0; 7 private var _tempSound:Sound; 8 9 public function Particles() { 10 _x = Stage.width / 2; 11 _y = Stage.height / 2; 12 13 _tempSound = new Sound(); 14 _tempSound.loadSound("../audio/note0.mp3"); 15 _tempSound.onLoad = Delegate.create(this, ¬ onSoundPreloaded); 16 } 17 18 private function onSoundPreloaded(success:Boolean):Void { 19 if (success) { 20 if (_soundNum < 7) { 21 _soundNum++; 22 _tempSound.loadSound("../audio/note" + _¬ soundNum + ".mp3"); 23 } 24 } 25 } 26 27 private function onEnterFrame():Void { 28 makeParticle(0x0066CC, Math.random() * 10 + 10); 29 } 30 31 private function makeParticle(col:Number, ¬ size:Number):Void { 32 if (!col){ col = 0x003366; } 33 if (!size){ size = 20; } 34 35 if (Math.random() * 10 <= 2) { 36 if (_count == undefined) { _count = 0; } 37 38 var p:MovieClip = this.createEmptyMovieClip("particle" + _¬ count, this.getNextHighestDepth()); 39 _count++; 40 p.beginFill(col); 41 _parent.makeRect(p, -size/2, -size/2, size, size); 42 p.endFill(); 43 44 p.xVel = Math.random() * 10 - 5; 45 p.yVel = Math.random() * 2 - 1; 46 p.onEnterFrame = onRunParticle; 47 48 _parent.txtFld.text += p._name + " " 49 _parent.txtFld.scroll = _parent.txtFld.maxscroll; 50 } 51 } 52 53 public function particleSound(p:MovieClip):Void { 54 if (!p.snd && _soundNum > 0){ 55 var num:Number = int(Math.random() * _soundNum); 56 p.snd = new Sound(); 57 p.snd.loadSound("../audio/note" + num + ".mp3"); 58 p.snd.onLoad = function(success:Boolean):Void { 59 p.snd.start(); 60 } 61 } 62 } 63 64 private function onRunParticle():Void { 65 var p:MovieClip = this; 66 p._x += p.xVel; 67 p._y += p.yVel; 68 p._rotation += 5; 69 p._alpha -= 2; 70 71 if (p.snd) { 72 p.snd.setVolume(p._alpha / 10); 73 p.snd.setPan(p._x / this._parent._x * 200) 74 } 75 76 if (p._alpha <= 0) { 77 p.removeMovieClip(); 78 } 79 } 80 81 public function get count():Number { 82 return _count; 83 } 84 }
The following features a brief explanation of particle behavior. Where appropriate, comments have been added to address ActionScript 2.0-specific concepts and, occasionally, to explain decisions made to demonstrate migration issues. Basic syntax issues discussed in the main timeline aren’t mentioned again.
Line 1 imports the Delegate
class to make it accessible to
the compiler.
Lines 3 and 9 make up the key elements of the ActionScript
2.0 class structure. You’ll see later that ActionScript 3.0 begins
a class with the package
identifier. Line 3 shows that this class extends MovieClip
. (It’s linked to a Library
movie clip.) The class constructor beginning on line 9 centers the
particle system on the stage, and creates and preloads the first
particle-specific sound.
Lines 5 through 7 create class properties but only initialize one. This step factors later into the use of default values.
Lines 5 and 6 use the Number
data type for integer counters
because no other number data types are available. You can also see
this property in lines 31 and 55. The value assigned in line 55,
as a good example, is always an integer, but must still be typed
as Number
.
Line 15 assigns the onLoad()
event handler for the sound
created and loaded in lines 13 and 14, respectively. Delegate
is used here, however, to
demonstrate that ActionScript 2.0 does not have method closures.
Due to this setup, the Delegate
class must pass the relevant scope to the event handler for it to
access the needed properties.
To prevent delays when triggering sounds later, lines 18
through 25 load seven additional sounds, but don’t play them. The
name of the audio file contains indices 0 through 7. The first
sound, note0.mp3, was loaded
in the class constructor, so the soundNum
property is incremented before
the load.
Lines 27 through 29 demonstrate that, because this class
extends MovieClip
, ActionScript
2.0 lets you create a method for a movie clip event handler,
without any further assignment. That is, because the method has
the same name as a MovieClip
event handler, it’s executed upon every movie clip enter frame
event. This method attempts to create a blue particle that’s
between 10 and 20 pixels square.
Lines 31 through 51 create each particle. Individual aspects of this function will be discussed in separate headers, but the basic creation process is as follows. Line 35 checks to see if a newly created random number between 0 and 10 is less than or equal to 2. If so, it creates the particle. This action both prevents a particle from being created on every enter frame, and adds a nice feeling of randomness to the process.
Each particle is drawn into an empty movie clip, with its
depth set to the next highest available level. It’s given an
instance name of particle
N
,
where N
is an integer from the _count
variable, incremented each time a
particle’s created. A fill of the color passed into the method is
created, a rectangle is drawn using the makeRect()
function discussed in the
main timeline (using the size passed into the function), and the
fill’s closed.
Next, random x and y velocities are chosen for each
particle, providing movement between 5 and −5 for x and between 1
and −1 for y. The onRunParticle()
method is then assigned
as the enter frame event handler for each particle. (This step’s
in contrast to the enter frame event handler assigned at the class
level that creates the particles.)
Finally, the name of the particle, and a subsequent new line, are added to the text field in the main timeline (the particle’s parent). This step is discussed in detail in the upcoming note, “Manipulating Text Fields.”
Lines 32 and 33 validate the argument values of col
and size
, assigning values if none are
found. ActionScript 2.0 has no built-in mechanism for assigning
default values in functions. As such, you must assign them
manually.
Further, line 36 checks for a value of undefined
in _count
and, if found, initializes the
property to 0. This demonstrates a change in the way ActionScript 3.0 handles default
values, and this is also why _count
wasn’t initialized in line
5.
Lines 41, 48, and 49 all access objects in the parent, in this case the main timeline.
The name of the particle, and a subsequent new line, are added to the text field in the main timeline (the particle’s parent). The text field is then scrolled to the bottom line so you can always see the newly added name.
Lines 53 through 62 create and play particle-specific sound.
A validation first tests to be sure a sound for this particle
doesn’t already exist, and that at least one sound has preloaded
(via the incremented counter in line 21). If so, a random number
is chosen from the current number of preloaded sounds (line 57)
and a new sound is created and stored in the snd
property within the current
particle. The random sound is then loaded and played upon load
completion.
(Many people consider it a best practice to group private and public methods (and properties). However, this method’s optional, and has been neglected in order to arrange this example in a slightly more linear fashion for easier explanation.
Lines 64 through 79 establish the independent behavior for each particle. For each enter frame event, lines 66 and 67 add the x and y velocity values to the particle’s location, line 68 rotates the particle 5 degrees, and line 69 reduces the alpha by 2 percent.
Lines 71 through 74 controls particle-specific audio. The volume and pan of the sound are set according to the particle’s alpha and x coordinate, respectively.
Finally, lines 76 through 78 remove the particle when its alpha is less than or equal to 0.
The last three lines of the class create a getter that
returns the value of the _count
property when requested, as seen in line 41 of the main timeline
frame script.
The following are the ActionScript 3.0 versions of the two previous scripts. Only language version-specific comments are included here so, if you’re unclear on overall functionality, see the equivalent ActionScript 2.0 section.
The following is the main timeline frame script.
1 import fl.controls.Button; 2 3 var imgURL:String = "bg.jpg"; 4 if (root.loaderInfo.parameters.imgURL) { 5 imgURL = root.loaderInfo.parameters.imgURL; 6 } 7 var txtFrmt:TextFormat = new TextFormat(); 8 txtFrmt.align = TextFormatAlign.RIGHT; 9 var txtFld:TextField = new TextField(); 10 txtFld.x = 380; 11 txtFld.y = 10; 12 txtFld.width = 100; 13 txtFld.height = 380; 14 txtFld.defaultTextFormat = txtFrmt; 15 addChild(txtFld); 16 17 var bg:Loader = new Loader(); 18 addChildAt(bg, 0); 19 bg.load(new URLRequest(imgURL)); 20 21 var snd:Sound = new Sound(); 22 snd.load(new URLRequest("../audio/bass_back.mp3")); 23 snd.addEventListener(Event.COMPLETE, onSoundLoaded, false, 0, ¬ true); 24 function onSoundLoaded(evt:Event):void { 25 var sndChannel:SoundChannel = new SoundChannel(); 26 sndChannel = evt.target.play(0, 100); 27 var sndTransform:SoundTransform = sndChannel.soundTransform; 28 sndTransform.volume = .1; 29 sndChannel.soundTransform = sndTransform; 30 evt.target.removeEventListener(Event.COMPLETE, onSoundLoaded); 31 } 32 33 var particles:Particles = new Particles(); 34 addChild(particles); 35 36 var controls:MovieClip = new MovieClip(); 37 controls.y = 360; 38 addChild(controls); 39 40 var siteLink:String = "http://www.learningactionscript3.com/"; 41 var linkBtn:SimpleButton = new BtnLink(); 42 linkBtn.x = 20; 43 controls.addChild(linkBtn); 44 45 linkBtn.addEventListener(MouseEvent.CLICK, onShowLink, false, 0, ¬ true); 46 function onShowLink(evt:MouseEvent):void { 47 navigateToURL(new URLRequest(siteLink), "_blank"); 48 } 49 50 var changePBtn:Button = new Button(); 51 changePBtn.x = 120; 52 changePBtn.label = "Change"; 53 controls.addChild(changePBtn); 54 55 changePBtn.addEventListener(MouseEvent.CLICK, onChangeParticle,¬ false, 0, true); 56 function onChangeParticle(evt:MouseEvent):void { 57 var p:MovieClip = MovieClip(particles.getChildByName(¬ "particle" + (particles.count - 1))); 58 if (p != null) { 59 p.graphics.clear(); 60 p.graphics.beginFill(0xFF0000); 61 p.graphics.drawRect(-20, -20, 40, 40); 62 p.graphics.endFill(); 63 particles.particleSound(p); 64 } 65 }
The following is an explanation of ActionScript 3.0-specific issues that appear in the main timeline frame script.
Import
Notice in Line 1 that the class path has changed from
mx
to fl
.
FlashVars
FlashVars
are no longer
stored as global variables in the root timeline. Instead, they’re
stored in the parameters
object
of the LoaderInfo
instance of
the root, as seen in line 4.
TextFormat
Although you can use appropriate string values, it is a best
practice to use relevant constants for many property values in
ActionScript 3.0. In this case, the format’s align
property is populated with the
RIGHT
constant of the TextFormatAlign
class, used in line
8.
The application of the TextFormat
instance in line 14 has been
changed from setNewTextFormat()
to defaultTextFormat()
.
TextField
All display objects are created with a simple consistent
new
<class
name>()
structure. The ActionScript 2.0 TextField creation method is
replaced with the ActionScript 3.0 instantiation (line 9) and
followed by the assignment of property values (lines 10 through
14).
The ActionScript 3.0 display list automatically handles
depth management so you don’t have to manually assign levels or
worry about methods like getNextHighestDepth()
or the DepthManager
. As such, you don’t see any
level assignments in any of the object instantiation
routines.
However, you can still control depths. For example, you
still have a swapDepths()
method for moving the background image below the text field, as
seen in line 14 of the ActionScript 2.0 main timeline frame script
code. However, you have an easier way to handle this when objects
are added to the display list. In ActionScript 2.0, existing
objects are replaced when a new object is added to the same level.
ActionScript 3.0, however, moves all objects above the target
level one level higher, and then inserts the addition where
specified.
Therefore, you can easily place the background image behind
the text field when the image is added, as seen in line 18. The
addChildAt()
method is used,
specifying level 0. The background image appears in level 0, and
the text field is moved to level 1.
While you can in some cases omit typing a variable, all
variables must be declared with the var
identifier.
Rather than creating an empty movie clip, a Loader
display object is used lines (17
through 19). Instead of using the image path as a string for the
load()
method, ActionScript 3.0
requires a consistent use of the URLRequest
class for processing
the URL prior to use.
Although the creation of the Sound
instance is the same (line 21),
sound management diverges significantly from that point on.
Loading is similar, with a change of method name to the more
consistent load()
and the
ever-needed URLRequest
instance
instead of a string. The event handling is significantly different
in ActionScript 3.0 (and is explained in a moment), but the idea
behind it, as it pertains to sound, is the same: wait until the
sound is loaded, and then proceed.
However, three new classes play a big part of sound
management. First, each sound is typically played into its own
discrete sound channel, an instance of the SoundChannel
class (lines 25 and 26).
This step is a requirement if you wish to perform sound
transformations. Where the volume
and pan
properties existed in the Sound
class in ActionScript 2.0, they’re
now accessible through the soundTransform
property of the SoundChannel
class.
To effect such a change, an instance of the second new
class, SoundTransform
is
derived from the SoundChannel
soundTransform
property (line 27), the desired property
is changed (volume
, line 28)
and the new instance is reapplied to the SoundChannel soundTransform
property
once again (line 29).
The third new class, not used in this example, is the
SoundMixer
class. This class
lets you manipulate all the sounds at once. The isolation of
sounds into their own discrete channels lets you control each
sound separately and with greater precision.
Event handling is very different in ActionScript 3.0. For detailed information, see Chapter 14. From a migration standpoint, event handlers are no longer attached to the target of the event. Instead, event listeners are created, specifying an event to listen for, and a function to trigger upon an occurrence of that event (lines 23 through 31).
A mandatory parameter is used to receive information from
the event that can be used inside the function. For example, the
target of the event in the mentioned listener is the snd
object. That is referenced by
evt.target
in lines 26 and
30.
The events are specified as constants, as discussed
previously with the TextFormat
align property, and optional parameters allow more granular
control over when the event is processed (capture or
target/bubbling phases and priority) as well as whether weak
references are used for a little backup help in the memory
management department.
Finally, you should remove the listener when you no longer need it, for optimal memory management (line 30). You also find event listeners in lines 45 through 48, and 55 through 65.
Void is now lower case (lines 24, 46, 56).
Percent value scales are now from 0 to 1 (instead of 0 to 100).
ActionScript 3.0 lets you much more easily use custom
classes as display objects. You don’t need to rely on a
Library-based symbol, or more convoluted methods, to instantiate
the class. Instead, provided the class extends MovieClip
, Sprite
, Shape, or another applicable
display object, you just need to instantiate it the way you would
any other display object: new
<classname>();
(line 33). You must then add the
instance to the display list for the user to see it.
Another movie clip is dynamically generated in line 36. Note
the simplicity of creating an empty movie clip container (to hold
buttons). Rather than using one of many methods, such as createEmptyMovieClip()
, the consistent
new MovieClip()
approach is all
you need, coupled with the addChild()
method on line 38.
Line 37 is one example of the fact that ActionScript 3.0 properties are not preceded by an underscore.
The same custom button used in the ActionScript 2.0 version
can be instantiated here as a proper button (SimpleButton
, line 41) rather than using
a MovieClip
method and typing
the instance as a MovieClip
or
Object
. It’s then positioned,
and added to the display list (lines 42 and 43, respectively).
In this case, however, the SimpleButton
class is a sealed class, so
you can’t add the site URL used by the button as a property. In
this case, it’s stored in a standard variable.
You could have brought these two examples into a more
parallel structure by using movie clips for buttons in both cases,
because MovieClip
is a dynamic
class and would allow the addition of a property. However, the
purpose of this chapter is not to change the way you want to work,
but to understand how best to migrate a legacy project to the new
syntax of ActionScript 3.0.
Taking advantage of the new SimpleButton
class is desirable, and
even lets you create a button entirely from code (no Library
assets) if preferred.
To see an exact parallel, you can add an example property to the text field instance in both versions of the project. In the ActionScript 2.0 version, adding
txtFld.inUse =
true;
after line 10 works. However, adding the same line in the
ActionScript 3.0 version after line 15 generates an error because
the TextField
class is sealed
in ActionScript 3.0.
Line 47 shows the new syntax for accessing a URL, using the
navigateToURL()
method and
URLRequest
instance.
Adding a component to your project on the fly is really no
different from adding a movie clip or other display object. Just
use the Button
class as you
would another display object class, as seen in line 50. (As with a
custom asset, a Button component must be in your
Library.)
You can’t access a dynamically created object directly by
instance name. That is, setting the name property in ActionScript
3.0 doesn’t make it possible to reference the object using the dot
syntax object model. Instead, you must use the getChildByName()
method, as seen in line
57.
Although the clear()
,
beginFill()
, and endFill()
methods are the same in
ActionScript 3.0, they’re methods of the Graphics
class, accessed through the graphics
property instance of each
relevant display object. Further, you don’t need a custom function
to draw a rectangle, as the new drawRect()
method does that for
you.
The Particles
class
functionality is the same in ActionScript 3.0. It creates each particle,
establishes its behavior, and ultimately removes it from the
project.
1 package { 2 3 import flash.display.*; 4 import flash.events.*; 5 import flash.media.*; 6 import flash.net.*; 7 8 public class Particles extends Sprite { 9 10 private var _count:int; 11 private var _soundNum:int = 0; 12 private var _tempSound:Sound; 13 14 public function Particles() { 15 addEventListener(Event.ADDED_TO_STAGE, onAdded, ¬ false, 0, true); 16 addEventListener(Event.ENTER_FRAME, onLoop, false, ¬ 0, true); 17 _tempSound = new Sound(new URLRequest( ¬ "../audio/note0.mp3")); 18 _tempSound.addEventListener(Event.COMPLETE, ¬ onSoundPreloaded, false, 0, true); 19 } 20 21 private function onAdded(evt:Event):void { 22 x = this.stage.stageWidth / 2; 23 y = this.stage.stageHeight / 2; 24 removeEventListener(Event.ADDED_TO_STAGE, onAdded); 25 } 26 27 private function onSoundPreloaded(evt:Event=null):void { 28 _tempSound.removeEventListener(Event.COMPLETE, ¬ onSoundPreloaded); 29 if (_soundNum < 7) { 30 _soundNum++; 31 _tempSound = new Sound(new URLRequest(¬ "../audio/note" + _soundNum + ".mp3")); 32 _tempSound.addEventListener(Event.COMPLETE, ¬ onSoundPreloaded, false, 0, true); 33 } 34 } 35 36 private function onLoop(evt:Event):void { 37 makeParticle(0x0066CC, Math.random() * 10 + 10); 38 } 39 40 private function makeParticle(col:uint=0x003366, ¬ size:Number=20):void { 41 if (Math.random() * 10 <= 2) { 42 var p:MovieClip = new MovieClip(); 43 44 if (isNaN(_count)) { _count = 0; } 45 p.name = "particle" + _count; 46 _count++; 47 48 p.graphics.beginFill(col); 49 p.graphics.drawRect(-size/2, -size/2, size, size); 50 p.graphics.endFill(); 51 52 p.xVel = Math.random() * 10 - 5; 53 p.yVel = Math.random() * 2 - 1; 54 55 p.addEventListener(Event.ENTER_FRAME, ¬ onRunParticle, false, 0, true); 56 addChild(p); 57 58 MovieClip(parent).txtFld.appendText(p.name + " "); 59 MovieClip(parent).txtFld.scrollV = ¬ MovieClip(parent).txtFld.maxScrollV; 60 } 61 } 62 63 public function particleSound(p:MovieClip):void { 64 if (!p.snd && _soundNum > 0) { 65 var num:int = int(Math.random()*_soundNum); 66 p.snd = new Sound(new URLRequest(¬ "../audio/note" + num + ".mp3")); 67 p.channel = new SoundChannel(); 68 p.channel = p.snd.play(); 69 } 70 } 71 72 private function onRunParticle(evt:Event):void { 73 evt.target.x += evt.target.xVel; 74 evt.target.y += evt.target.yVel; 75 evt.target.rotation += 5; 76 evt.target.alpha -= .02; 77 78 if (evt.target.snd) { 79 var trans = evt.target.channel.soundTransform; 80 trans.volume = evt.target.alpha / 10; 81 trans.pan = (evt.target.x / this.x) * 2; 82 evt.target.channel.soundTransform = trans; 83 } 84 85 if (evt.target.alpha <= 0) { 86 if (evt.target.snd) { 87 evt.target.channel.stop(); 88 } 89 evt.target.removeEventListener(Event.ENTER_FRAME, ¬ onRunParticle); 90 removeChild(MovieClip(evt.target)); 91 } 92 } 93 94 public function get count():int { 95 return _count; 96 } 97 } 98 }
As with the ActionScript 2.0 section, basic syntax issues discussed in the main timeline won’t be mentioned again.
Line 1 shows that all ActionScript 3.0 classes must be
enclosed in a package
statement. This line
would also be where you would include a path to the class, if
desired. Lines 8 and 14 remain consistent with ActionScript
2.0.
Lines 3 through 6 import all the classes to make them accessible to the compiler. Unlike ActionScript 2.0, even classes in Flash Player must be imported.
Lines 10 through 12 are consistent with ActionScript 2.0.
Lines 10 and 11 use the int
data type because you don’t need
float values. You can also see this characteristic in lines 40 and
65. 40 is a good example, as the uint
data type is used, because a color
value can’t be negative.
Much has been made of the performance of the uint
data type and, to a lesser degree,
the int
data type, so you can
decide whether or not to use them. This is just an example.
Unlike ActionScript 2.0, the stage isn’t a global object.
Instead, you must access the stage through a display object. The
Particles
class both extends
MovieClip
, and is added to the
display list in the main timeline frame script, so you can access
the stage without passing a reference to it through the
constructor.
However, you can access the stage only after the display
object has been added to the display list. Therefore, this class
can’t access the stage within its constructor, as the class hasn’t
yet been fully initialized. Instead, a new event listener is added
to listen for the Event.ADDED_TO_STAGE event (line 15). Once this
event fires, the display object is part of the display list, and
the stage reference doesn’t return null
.
Lines 21 through 25 contain the function used for this
purpose and, because the listener is no longer necessary, it’s
removed upon execution of this function. (The this
keyword is not strictly needed
because the relevant scope is the class itself, but it’s been
added to emphasize that you’re accessing the stage through a
display object.)
The use of the enter frame event for the class is the same;
however, note that, because event handlers no longer exist, you
can’t just name a function onEnterFrame()
and expect it to work.
You must convert that structure to an event listener design, seen
in lines 16 and 36 through 38.
You no longer need the Delegate
class, as ActionScript 3.0
supports method closures.
The sound preloading routine hasn’t changed, and doesn’t
include any ActionScript 3.0 syntax issues that haven’t already
been discussed, with one small exception. If you pass a valid
URLRequest instance to the Sound
class constructor, as in line 31,
the load()
method is
automatically called.
It’s also a good idea to look this method over with regard
to removing listeners. It’s important to remove the load complete
listener from _tempSound
after
each sound has been loaded (or, if you prefer, after the last
sound has loaded) to prevent the listener from remaining on the
last sound.
ActionScript 3.0 has nothing unique in the makeParticle()
method that hasn’t been,
or won’t be, discussed elsewhere. However, be sure to read about
changes to default values, accessing objects in the parent, and
using the Drawing API (now commonly referred to as the Graphics
class).
ActionScript 3.0 allows the assignment of default values to method arguments, as seen in line 40. This action makes the associated arguments optional, but all optional arguments must appear at the end of the method signature.
Further, default values for data types have changed in
ActionScript 3.0. For example, line 44 can no longer test for
undefined
, as the default value
for number data types is NaN
(not a number). As such, you must use the isNan()
method to validate its
value.
As is true with many intentionally injected migration issues in this exercise, this could have been handled a different way. This property could have been initialized in line 10, for example, but was not so this issue could be discussed.
In Lines 58 and 59, the particle must cast the type of its
parent before it can access the parent’s methods or properties.
Without this step, the compiler knows only that the parent’s a
display object container, but not what kind. The compiler,
therefore, doesn’t recognize the txtFld
property of the parent.
When cast to a MovieClip
,
however, the compiler knows that MovieClip
is a dynamic class and can,
therefore, have custom properties. It then looks for txtFld
in the parent. and finds the text
field you created.
When adding text to the text field (line 58), the appendText()
method was used, as it’s
much faster than the compound operator +=
. Furthermore, the property scrollV
must be updated to the value of
maxScrollV
(line 59).
Nothing about the particleSound()
method that is unique to
ActionScript 3.0 hasn’t already been discussed. Line 64 checks to
make sure the particle’s Sound
instance hasn’t already been created, and that _soundNum
has been incremented to be
sure the sounds have preloaded. Line 65 creates a random number
within the count of available sounds, line 66 creates an instance
of the Sound
class and loads
the sound, and lines 67 and 68 create a SoundChannel
instance and play the
sound.
The behavior of the ActionScript 3.0 particles isn’t unique,
but a few very important concepts should be discussed. To begin,
the first number of the product used for the sound transformation
pan
value is calculated using
the particle’s x
divided by the
Particles
class’ x
(line 81). This step’s in contrast to
the ActionScript 2.0 calculation, which divides the first number
by the class’s parent’s x
value (line 73 of the ActionScript 2.0
class code). ActionScript 2.0 requires the Library movie clip to
instantiate the class this way, so the movie clip must be
referenced in the calculation. ActionScript 3.0 lets you add this
class to the display list directly, so only the class needs to be
referenced.
Next, you must stop the sound, and remove the event listener, before removing the particle. Otherwise, the particle and its attendant objects, (such as listeners) won’t be collected by the garbage collector and purged from memory.
The compiler must be told that the object is a MovieClip
to prevent an error from
occurring, because the compiler sees only the target of the method
as an Object
that may or may
not be removable. However, this issue has already been addressed
in the “Accessing Objects in the Parent (Type Casting)” section of
this discussion.
Nothing unique about the count()
getter method is unique to
ActionScript 3.0 hasn’t already been discussed
This is a small example of one possible migration path used to update a legacy project to ActionScript 3.0. Although awkward coding choices were made to show the largest number of migration issues practical in this size example, the exercise is still relatively close to a real-world scenario.
Having read this chapter, you may want to see if you can migrate this example on your own. Once you try the process a few times, you’ll have a pretty good idea of what you need, and you can evaluate the effectiveness of migration on a case-by-case basis. Depending on the extent of the changes, you may wish to use the old project as a kind of template, and then code the new version from scratch.