Motion: Chapter 7 - Learning ActionScript 3.0
Pages: 1, 2, 3, 4

When these initializations are complete, it is safe to add the ball movie clip to the display list and, to round out the xmlLoaded() function, create another listener to react to the end of the animation. The listener's function, in lines 22 through 24, simply resets the name of a Play button, which we will discuss in the next code block.

14  function xmlLoaded(evt:Event):void {
15      var anim_xml:XML = XML(xml_loader.data);
16      anim = new Animator(anim_xml, ball);
17     ball.transform.colorTransform = anim.motion.keyframes[0].color;
18     addChild(ball);
19     anim.addEventListener(MotionEvent.MOTION_END, onMotionEnd,
       false, 0, true);
20  }
21
22  function onMotionEnd(evt:MotionEvent):void {
23      Button(getChildByName("Play")).label = "Play";
24  }

The following segment of ActionScript is responsible for creating all the buttons that will control the animation. The createController() function walks through a loop that creates as many buttons as are named in the array that is passed to it. Each time through the loop, an instance of the Button component is created and positioned, its width is adjusted, and its label and name are set to the string in the current index of the function array. Lastly, a mouse click listener is added to the array to trigger the function responsible for navigation, and the button is added to the display list. This process takes place five times, to match the five button names in the array passed to the function.

25  createController(["Play","Pause","Stop","Next Frame","Toggle
    Scale"]);
26
27  function createController(btns:Array):void {
28      for (var i:Number = 0; i<btns.length; i++) {
29          var btn:Button = new Button();
30          btn.x = 35 + i*100;
31          btn.y = 350;
32          btn.width = 80;
33          btn.label = btns[i];
34          btn.name = btns[i];
35          btn.addEventListener(MouseEvent.CLICK, onNav, false, 0,
            true);
36          addChild(btn);
37      }
38  }

The last function in the script handles all the navigation for the animation. When a button is clicked, the listener calls this function, passing information about the event and, by extension, the button itself. Based on the name of the button, one of five blocks in a switch statement executes, invoking methods from the Animator class, as well as other tasks. If you are unfamiliar with the switch statement, please consult Chapter 2 for more information.

The Play button first confirms that the animation is not already playing and, if it is not playing, checks to see whether it's paused. If so, it resumes playback and clears the isPaused flag. If not, it plays the animation from the beginning. The Pause button pauses the animation and sets the isPaused flag. It also switches the label of the Play button to reflect the animation's paused status. The Stop button stops and rewinds the animation, clears the isPaused flag, and sets the label of the Play button back to "Play." The last playback control simply advances the animation to its next frame.

39   function onNav(evt:MouseEvent):void {
40       switch (evt.target.name) {
41          case "Play" :
42              if (!anim.isPlaying) {
43                 if (isPaused) {
44                     anim.resume();
45                     isPaused = false;
46                 } else {
47                     anim.play();
48                 }
49             }
50             break;
51         case "Pause" :
52             anim.pause();
53             isPaused = true;
54             Button(getChildByName("Play")).label = "Resume";
55             break;
56         case "Stop" :
57             anim.stop();
58             anim.rewind();
59             isPaused = false;
60             Button(getChildByName("Play")).label = "Play";
61             break;
62         case "Next Frame" :
63             anim.nextFrame();
64             break;
65         case "Toggle Scale" :
66             var m:Matrix = anim.positionMatrix = new Matrix();
67             var s:Number;
68             if (isScaled) {
69                 s = 1;
70                 isScaled = false;
71             } else {
72                 s = .5;
73                 isScaled = true;
74             };
75             MatrixTransformer.setScaleX(m, s);
76             MatrixTransformer.setScaleY(m, s);
77             break;
78     }
79 }

The final button in the controller just begins to hint at some of the most interesting things you can do to the preexisting animations. The Animator class has a property called the positionMatrix that allows you to alter the animation as a whole. It can be shifted, scaled, rotated, and/or skewed without otherwise distorting its appearance. The final controller button toggles the animation between full- and half-scale, the paths of which are both visible in Figure 7-11. If the animation is already scaled, the Toggle Scale button will toggle the scale value between full- and half-size, and set the isScaled flag accordingly. Finally, the code uses the static MatrixTransformer class—which automatically adjusts a matrix for you to reflect your desired changes—invoking the setScaleX() and setScaleY() methods to scale the entire animation.

With the ability not only to control, but also to easily transform, a potentially complex timeline tween entirely through ActionScript, the Animator class has a lot of potential for both utility and creativity. Many animations can be lovingly crafted and tweaked in the timeline and can be played back anywhere through code, again and again—even swapped and loaded from external sources at runtime. Entire libraries of highly portable animations can be created and stored in efficient XML formats. Even if you entirely focus on ActionScript, the fl.motion package, in which both the Motion and Animator classes reside, may be worth a look.

Section 7.6: Particle Systems

Example
Figure 7-12: A particle system with a gravity setting of 2 and .2

Particle systems are a way of simulating complex objects or materials that are composed of many small particles, such as fluids, fireworks, explosions, fire, smoke, water, snow, and so on. Complex systems are achievable because individual particles are generated, each is given its own characteristics, and each behaves autonomously. Further, the particles themselves are typically easy to adjust, or even replace, making it possible to alter the appearance or functionality of the system relatively easily. These are also characteristics of object-oriented programming, so it's not surprising that particle systems are often written using this approach.

To end this chapter, we'd like to create a very simple particle system—using only two classes—which looks a little bit like a psychedelic water fountain. Color circles shoot up out of the "fountain" and then fall down under the effect of gravity. shows what the system looks like.

The entry point to the particle system is the document class ParticleDemo. After declaring variables to determine the point at which the particles will appear, all the class constructor does is add an enter frame event listener to the stage. Upon every enter frame event, the listener function creates a new instance of the Particle class, which creates a particle and adds it to the display list.

1  package {
2
3      import flash.display.Sprite;
4      import flash.events.Event
5
6      public class ParticleDemo extends Sprite {
7
8         private var emitterX:Number = stage.stageWidth/2;
9         private var emitterY:Number = stage.stageHeight/2;
10
11          public function ParticleDemo() {
12             stage.addEventListener(Event.ENTER_FRAME, onLoop,
            false, 0, true);
13         }
14
15         private function onLoop(evt:Event):void {
16             var p:Particle = new Particle(emitterX,
17                                           emitterY,
18                                         Math.random()*11 − 6,
19                                         Math.random()*−20,
20                                         1,
21                                         Math.random()*0xFFFFFF);
22             addChild(p);
23         }
24     }
25 }

Five parameters pass to each class when its particle is created. The first two are the x and y coordinates of the particle emitter which dictate the origin location for each new particle. The next two parameters are randomly chosen values for the x and y velocities. The first value is for the x velocity, and is between −5 and 5. That's because the range of numbers is 0 to 11, but subtracting 6 offsets the selected value. The y velocity is between 0 and −20, so particles start by moving up. Next are a gravity value of 1 and a random color for each particle in the range of black (0x000000) to white (0xFFFFFF).

To see how the particle generator works, look within the Particle class. The first 13 lines cover the required setup, including importing the display and geom packages, and the Event class from the events package. Also included are the declaration of the position, velocity, and gravity variables that are private to this class.

1 package {
2
3     import flash.display.*;
4     import flash.geom.*
5     import flash.events.Event;
6
7     public class Particle extends Sprite {
8
9         private var _xpos:Number;
10         private var _ypos:Number;
11         private var _xvel:Number;
12         private var _yvel:Number;
13         private var _grav:Number;

First, the class constructor populates the private variables with the parameters passed in from the document class described earlier. Next, it creates a particle from the Ball class in the library and adds it to the display list. The constructor then sets four values: the particle's x and y position, opacity, and x and y scale. The same range-plus-offset technique used for x velocity when creating the particle is also used here for scale. This allows a scale range up to 200 percent, but guarantees a minimum scale of 10 percent.

14          public function Particle(xp:Number, yp:Number, xvel:Number,
            yvel:Number, grav:Number, col:uint) {
15              _xpos = xp;
16              _ypos = yp;
17              _xvel = xvel
18              _yvel = yvel
19              _grav = grav;
20
21              var ball:Sprite = new Ball();
22              addChild(ball);
23
24              x = _xpos;
25              y = _ypos;
26              alpha = .8;
27              scaleX = scaleY = Math.random() * 1.9 + .1;
28
29              var colorInfo:ColorTransform = ball.transform.
                colorTransform;
30              colorInfo.color = uint(col);
31              ball.transform.colorTransform = colorInfo;
32
33              addEventListener(Event.ENTER_FRAME, onRun, false, 0,
                true);
34          }

Lines 29 through 31 assign the particle's color by way of the ColorTransform class. The first step in this process is to store the particle's default colorTransform information (which allows manipulation of the red, blue, green, and alpha channels of the color), retrieved from the particle's transform object. Next, line 30 changes the color property of the colorTransform to the new color passed into the col argument.

However, that property prefers a uint data type (non-negative whole number) and the initial random color selection made when creating the particle converted the color to a Number data type. Therefore, it's a good idea to cast the number back to uint with the uint() data casting method. Finally, once the color has been changed, update the particle's own colorTransform object using the one in the variable you've been manipulating.

The last line of the constructor adds an enter frame listener to control the particle's movement. It triggers the onRun() function, which follows. This function uses the techniques discussed in the velocity and gravity examples of this chapter, but adds one thing. A conditional determines whether the next particle position is off the stage on the left, top, right, or bottom edge. If so, the event listener is removed and the particle is removed from the display list, ready for garbage collection.

35          private function onRun(evt:Event):void {
36              _yvel += _grav;
37              _xpos += _xvel;
38              _ypos += _yvel;
39              x = _xpos;
40              y = _ypos;
41
42              if (xpos < 0 || ypos < 0 || _xpos >
                stage.stageWidth || ypos > stage.stageHeight) {
43                 removeEventListener(Event.ENTER_FRAME, onRun);
44                 parent.removeChild(this);
45              }
46          }
47      }
48  }

Particle systems are a lot of fun and can lead to many fruitful experiments. Run this system several times, modifying the values sent to the Particle class. Change the x and y velocities for a larger spread of particles, decrease the force of gravity to see what particle life is like on the moon, or even set the emitter location to the mouse location to move the system around.

Try to move some of the hard-coded properties, like alpha, scaleX, and scaleY into the argument list so they can be varied, too. As an example, we've created another version of this system for the book's companion web site that includes several new properties, including filter and blend mode settings that you'll learn about in the next chapter.

Project Package

The project package for this chapter includes several of the basic formulas covered herein, including the degree-to-radian and radian-to-degree conversion, Zeno's paradox, Hooke's law, and more. These formulas apply to the project but can also be used in your own work.

Section 7.7: What's Next?

While this chapter details a variety of ActionScript animation techniques, it only begins to cover the subject of motion through code. However, the basic building blocks are here, and it's with these concepts (and related skills that grow from the ideas herein) that greater art and industry can be achieved.

Next on the to-do list is the ability to partially free yourself from the constraints of the Flash interface and approach code-only projects with a little more latitude. When working with visual assets, we've so far relied on symbols created within Flash and stored in the library of a SWF. We will continue to do that any time complex art warrants this practice, but we'll also begin to work with vectors and bitmaps created with code. In addition to giving you more freedom, this approach can also reduce file size and make your SWFs load faster.

In the next chapter, we'll discuss:

  • Using the Graphics class to draw vectors to create assets on the fly without contributing to file size
  • Calling methods of the flash.geom package to use rectangles and points in your scripts
  • Using 9-slice scaling to achieve distortion-free symbol instance scaling

This excerpt is from Learning ActionScript 3.0. Learning ActionScript 3.0 gives you a solid foundation in the Flash language and demonstrates how you can use it for practical, everyday projects. The book does more than give you a handful of sample scripts, defining how ActionScript and Flash work. It gives you a clear look into essential topics such as logic, event handling, displaying content, migrating legacy projects to ActionScript 3.0, classes, and much more. Written for those new to the language, this book doesn't rely exclusively on prior knowledge of object-oriented programming (OOP). Instead, it helps you expand your skillset by first focusing on clear, concise examples in the timeline, evolving into OOP examples over time-allowing you to choose the programming approach with which you are most comfortable.

buy button