Chapter 7. Motion

IN THIS CHAPTER

Basic Movement

Simple Physics

A Basic Particle System

Simple Collision Detection

Geometry and Trigonometry

Programmatic Tweening

What’s Next?

From your very first experiment to the umpteenth time you’ve performed a familiar task, moving assets with code can be very gratifying. In addition to creating more dynamic work by freeing yourself from the permanency of the timeline, there is something very immediate and pleasing about controlling the motion of an object purely with ActionScript.

Because programming motion can cover many concepts, we’ve chosen to focus on a few key topics in this chapter. For each group of ideas we introduce, we offer what we call simplified simulations—that is, we don’t maintain that our examples accurately reflect real-world scenarios. When discussing physics, for example, we won’t be accounting for every force that can act on an object. On the contrary, we’ll try to provide simple implementations that you can easily integrate into your own projects.

We also hope to show that math can be your friend. To some of you, this may be a given, but to others, math can be a little intimidating. If you’re in the latter group, we hope to reduce what may be a knee-jerk reaction to nothing more than working with numbers. Understanding just a few practical applications of mathematical or scientific principles can really go a long way. Before you know it, you’ll be creating what seem like complex animations with little effort. You’ll be building grids of movie clips, animating planets in elliptical orbits, shooting projectiles in games, building novel navigation systems, and more.

In this chapter, we’ll look at the following topics:

  • Basic Movement. We’ll start with simple movement, updating x and y coordinates using velocity, acceleration, and easing.

  • Physics. Gravity, friction, and elasticity add a bit of realism to animations, and you may be surprised how easy they are to simulate.

  • A Basic Particle System. Learning by doing is important, so we’ll combine movement, physics, and object-oriented programming to create a class-based particle system.

  • Collision Detection. Next we’ll discuss how to detect collisions with other objects, points, and the boundaries of the stage.

  • Geometry and Trigonometry. Even basic geometric and trigonometric principles can make animated objects move in wonderful ways. We’ll show you how to determine the distance between two points, how to move an object along a specific angle, how to animate objects in a circular path, and how to rotate objects to point at a specific location. We’ll also combine some of these skills to create a novel navigation widget and another basic particle system.

  • Programmatic Tweening. Scripting movement entirely from scratch affords the greatest flexibility but also requires a fair amount of labor. Sometimes, a prewritten method or two can satisfy a basic need for motion. We’ll demonstrate ActionScript’s Tween class and its occasional partner in crime, the Easing package.

  • Using a Tweening Engine: TweenLite. Finally, we’ll show you an alternative to ActionScript’s built-in tweening options (and, at the same time, give you some experience using third-party ActionScript 3.0 packages) by introducing the fabulous TweenLite tweening engine.

Basic Movement

When discussing scripted motion, a good place to begin is simple movement through updating x and y coordinates of a display object. Whether you realize it or not, you’re probably already used to thinking of points in two-dimensional space as x and y coordinates. However, you’re probably used to thinking about positive x values moving to the right and positive y values moving up, the way simple graphs are usually expressed.

The Flash coordinate system differs a bit from the typical x-, y-coordinate system with which you may be familiar. The x values still increase to the right, but the (0, 0) point of the stage is in the upper-left corner, and the y values increase when moving down. This becomes important when you want an object to travel up, because you must subtract from the y property. For example, if a MovieClip starts at an x, y position of (100, 100), getting it to move up by 10-pixel increments means changing its y property to 90, 80, 70, and so on. This inverted y axis also makes a difference when creating practical user interface elements, such as sliders. We’ll create a slider in Chapter 11 to control sound volume, in which the inverted y value plays a part.

To increase or decrease a value, you simply add to, or subtract from, the previous value. You may recall from Chapter 2 that the increment operator, two plus signs (++), are equivalent to value = value + 1, and two minus signs (--) represent value = value − 1. We also discussed the compound assignment operators += and -=, which add or subtract (respectively) whatever is on the right of the equal sign from the existing value of the variable or property on the left of the equal sign. Assuming two movie clips begin at (0, 0), where will they be after this code?

mc.x++;
mc.y−−;
mc2.x += 10;
mc2.y −= 10;

The mc movie clip ends up at (1, −1) and the mc2 movie clip ends up at (10, −10). In this chapter, you’ll be moving objects around the stage in this way, as well as by using more involved formulas and variables. To give you some perspective on what lies ahead, it will help to understand the terms speed, velocity, and acceleration, which we use throughout the chapter:

Speed

Speed, or how fast an object is moving, is a scalar quantity, which means it’s a value that can be expressed with magnitude alone. That is, you can drive your car at 60 miles per hour, but that speed doesn’t imply a direction. We can create a variable or object property called speed, but it won’t help animate a display object until we add a direction to the mix.

Velocity

Velocity is a constant speed of motion, but adds direction. It’s called a vector quantity because it’s expressed with both magnitude and direction. One easy way to do this when animating objects in ActionScript is by referring to the x and y properties of a display object. For example, moving a movie clip 20 pixels in the x direction sends it to the right, representing speed and direction.

Acceleration

Acceleration is the rate of change in velocity and is also a vector quantity, requiring both magnitude and direction. For example, an object that accelerates to the right has an ever increasing change in its x property.

These distinctions may be subtle, but they are helpful when understanding how to get from point a to point b. You certainly don’t have to memorize them, but understanding the basics of each term will help you plan, and even troubleshoot, your code. If a display object is moving at a constant rate of speed when it’s meant to move faster over time, you may realize that you forgot to add acceleration before updating your x or y values.

Velocity

Starting out, velocity will be expressed as an increase or decrease of x and y coordinates. Later on, we’ll show you how to move an object using an angle but, for now, remember that incrementing the x property by 4 means a velocity of 4 pixels to the right. Breaking this update into separate components for position and velocity can make this clearer and easier to work with—particularly when additional factors enter the equation such as acceleration, gravity, friction, and so on.

The following code, found in velocity.fla in the companion source files, creates a ball from a library movie clip with the linkage class, Ball. It then adds 4 pixels to the ball’s x and y coordinates each time the enter frame event occurs. This means the ball moves down and to the right, as depicted in multiple frames in Figure 7-1.

Simulated movement of a movie clip, at a constant velocity, down and to the right
Figure 7-1. Simulated movement of a movie clip, at a constant velocity, down and to the right

Note

See the Adding Custom Symbol Instances to the Display List section of Chapter 4 to review how to use a linkage class.

1    var ball:MovieClip = new Ball();
2    ball.x = ball.y = 100;
3    addChild(ball);
4
5    var xVel:Number = 4;
6    var yVel:Number = 4;
7
8    addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
9    function onLoop(evt:Event):void {
10       ball.x += xVel;
11       ball.y += yVel;
12   }

If the ball’s x velocity is 4, how far does the ball move along the x axis in one second? This an important question because the ball’s speed is dependent upon the FLAs frame rate. Because the function is executed every time an enter frame event is received, a frame rate of 20 frames per second (fps) yields a distance of 80 pixels per second—approximately one inch on a 72-pixel-per-inch monitor—in the x direction. Next let’s look at how to change that ball’s velocity.

Acceleration

Changing the velocity over time accelerates or decelerates an object. Consider a ball that moves 4 pixels in the x direction every time an enter frame event occurs; you can easily calculate the ball’s movement as 4 + 4 + 4 + 4 and so on. In 3 seconds the ball would travel 240 pixels (4 pixels per frame * 20 frames per second * 3 seconds). If we accelerate the object 1 pixel per enter frame event, however, the ball’s changing velocity would be 4 + 5 + 6 + 7, and so on. At the same frame rate of 20 frames per second, the ball would travel 2070 pixels in the same 3 seconds! Acceleration is the compound interest of motion.

Acceleration increasing the velocity over time, simulated by increased movement in each frame
Figure 7-2. Acceleration increasing the velocity over time, simulated by increased movement in each frame

Figure 7-2 illustrates the effect of acceleration by depicting an increasing distance traveled by a ball each time an update occurs. You can illustrate this change more dramatically by changing acceleration in only one direction. All you have to do is increment velocity by acceleration every time the function executes. The source file acceleration.fla demonstrates this idea by adding acceleration to the x velocity. This file augments the velocity example by adding lines 7 and 14, shown in bold:

1    var ball:MovieClip = new Ball();
2    ball.x = ball.y = 100;
3    addChild(ball);
4
5    var xVel:Number = 4;
6    var yVel:Number = 4;
7    var xAcc:Number = 1;
8
9    addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
10    function onLoop(evt:Event):void {
11        ball.x += xVel;
12        ball.y += yVel;
13
14       xVel += xAcc;
15    }

Easing

One of the biggest challenges to creating good animated sequences is bringing realism to your work. As any animator will tell you, this is a lifelong effort, but adding easing to your animation is a very quick way to take a big step towards that goal. Easing is so named because when used, an object appears to “ease in” to an animation, accelerating as the animation progresses, or “ease out” of an animation, decelerating as the animation finishes. As a crude real-world example, think about merging onto a highway. As you approach the highway from the on ramp, you slowly increase your speed looking for a chance to join the stream of vehicles. You continue to add acceleration, find an opening, and ease into the highway traffic.

Later in this chapter we’ll show you how to use preexisting easing equations but it’s very useful to first understand the basics behind easing. For one thing, writing your own simple easing code helps you learn more about programming motion. More importantly, however, integrating easing into your scripts is also more flexible. The most common use of easing is when adding it to tweens—sequences where the computer calculates the interim property values between starting and finishing frames. However, these starting and finishing values are typically preset in tweens. Writing your own easing code means you can add it to any other scripted motion, even when values are changing on the fly.

The simplest easing equation is a formula akin to Zeno’s paradox. This philosophical idea says that, when moving from one point to another, you never really reach your ultimate destination because you’re dividing the remaining distance with every movement. If you divide the distance between point a and point b in half with every step, theoretically, you could never reach point b. As a philosophical idea this may be interesting, but in practical terms, objects reach their intended destinations all the time. In fact, we can use a formula derived from Zeno’s paradox in animation, to slow down an object as it approaches its new location, as shown in Figure 7-3.

Zeno’s paradox, a simple way to depict friction or easing
Figure 7-3. Zeno’s paradox, a simple way to depict friction or easing

The following example, found in the easing.fla source file, demonstrates this principle; it first creates a ball movie clip from the library, and then calls the onLoop() function every enter frame. This updates the movie clip’s x and y coordinates every enter frame by calling the zeno() function, where the easing equation does its work:

1    var ball:MovieClip = new Ball();
2    ball.x = ball.y = 100;
3    addChild(ball);
4
5    addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
6    function onLoop(evt:Event):void {
7        ball.x += zeno(ball.x, mouseX, 2);
8        ball.y += zeno(ball.y, mouseY, 2);
9    }
10
11    function zeno(orig:Number, dest:Number, reducer:Number):Number {
12        return (dest - orig) / reducer;
13    }

The zeno() function starts by subtracting the starting location (the ball’s current x or y location) from the ending location (the mouse x or y location) to determine the distance that must be traveled. It then divides that distance by an amount used to slow the progress. Finally, it adds that diminished value to the current coordinates of the ball and the process begins again. The result is that every time you move the mouse, the ball begins moving again and slows down as it approaches the new mouse position.

In this example, the amount is cut in half every time simply to relate back to the explanation of Zeno’s paradox. Discussing only the horizontal position for simplicity, if the ball must move from an x coordinate of 100 to an x coordinate of 200, the first few updated positions are as follows. (Also shown are the formula values used to calculate each position.) The effect is that the ball eases in to the final destination.

starting point            :100
100 += (200 - 100) / 2    :150
150 += (200 - 150) / 2    :175
175 += (200 - 175) / 2    :187.5

You don’t always have to cut the remaining distance in half, of course, when using this formula. In fact, this is how you vary an animation’s deceleration. Numbers higher than 2 require more time for the object to reach its destination, and lower numbers finish the animation more quickly. The easing.fla source file in the companion source code demonstrates this by passing 8 into the reducer parameter of the zeno() function.

Best of all, every time you move the mouse in this example, the equation automatically adjusts because the starting and ending locations are dynamic. The starting point will always be the current location of the ball, and the ending point will always be the mouse location.

Note

Although the examples in this book are necessarily general and concise for tutorial purposes, you may sometimes want to add tolerance factors when applying them to your own projects. When easing, for example, you may want to add a conditional statement that removes an event listener when your display object comes close enough to your destination. This will eliminate unnecessary activity in your scripts, and you can also use the opportunity to snap your display object to your exact destination, if important.

The upcoming section A Basic Particle System shows a variant of this approach by removing a listener when a particle leaves the stage.

Simple Physics

In the quest for more expressive animation, you will find that adding physics to animations, games, and similar projects can really elevate them to another level of user enjoyment. The visual appearance and, in interactive scenarios, even the user experience of a project are sometimes dramatically enhanced by surprisingly small code additions.

We’re going to be discussing some basic physics principles in this section, but it’s more important for you to understand their effects than to focus minutely on the math and science behind them. This is because the formulas offered here are necessarily simplified, or even adapted, from their real-world counterparts. Once you’re comfortable with the principles in general, you can refine formulas, account for additional variables, and so on, to improve their realism. For example, it’s often helpful to first simulate the simple orbit of a planet before considering the orbit’s decay, the gravitational attraction of other bodies, and so on.

Gravity

What happens when you toss a ball into the air? It goes up, starts to slow down as gravity affects its rate of ascent, it stops momentarily at the top of its journey, and then the ball starts moving faster again as gravity starts to accelerate its trip downward.

If you think about it carefully, a simple ActionScript simulation of gravity requires little more than acceleration in the y direction. The following code, found in the gravity.fla source file, requires only minor changes to the previous acceleration example. Here we’ll focus on acceleration in the y direction, and we’ll start with a negative y velocity to start the ball moving upward:

Note

Remember that, in the ActionScript coordinate system, increasing y values move an object downward.

1    var ball:MovieClip = new Ball();
2    ball.x = ball.y = 100;
3    addChild(ball);
4
5    var xVel:Number = 4;
6    var yVel:Number = −10;
7    var yAcc:Number = 1;
8
9    addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
10    function onLoop(evt:Event):void {
11        ball.x += xVel;
12        ball.y += yVel;
13
14        yVel += yAcc;
15    }

The ball begins moving at 10 pixels per enter frame event, but acceleration adds 1 to the y velocity each iteration. As such, the velocity decreases from −10 to −9 to −8, and so on, slowing the ball’s ascent, just as if gravity were counteracting the upward force of the toss. Eventually, the y velocity reaches zero at the height of the toss, where the upward force and gravity reach equilibrium. Then, as we continue to add 1 to the y velocity, its value becomes 1, then 2, then 3, and so on, as the ball begins to accelerate downward due to the effect of gravity. Figure 7-4 shows the effect of the simulated gravity by depicting several frames of the animation at once. When a ball is tossed in the air, gravity slows its rate of ascent and then increases the rate at which it falls.

The effect of gravity on acceleration
Figure 7-4. The effect of gravity on acceleration

Note

To continue your exploration of gravity, velocity, and acceleration, visit the book’s companion website. The “More Motion (Gravity)” post includes a file called wall_bounce.fla that demonstrates all these concepts and adds several additional features. Included are conditionals to change the ball’s direction when hitting a stage boundary (which we’ll discuss in a moment), bounce behavior, and even a texture to simulate rotation during bouncing.

Friction

All other things being equal, if you slide a hockey puck along three surfaces—a street, a marble floor, and an ice rink—the puck will travel three different distances due to friction. Friction will be highest on the street, building up resistance to motion between the puck and the street surface, limiting the progress of the puck. Friction will be reduced on the marble surface, and lowest on the ice, allowing the puck to travel the farthest.

A simple way to add friction to an animation is to create a friction coefficient. A coefficient is a modifier that alters an object’s property, the way friction alters the speed of the hockey puck. It’s often a multiplier, which we’ll use in this example, multiplying by a value less than 1 to reduce an effect, or by a value grater than 1 to exaggerate an effect.

To demonstrate this, we’ll adapt the prior velocity and gravity examples to create the friction.fla source file. The example begins with x and y velocities of 10 in lines 5 and 6. Like the gravity example, we’ll update the velocity before adding it to the ball’s x and y properties. This time, however, instead of accelerating the ball in the y direction only, we’re going to decelerate the ball’s movement in both directions, as if friction was slowing its movement.

Remember that friction hinders movement, so you want to choose a friction value between 0 and 1 to slow down the motion. If you choose a value greater than 1, the motion would speed up, while a negative friction coefficient would move an object in reverse. Depending on the application, you can vary the number. Perhaps you might use 0.95 for ice, 0.90 for marble, and 0.60 for asphalt. With a friction coefficient in place in line 7, we can then multiply the x and y velocities by this value in lines 11 and 12. Then we can update the ball’s x and y positions in lines 13 and 14.

1    var ball:MovieClip = new Ball();
2    ball.x = ball.y = 100;
3    addChild(ball);
4
5    var xVel:Number = 10;
6    var yVel:Number = 10;
7    var frCoeff:Number = 0.95;
8
9    addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
10    function onLoop(evt:Event):void {
11        xVel *= frCoeff;
12        yVel *= frCoeff;
13        ball.x += xVel;
14        ball.y += yVel;
15    }

In addition to simulating friction, this formula is another type of easing. The big difference here is that you don’t need a final value for the formula to work. That is, in the previous Easing section, the formula diminished the distance between two known points by adding ever decreasing values to an object’s current location. In this case, all you need to know is the degree to which the velocities of an object will be reduced. Where that object ends up depends on the velocities and coefficients used.

Note

Developer extraordinaire Seb Lee-Delisle is developing an ActionScript animation library called Tweaser, based on easing coefficients that alter property values over time. Other animation libraries work by using the starting and ending points of the motion. Tweaser, on the other hand, works by using a starting point and an easing coefficient so you don’t have to have a final destination for the animated object. This adds a bit of freedom to the task of animation. Tweaser was in beta at the time of this writing, but you can learn more at http://www.tweaser.org.

Elasticity

The last simple physics principal we’ll look at is elasticity. Elastic properties can be applied to simulate springs, of course, but can also be used as yet another easing method.

The following example uses elasticity to settle a movie clip into a new location. The movie clip moves from a starting position to the mouse location, bouncing around the destination until settled. Figure 7-5 simulates this by showing that each successively larger position gets closer to the final location, indicated by the red crosshairs.

A basic depiction of easing using Hooke’s law of elasticity
Figure 7-5. A basic depiction of easing using Hooke’s law of elasticity

The ball in the figure overshoots the destination just like a spring, stopping at position 1. It then bounces back, but not quite as far, to position 2. This continues, bouncing to position 3, then 4, and ultimately settling at position 5.

Elasticity is calculated using Hooke’s law. Hooke’s law says that the force exerted by a spring is linearly proportional to the distance it’s stretched or compressed. It’s expressed with the formula F = −kx. F is the resulting force of the spring, −k is a spring constant (the strength of the spring, so different springs can have different elasticities), and x is the distance to which the spring is stretched or compressed. This formula determines the power of the spring but eventually all springs return to their original state due to conservation of energy. So we’ll also add a damping factor to reduce the bounce of the spring over time.

Note

Although not vital to this discussion, the elasticity equation is expressed as a negative because the force given off by the spring is not in the same direction as the force applied to the spring. This is called a restorative force because it helps restore a property to its prior value.

The following script, found in the elasticity.fla source file, starts as the prior examples have begun, by creating and adding a movie clip to the display list (lines 1 through 3), and initializing x and y velocity variables (lines 5 and 6). It then creates a listener in line 8, which calls the listener function in lines 9 through 14, every enter frame. In turn, the velElastic() function determines the x and y velocity of the movie clip, and the clip’s x and y properties are updated.

Passed to the function in lines 10 and 11 are the movie clip’s starting and ending positions, the spring constant and damping factor, and the current velocities that will be changed by the formula. The last part of the listener function includes updates to the x and y locations of the movie clip, using the newly calculated velocities. The elasticity calculation follows in the velElastic() function, which we’ll discuss after the code.

1    var ball:MovieClip = new Ball();
2    ball.x = ball.y = 100;
3    addChild(ball);
4
5    var xVel:Number = 0;
6    var yVel:Number = 0;
7
8    addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
9    function onLoop(evt:Event):void {
10        xVel = velElastic(ball.x, mouseX, 0.3, 0.8, xVel);
11        yVel = velElastic(ball.y, mouseY, 0.3, 0.8, yVel);
12        ball.x += xVel;
13        ball.y += yVel;
14    }
15    function velElastic(orig:Number, dest:Number,
16                        springConst:Number,
17                        damp:Number, vel:Number):Number {
18        var elasticForce:Number = -springConst * (orig - dest);
19        var newVelocity:Number = (vel + elasticForce) * damp;
20        return newVelocity;
21    }

All that remains is the elasticity calculation itself. Line 18 uses Hooke’s law to calculate the force of the spring by multiplying the spring constant (the strength of the spring) by the distance between the starting point and the mouse location (the distance the metaphorical spring is stretched). Line 19 calculates the new velocity affected by the spring. It adds the newly calculated elastic force to the velocity, but reduces the value due to conservation of energy. If this dampening effect were not in place, the spring would bounce infinitely.

Both the strength of a spring (the spring constant), and the dampening effect on its force, are arbitrary values that can be adjusted to fit the needs of your projects. In this example, each successive force of the spring will be only 80 percent (0.8) of the prior force.

A Basic Particle System

Now let’s combine several of the things you’ve learned—including velocity, acceleration, gravity, and object-oriented programming—to create a class-based project. 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 have their own characteristics and behave 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.

As you’re just getting started, this is a simple particle system using only two classes, which looks a little bit like a primitive water fountain. Blue circles shoot up out of the “fountain” and then fall down under the effect of gravity. Figure 7-6 shows what the system looks like, and you can get a look for yourself by testing the particle_system.fla source file.

A particle system simulating a primitive water fountain
Figure 7-6. A particle system simulating a primitive water fountain

The particle

The first step in creating the system is to create a Particle class, found in the Particle.as class file. This class will give life to each individual particle. Reviewing class syntax, line 1 creates the class package, lines 3 and 4 import the required classes, and line 6 declares the class and extends Sprite to inherit display object properties like x and y. Lines 8 through 12 declare the position, velocity, and gravity properties that are private to this class.

1    package {
2
3        import flash.display.Sprite;
4        import flash.events.Event;
5
6        public class Particle extends Sprite {
7
8            private var _xPos:Number;
9            private var _yPos:Number;
10           private var _xVel:Number;
11           private var _yVel:Number;
12           private var _grav:Number;

Next, the class constructor creates and initializes the particle. Lines 17 through 21 populate the private properties with values passed into the constructor when the particle is instantiated. These parameters all have default values, but our example will vary their values when creating each particle.

Next, the constructor adds visual content to the particle by creating an instance of the Ball class from the FLA library and adds it to the display list. This ball movie clip is nothing more than a blue circle with a radius of 20 pixels. Five particle properties are then populated in lines 26 through 29: x, y, alpha, scaleX, and scaleY, their values coming into the class during instantiation. The last line of the constructor adds an enter frame event listener to the particle.

13    public function Particle(xPos:Number=100, yPos:Number=100,
14                             scale:Number=1, opacity:Number=1,
15                             xVel:Number=4, yVel:Number=-10,
16                             grav:Number=1) {
17        _xPos = xPos;
18        _yPos = yPos;
19        _xVel = xVel;
20        _yVel = yVel;
21        _grav = grav;
22
23        var ball:Sprite = new Ball();
24        addChild(ball);
25
26        x = _xPos;
27        y = _yPos;
28        alpha = opacity;
29        scaleX = scaleY = scale;
30
31        addEventListener(Event.ENTER_FRAME, onRun,
32                         false, 0, true);
33    }

The event listener function, onRun(), uses the techniques discussed in the velocity and gravity examples of this chapter—first altering the y velocity with the effect of gravity, and then updating the x and y properties of the particle every enter frame. It also adds one new thing. A conditional statement determines whether the particle position is off the stage on the left or right (line 41), or top or bottom (line 42). If so, the event listener is removed in line 43, and the particle is removed from the display list in line 44.

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

Note, in line 44, that an object can’t directly remove itself using syntax like removeChild(this). A display object to be removed must be a child of the object calling the removeChild() method, and an object can’t be a child of itself. One way to remind yourself about this is to precede the method call with the optional this reference to clarify which object is calling the method. Ideally, writing this.removeChild(this) shows that this can’t be a child of this. Instead, the object instructs its parent to remove itself and, as the object is a child of its parent, the syntax works just fine.

Note

Because particle systems can create hundreds or even thousands of particles a second, it’s very easy to run out of memory if you don’t remove listeners, display objects, and particle storage (such as a variable or array).

The system

The following simple document class ParticleDemo is responsible for creating the particles. It creates a particle every time an enter frame event is received and adds it to the display list. The variance in the system comes from the values passed into the Particle class in the listener method onLoop().

1    package {
2
3        import flash.display.MovieClip;
4        import flash.events.Event;
5
6        public class ParticleDemo extends MovieClip {
7
8            public function ParticleDemo() {
9                addEventListener(Event.ENTER_FRAME, onLoop,
10                                  false, 0, true);
11           }
12
13           private function onLoop(evt:Event):void {
14               var p:Particle = new Particle(mouseX,
15                                             mouseY,
16                                             (Math.random()*1.8) + 0.2,
17                                             (Math.random()*0.8) + 0.2,
18                                             (Math.random()*10) - 5,
19                                             Math.random()*-10,
20                                           1);
21               addChild(p);
22           }
23       }
24    }

Recalling the signature of the Particle class, its parameters are xPos, yPos, scale, opacity, xVel, yVel, and grav. The corresponding order of arguments passed into the class when a particle is instantiated (starting in line 14), determine its appearance and behavior. To begin with, the particle is born at the mouse location (mouseX, mouseY).

Note

A signature describes a constructor or method by including its name; parameters, data types, and possible default values; and return data type. This lets a programmer know how to invoke the constructor or method.

The formulas for scale, opacity, xVel, and yVel are then randomized within specific ranges. The random() method of the Math class always generates a random number greater than or equal to 0 and less than 1. Therefore, to pick a random value greater than or equal to 0 and less than a number other than 1, you must multiply the decimal value generated by the desired maximum value. Jumping ahead to the y velocity, for example, the ultimate value will be greater than or equal to 0 and less than −10. If a range that does not start with 0 is desired, an offset must be applied.

For example, the scale value is not just a random number times 2. This may result in a scale of 0 and the particle would disappear. The 0.2 offset guarantees this will not happen. If the random number selected is 0 or very near 0, the minimum size of 0.2 will be used (0 + 0.2). If the random number chosen is near 1, the ultimate outcome is 2 (1.8 + 0.2). The opacity of the particle is determined the same way with the next formula, yielding a value between 0.2 and 1 (20 and 100 percent, respectively).

The x velocity is calculated in a similar manner, but this time the offset value is subtracted from the possible range of random numbers. If the random number is near 0, the resulting value is 0 minus 5, or −5. If the random number is near 1, the outcome will be 10 minus 5, or 5. Therefore, the possible x velocity values are between −5 and 5.

The last argument represents gravity, for which a constant value of 1 is used.

The FLA file

The particle_system.fla source file uses the ParticleSystem class as a document class, so there is no additional code therein. If you prefer not to use the document class approach, however, all you need to do is instantiate the ParticleSystem class and add it to the display list.

1    var ps:ParticleSystem = new ParticleSystem();
2    addChild(ps);

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. Increase the range of x and y velocities for a larger spread of particles, or decrease the force of gravity to see what particle life is like on the moon. Let your creativity flow.

Simple Collision Detection

Once you get your display objects on the move, you can add code that will react when objects collide. For example, games like pool, pinball, and platform scrollers wouldn’t be possible without collisions. We’ll show you three collision types in this section: collisions between two objects, between an object and a point, and between an object and the boundaries of the stage.

Collision with Objects

Collisions between two objects are detected using the hitTestObject() method. It determines whether the object calling the method collides with another object passed in as an argument in the method call. The following code, found in the collision_objects.fla source file, will remove two objects from the display list when they collide. This is handy, for example, when bullets hit spaceships and they must disappear. Lines 1 through 11 give us two balls and an event listener to work with. Every enter frame, line 12 moves the ball to the right, and line 13 checks to see if ball collides with ball2. If so, the listener is removed in line 14, and both ball and ball2 are removed from the display list in lines 15 and 16.

1    var ball:MovieClip = new Ball();
2    ball.x = ball.y = 100;
3    addChild(ball);
4
5    var ball2:MovieClip = new Ball();
6    ball2.x = 100;
7    ball2.y = 400;
8    addChild(ball2);
9
10   addEventListener(Event.ENTER_FRAME, onEnter, false, 0, true);
11   function onEnter(evt:Event):void {
12       ball.x += 5;
13       if (ball.hitTestObject(ball2)) {
14           removeEventListener(Event.ENTER_FRAME, onEnter);
15           removeChild(ball);
16           removeChild(ball2);
17        }
18    }

It’s important to note that the hitTestObject() method uses the minimum bounding rectangle of both objects to detect collisions. Figure 7-7 shows two circles that appear to not collide. However, the minimum bounding rectangles of the circles overlap and, therefore, a collision is reported.

The pictured overlap of circles would cause a collision using hitTestObject() because the method uses the minimum bounding rectangle of each object
Figure 7-7. The pictured overlap of circles would cause a collision using hitTestObject() because the method uses the minimum bounding rectangle of each object

Note

Checking for more accurate collisions of nonrectangular assets requires significantly more advanced programming—typically using precise pixel analysis with the BitmapData class, which we’ll introduce in Chapter 9. Fortunately, Corey O’Neil has done most of the work for you by creating his fantastic Collision Detection Kit. Now, instead of programming all the collision detection yourself, you only have to implement his code in your projects. Documentation and examples can be found at http://code.google.com/p/collisiondetectionkit/.

Collision with Points

Similarly, collisions between an object and a point are detected using the hitTestPoint() method. It determines whether the object calling the method collides with a point specified in the method call. The script in the collision_points.fla source file, will move an object to a random location when it comes in contact with the mouse. After creating the ball and listener in lines 1 through 6, line 7 checks to see if ball collides with the mouse, and sets the optional shape flag to true. When true, the shape flag uses nontransparent pixels to test for collisions with the point, rather than the minimum bounding rectangle of the object. If a collision occurs, the ball is relocated.

Note

Any alpha value above 0 will register a collision using the hitTestPoint() method. Only when a pixel is completely transparent will no collision be detected. To register collisions with nontransparent alpha values, use Corey O’Neil’s Collision Detection Kit. See the previous note.

1    var ball:MovieClip = new Ball();
2    ball.x = ball.y = 100;
3    addChild(ball);
4
5    addEventListener(Event.ENTER_FRAME, onEnter, false, 0, true);
6    function onEnter(evt:Event):void {
7        if (ball.hitTestPoint(mouseX, mouseY, true)) {
8            ball.x = Math.random() * stage.stageWidth;
9            ball.y = Math.random() * stage.stageHeight;
10       }
11    }

Note

Placing a display object on the stage within a given area ensures only that the registration point of the object is in the area prescribed. If, for example, the object is placed adjacent to a stage edge, part of the object may be out of view. Later in the chapter, we’ll show you how to be sure the entire object is always visible, even with random placement.

Figure 7-8 shows the mouse overlapping the bounding rectangle of the circle, but not touching any nontransparent pixels. In this case, because the shape flag is true, no collision would be detected.

No collision is detected here because only nontransparent pixels collide with a point (such as the mouse location) when the shape flag of the hitTestPoint() method is true
Figure 7-8. No collision is detected here because only nontransparent pixels collide with a point (such as the mouse location) when the shape flag of the hitTestPoint() method is true

Collision with Stage Boundaries

The following code, found in the collision_stage_boundaries.fla source file, moves the movie clip instance, ball, to the right 5 pixels every enter frame. For this example, the movie clip added in lines 1 through 3 has a center registration point. Before moving the ball, however, the conditional in line 7 checks to make sure the ball hasn’t passed the right side of the stage. If not, the ball’s position is updated. If it has passed that boundary, the listener is removed and the ball stops moving.

1    var ball:MovieClip = new Ball();
2    ball.x = ball.y = 100;
3    addChild(ball);
4
5    addEventListener(Event.ENTER_FRAME, onEnter, false, 0, true);
6    function onEnter(evt:Event):void {
7        if (ball.x + 5 < (stage.stageWidth - ball.width / 2)) {
8            ball.x += 5;
9        } else {
10           removeEventListener(Event.ENTER_FRAME, onEnter);
11       }
12    }

Notice that the right stage boundary is detected using the width of the stage, but that’s not the only value used in the conditional. Instead, half the width of the ball is subtracted from the boundary value first to prevent the ball from leaving the stage before the test fails. If this adjustment were not made, at least half of the ball would need to leave the stage before its center registration point caused the conditional to fail. Figure 7-9 shows the point at which a boundary collision is detected without accounting for a display object’s center registration point (top) and when subtracting half the width of the object from the test value (bottom).

When testing for boundary collisions on display objects with a center registration point, the collision value must be inset by half the width of the object from the stage dimensions
Figure 7-9. When testing for boundary collisions on display objects with a center registration point, the collision value must be inset by half the width of the object from the stage dimensions

A similar equation is used to detect movement beyond the bottom of the stage, using stage.stageHeight in the conditional. To check whether an object is about to leave the left or top of the stage, the test must start with a value of 0, but add half the width of the display object to inset the boundary from each edge. Later in this chapter, a more complete example will be used to reverse the direction of a particle’s movement before leaving the stage.

Note

If you create a display object with a noncenter registration point, your collision detection code will need to change. For example, using a registration point in the upper-left corner of a display object, you will need to subtract the full width of the object to see if it leaves the left or top sides of the stage, and subtract nothing to see if it leaves the right or bottom sides of the stage.

Geometry and Trigonometry

Although many people find geometry and trigonometry intimidating, the small investment required to understand a few basic principles in these disciplines can pay large dividends. For example, what if you needed to find the distance between two points, or rotate one object around another? These small tasks are needed more often than you may think, and are easier to accomplish than you may realize.

Movement Along an Angle

Earlier we discussed velocity as a vector quantity because it combined magnitude and direction. However, the direction in the previous example was determined by changing x and y coordinates. Unfortunately, such a direction is easily identifiable only when moving along simple paths, such as along the x or y axis. A much better way to indicate a direction is to specify an angle to follow.

Before we discuss angles and their different units of measure, you need to understand how angles are indicated in the ActionScript coordinate system. As you might expect, angles are commonly referenced using degrees, but it’s important to note that 0 degrees is along the x axis pointing to the right. The 360-degree circle then unfolds clockwise around the coordinate system. This means 90 degrees points down along the y axis, 180 degrees points left along the x axis, and so on, as shown in Figure 7-10.

How Flash angles are referenced
Figure 7-10. How Flash angles are referenced

Now that you have a correct point of reference, the next important concept to understand is that most of ActionScript, like most computer languages and mathematicians, does not use degrees as its preferred unit of measurement for angles. This is true for just about all common uses of angles, except for the rotation property of display objects and one or two more obscure items also related to rotation. Predominately, ActionScript uses radians as the unit of measure for angles. A radian is the angle defined by moving along the outside of the circle only for a distance as long as the circle’s radius, as seen in Figure 7-4. One radian is 180/pi degrees, which is approximately 57 degrees.

How radians are calculated
Figure 7-11. How radians are calculated

Though some of you may find that interesting or helpful, memorizing this definition isn’t vital. Instead, all you need to do is remember a handy conversion formula: radians = degrees * (Math.PI/180). Conversely, to convert radians to degrees use: degrees = radians / (Math.PI/180). (You may also see a degrees-to-radians conversion that looks like this: degrees = radians * (180/Math.PI)). In the upcoming example, we’ll write utility functions for this purpose that you can use throughout the rest of the examples.

Now we’re prepared to address the task at hand. We must send a movie clip off in a direction specified by an angle (direction) at a specific speed (magnitude). This will be the resulting velocity. This script, found in the movement_along_angle.fla source file, starts by creating a movie clip and positioning it on stage at point (100, 100). It then specifies the speed and angle at which the movie clip will travel, and converts commonly used degrees to ActionScript-preferred radians using the utility function at the end of the script.

1    var ball:MovieClip = new Ball();
2    ball.x = ball.y = 100;
3    addChild(ball);
4
5    var speed:Number = 12;
6    var angle:Number = 45;
7    var radians:Number = deg2rad(angle);

With both a direction (angle) and magnitude (speed), we can determine the required velocities relative to the x and y axes. To do so, we use the sin() and cos() methods of the Math class, which calculate the sine and cosine of an angle, respectively. If this dredges up bad memories of high school math class, just relax and picture a right-angle triangle with one point at the origin of the x/y axes (Figure 7-12).

A point on a circle can be determined by using the cosine and sine of an angle and the circle’s radius
Figure 7-12. A point on a circle can be determined by using the cosine and sine of an angle and the circle’s radius

The sine of an angle is the length of the opposite side of the triangle (shown in blue in Figure 7-12) divided by the length of the triangle’s hypotenuse (the longest side, opposite the triangle’s right angle). The cosine of an angle is the length of the adjacent side of the triangle (shown in red in Figure 7-12) divided by the length of the triangle’s hypotenuse. In terms more applicable to our needs, the x component of the direction we’re looking for is the cosine of an angle (in radians), and the direction’s y component is the sine of the same angle.

Multiply each value by a speed and you get x and y velocities, as seen in lines 8 and 9 of the following script block, respectively. All that remains is to add those velocities to the x and y coordinates of the ball (in the listener function at lines 13 and 14) and it’s on the move.

8    var xVel:Number = Math.cos(radians) * speed;
9    var yVel:Number = Math.sin(radians) * speed;
10
11    addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
12    function onLoop(evt:Event):void {
13        ball.x += xVel;
14        ball.y += yVel;
15   }
16
17   function deg2rad(deg:Number):Number {
18        return deg * (Math.PI / 180);
19    }

Lines 17 and 18 contain the conversion function called in line 7. It takes an angle in degrees and returns the same angle in radians.

Distance

Let’s say you’re programming a game in which a character is pursued by an enemy and must exit through one of two doors to safety. However, the enemy is close enough that the character must choose the nearest exit to survive. The player controls the character, but you must make sure the enemy catches the character if the player makes the wrong decision. To do that, the enemy must know which exit is closest.

To determine the distance between the enemy and a door, all you need to do is imagine a right triangle between those points and use a formula called the Pythagorean theorem. The theorem states that the square of the longest side of a right triangle is equal to the sum of the squares of the other two sides. This is illustrated in the top of Figure 7-13.

Calculating the distance between two points using geometry
Figure 7-13. Calculating the distance between two points using geometry

The bottom of Figure 7-13 shows this theorem in use, determining the distance between two movie clips, or, in our metaphorical case, between an enemy and a door. The triangle appears beneath the two points, and the differences between the x and y coordinates of points 1 and 2 are shown in dotted lines. These lengths correspond to the a and b sides of the triangle, so we need to square (x2 − x1) and square (y2 − y1) to satisfy the theorem.

The linear distance between the two points is shown as a solid red line. This linear distance corresponds to the length of the longest side of the triangle, but we don’t want the square of this length. So we must take the square root of both sides of the equation. In other words, we need the square root of (x2 − x1) * (x2 − x1) + (y2 − y1) * (y2 − y1).

Once you determine the distance between the enemy and one door, you repeat the process for the distance between the enemy and the other door. You can then determine which door is closest.

In the source file, distance.fla, the getDistance() function calculates the distance between two balls and returns that value as a Number. Line 3 determines the distance between the x coordinates, and line 4 determines the distance between the y coordinates. Line 5 uses the sqrt() method of the Math class to calculate the square root of the sum of those squares.

It compares the distance between ball0 and ball1 to the distance between ball0 and ball2:

1    function getDistance(x1:Number, y1:Number,
2                         x2:Number, y2:Number):Number {
3       var dX:Number = x2 - x1;
4       var dY:Number = y2 - y1;
5       return Math.sqrt(dX * dX + dY * dY);
6    }
7
8    var dist1:Number = getDistance(ball0.x, ball0.y,
9                                   ball1.x, ball1.y);
10   var dist2:Number = getDistance(ball0.x, ball0.y,
11                                  ball2.x, ball2.y);
12
13   if (dist1 < dist2) {
14       trace("ball1 is closest to ball0");
15   } else {
16       trace("ball2 is closest to ball0");
17   }

Note

In Chapter 8, we’ll show you another way to calculate the distance between two points using a simple method of the Point class.

More Particles: Collision and Distance

Now it’s time for another project to put your latest knowledge to the test. This second particle system, found in particles_angle.fla, will again create particles that move around on their own. This time, however, they’ll bounce off the edges of the stage and a line will be drawn between any particles that are within 100 pixels of each other.

This exercise will combine skills you’ve developed in moving objects along angles, collision detection, and distance calculation. It also uses such language fundamentals as for loops, conditionals, array structures, and random numbers, as well as reviews the display list and event listeners.

Finally, it makes use of the Graphics class to draw lines at runtime. We’ll cover this class in greater depth in the next chapter, but briefly, it allows you to draw vectors, including lines, curves, fills, and shapes, into display objects. In this script, we’ll just define line characteristics, connect points, and periodically clear what we’ve drawn.

Lines 1 through 4 of the following code create variables for use throughout the script. Line 1 creates an array to hold all the particles created. Line 2 creates a single particle so its diameter (line 3) and radius (line 4) can be determined. Lines 6 and 7 create a container sprite and add it to the display list. This will be a container into which we’ll draw lines that connect our particles. Line 8 makes this process a bit easier and more efficient by storing a reference to the graphics property of the container. This is the virtual canvas into which we’ll draw.

Lines 10 through 20 create 20 particles. Line 11 creates a new Particle instance, and lines 12 and 13 position the particles randomly on stage. Like the previous discussion about stage boundary collision testing, these lines guarantee that the particle is placed wholly within the stage. They do so by reducing the available area by the diameter of the particle, and insetting the left- and topmost positions by the radius.

1    var particles:Array = new Array();
2    var particle:Particle = new Particle();
3    var pD:Number = particle.width;
4    var pR:Number = particle.width / 2;
5
6    var container:Sprite = new Sprite();
7    addChild(container);
8    var g:Graphics = container.graphics;
9
10    for (var i:int = 0; i < 20; i++) {
11       particle = new Particle();
12       particle.x = Math.random() * (stage.stageWidth - pD) + pR;
13       particle.y = Math.random() * (stage.stageHeight - pD) + pR;
14       particle.speed = Math.random() * 5 + 1;
15       particle.angle = Math.random() * 360;
16       updateParticleVelocities(particle);
17
18       container.addChild(particle);
19       particles[i] = particle;
20    }

Line 14 creates a random speed, between 1 and 6, for each particle, and line 15 creates a random angle for movement, in degrees. This angle will be converted later into radians. Note that these are properties specific to each particle, not variables available to a function or the entire script. This is a useful practice because the values are created randomly when the particle is instantiated, and they are easily stored this way within each particle.

Line 16 calls the updateParticleVelocities() function found in lines 57 through 61. In line 58, the function converts the particle’s angle into radians using the conversion function at the end of the script. It then uses the formulas from the Movement Along an Angle section in lines 59 and 60 to update the x and y velocities for each particle. The particle is passed into the function as an argument, so these velocities can be stored in the particle object, as described in the previous paragraph. The velocities are calculated using the cosine and sine, respectively, of the angle, multiplied by the particle’s speed. Finally, the particle is added to the container (line 18), and to the array we’ll use to keep track of all the particles (line 19).

The remainder of the script is an event listener that’s executed every time an enter frame event is received. The listener function begins with line 23 by clearing the graphics property of any previously dynamically drawn lines. Next a loop executes once for every particle upon every enter frame. The loop first stores a reference to the next instance in the particles array (line 26). Lines 28 through 37 then determine if the next location of the particle is beyond the bounds of the stage; they check the current location plus the current velocity to see if the resulting point is outside the area available for placement.

The conditional uses the same technique explained in the Collision with Stage Boundaries section of this chapter. It first takes the appropriate stage edge (top or bottom in lines 28 and 29, or left and right in lines 33 and 34), and then insets the radius of the particle from each edge to determine the allowable values for particle movement. If a particle collides with a horizontal plane (top or bottom stage edge), the angle of movement is turned into a negative of itself (multiplied by −1) (line 30). Table 7-1 shows a range of incoming angles (angles of incidence) and after-bounce angles (angles of reflection), off both bottom and top edges, using this formula.

Table 7-1. Angles before and after bounce off horizontal planes

Angle of incidence

Angle of reflection

45

−45

90

−90

135

−135

225

−225

270

−270

315

−315

If a particle collides with a vertical plane (left or right stage edge), the angle of movement is turned into a negative of itself and 180 is added to that value (line 35). Table 7-2 shows a range of incidence and reflection angles, off both right and left edges, using this formula. Remember that you don’t have to think in terms of radians because the conversion function takes care of that for you.

Table 7-2. Angles before and after bounce off vertical planes

Angle of incidence

Angle of reflection

45

135

135

45

180

0

225

−45

315

−135

360

180

The last step in handling the movement of each particle is to again call the updateParticleVelocities() method (lines 31 and 36), to update the particle’s x and y velocities after the collision, and, in turn, its x and y properties

21    addEventListener(Event.ENTER_FRAME, onEnter, false, 0, true);
22    function onEnter(evt:Event):void {
23        g.clear();
24
25        for (var i:int = 0; i < particles.length; i++) {
26            var particle:Particle = particles[i];
27
28        if (particle.y + particle.velY < 0 + pR ||
29            particle.y + particle.velY > stage.stageHeight - pR) {
30            particle.angle = -particle.angle;
31            updateParticleVelocities(particle);
32        }
33        if (particle.x + particle.velX < 0 + pR ||
34            particle.x + particle.velX > stage.stageWidth - pR) {
35            particle.angle = -particle.angle + 180;
36            updateParticleVelocities(particle);
37        }
38
39        particle.x += particle.velX;
40        particle.y += particle.velY;
41
42        for (var j:int = i + 1; j < particles.length; j++) {
43            var nextParticle:Particle = particles[j];
44
45            var dX:Number = particle.x - nextParticle.x;
46            var dY:Number = particle.y - nextParticle.y;
47            var distance:Number = Math.sqrt(dX * dX + dY * dY);
48            if (distance < 100) {
49                g.lineStyle(0, 0x999999);
50                g.moveTo(particle.x, particle.y);
51                g.lineTo(nextParticle.x, nextParticle.y);
52            }
53        }
54    }
55   }
56
57   function updateParticleVelocities(p:Particle):void {
58        var radians:Number = deg2rad(p.angle);
59        p.velX = Math.cos(p.angle) * p.speed;
60        p.velY = Math.sin(p.angle) * p.speed;
61   }
62
63   function deg2rad(degree):Number {
64       return degree * (Math.PI / 180);
65   }

Finally, the loop in lines 42 through 53 checks the distance between every particle. Upon entering this nested loop, the current particle (particle, assigned in the outer loop in line 26) is compared with every other particle (nextParticle, assigned in the inner loop in line 43). By nesting the loop this way, each particle compares itself with the other remaining particles every time an enter frame event is received. This way, we can determine whether the distance between any two particles is less than 100 so we can draw a line between them. Note, too, that the counter variable of the inner loop is j, not i. This is necessary because if i were used again, it would conflict with the outer loop, get reassigned, and wreak havoc.

This nested loop structure is also more efficient than it could be, because the inner loop doesn’t start with 0 every time. Instead, it starts at the next particle in line (i + 1), after the current particle (i). This is possible because the relationships between the previous particles have already been examined. Put another way, when the outer loop reaches 19, the inner loop need only compare particle 19 (i) with particle 20 (i + 1).

When making the comparisons, the loop checks the distance between every two particles. If less than 100 (line 48), it readies a gray hairline stroke (line 49), moves to the location of the first point (line 50) and draws a line to the location of the second point (line 51) being compared. We’ll discuss drawing vectors with code in the next chapter, but the effect is that only those particles within close proximity of each other will be connected. As the positions of the particles change, so do their connections. Figure 7-14 shows the file in action.

During movement, particles in close proximity to each other will be connected.
Figure 7-14. During movement, particles in close proximity to each other will be connected.

Circular Movement

Now that you know how to determine x and y coordinates from an angle, circular movement is a snap. It will now be relatively trivial for you to move an object in a circle, the way a moon revolves around a planet. With circular movement, we are not interested in the velocity derived from direction and magnitude, because the display object will not be traveling along that vector. Instead, we want to calculate the x and y coordinates of many consecutive angles. By plotting the sine and cosine of many angles, you can move the ball in a circle.

If you think of the sine and cosine values of various angles, this technique is easy to understand. (For simplicity, all angles will be discussed in degrees, but assume the calculations are performed with radians.) The values of both cosine and sine are always between −1 and 1. The x component, or cosine, of angle 0 is 1, and the y component, or sine, of angle 0 is 0. That describes an x, y point (1, 0), or straight out to the right. The cosine of 90 degrees is 0 and the sine of 90 is 1. That describes (0, 1), or straight down.

This continues around the axes in a recognizable pattern. Remembering that we’re discussing degrees but calculating in radians, the cosine and sine of 180 degrees are −1 and 0, respectively (point (−1, 0), straight to the left), and the cosine and sine of 270 degrees are 0 and 1, respectively (point (0, 1), straight up).

You must do only two more things to plot your movie clip along a circular path. Because all the values you’re getting from your math functions are between −1 and 1, you must multiply these values by the desired radius of your circle. A calculated value of 1 times a radius of 100 equals 100, and multiplying −1 times 100 gives you −100. This describes a circle around the origin point of the axes, which spans from −100 to 100 in both horizontal and vertical directions.

Figure 7-15 illustrates these concepts in one graphic. Each color represents a different angle shown in the legend in both degrees and radians. The x and y values of the radians are expressed in the legend in standard cosine and sine units (between −1 and 1). The resulting x and y coordinates determined by multiplying these values by 100 are shown in the graph.

Four angles around a circle, expressed in degrees, radians, and as x and y points on a circle with a radius of 100 pixels
Figure 7-15. Four angles around a circle, expressed in degrees, radians, and as x and y points on a circle with a radius of 100 pixels

Finally, you can position your invisible circle wherever you want it on the stage. If you take no action, the object will rotate around the upper-left corner of the stage, or x, y coordinates (0, 0). The following script centers the circle on the stage.

The following example is found in the circular_movement.fla source file. The first nine lines of the script initialize the important variables. Specified are a starting angle of 0, a circle radius of 100, an angle increment of 10, and a circle center that matches the center of the stage (its width and height divided by 2, respectively). Also created is the satellite that will be orbiting the center of the stage, derived from the Asteroid linkage class assigned to a library symbol (line 7). It’s initially placed offstage in line 8 before becoming a part of the display list in line 9.

1    var angle:Number = 0;
2    var radius:Number = 100;
3    var angleChange:Number = 10;
4    var centerX:Number = stage.stageWidth / 2;
5    var centerY:Number = stage.stageHeight / 2;
6
7    var satellite:MovieClip = new Asteroid();
8    satellite.x = satellite.y = −200;
9    addChild(satellite);

The last part of the script is the enter frame event listener and degree-to-radian conversion utility discussed earlier. The listener function sets the x and y properties of the asteroid by starting with the center of the circle, and multiplying its radius by the x and y values calculated by the Math.cos() and Math.sin() methods (lines 13 and 14). After each plot, the angle is incremented in line 15.

Note

As discussed in Chapter 3, ActionScript will automatically adjust incoming rotation angles to create values most efficient for Flash Player to handle. Therefore, it doesn’t matter if angle continues to increment and exceed 360. For example, if you set a display object’s rotation property to 370 degrees, Flash Player will understand that this is equivalent to 10 degrees.

10    addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
11    function onLoop(evt:Event):void {
12        var radian:Number = deg2rad(angle);
13        satellite.x = centerX + radius * Math.cos(radian);
14        satellite.y = centerY + radius * Math.sin(radian);
15        angle += angleChange;
16    }
17
18    function deg2rad(deg:Number):Number {
19        return deg * (Math.PI / 180);
20    }

Note

The companion website discusses additional ways to convert rotation angles to usable values. See the “Working with Rotation Angles” post at http://www.LearningActionScript3.com.

A Circular Navigation System

Although this chapter is called Motion, you can do more with the skills you’re accumulating than move objects around the stage. You can use the same math that animates an object along a circular path to position static elements along a circle. The following script, found in the circle_navigation.fla source file, automatically positions six buttons around a center draggable object, as shown in Figure 7-16. The buttons, complete with labels, are children of the center object. So, when the center object is dragged around, all the buttons follow making a movable navigation system. Such a system could be very useful for projects with large visual assets, or many user interface elements, because the navigation widget could be moved around as needed to expose underlying content.

A navigation system created by positioning buttons in a circle
Figure 7-16. A navigation system created by positioning buttons in a circle

Line 1 sets the number of satellite buttons positioned around the center object. Line 2 sets the radius of the hidden outer circle, effectively setting the distance each button rests from the center object. Line 3 sets the starting angle of the first button. Remember that ActionScript angles begin at 0 to the right (or 3:00 on a clock face) and increase clockwise. Therefore, the first button appears straight up, or 12:00 on a clock face. Line 4 sets the amount the angle will be incremented with each new button. The number of buttons needed determines this. Our example uses six buttons, so they are positioned 60 degrees apart (360/6).

Lines 6 through 9 create the center button from the FLA library using the MainButton linkage class, center the button in the middle of the stage, and add it to the display list.

1    var numButtons:int = 6;
2    var radius:Number = 100;
3    var angle:Number = 270;
4    var angleChange:Number = 360/numButtons;
5
6    var mainButton:MainButton = new MainButton();
7    mainButton.x = stage.stageWidth / 2;
8    mainButton.y = stage.stageHeight / 2;
9    addChild(mainButton);

The heart of this script is the positionButtons() function (lines 10 through 33). When called from line 34, it runs through a loop once for every button requested—6 times, in this example. For each button, the loop begins by storing the current angle in a variable (line 12) and incrementing the angle to the next button position (line 13). The value of the angle is converted from degrees to radians using deg2rad(), the utility function we’ve discussed before, at the end of the script.

The button is then created using the library symbol with the SatelliteButton linkage class, centered, and positioned on the circle defined by the mainButton center point and radius. The same technique to move an object along a circular path is used here. The cosine of the current angle times the radius of the circle determines the x coordinate, and the sine of the angle multiplied by the circle’s radius calculates the y coordinate (lines 16 and 17).

Each button is then given a name in line 18, consisting of an uppercase “B,” and the number of the button, taken from the loop counter. The first button, for example, will be B0, the second B1, and so on. the last line of this code block adds a mouse click listener to each button that calls the onClick() function found in lines 36 through 38. In this simple example, this function just traces the button name. However, as discussed in Chapter 6, you can change this instruction to update the playhead in a movie clip, and we’ll teach you how to load external assets in Chapter 13.

Note

Although not strictly necessary in this example, it’s good practice to convert the int data type of the loop counter to a String data type before adding it to the button name.

Because the buttons in this example have text labels, Line 21 is very important. Setting the mouseChildren property of an object to false prevents the content of that object from receiving mouse events. By default, the mouse will automatically interact with the text fields in this example that display the labels inside the buttons. This interaction includes text selection, cursor feedback, and more. With mouseChildren set to false for each button, the text field child of the button won’t react to mouse events.

Line 22 is also important to this example because the navigation widget is draggable. By adding each button as a child of mainButton, rather than the main timeline, dragging the center button will also drag all its satellite button children.

The remainder of the function is consistent with our prior basic uses of text fields in the Hello World! applications presented in earlier chapters. Line 24 creates the text field, line 25 sets the field’s width to the width of the button, and lines 26 and 27 center the button horizontally and vertically, respectively. Line 28 automatically scales the text field down to fit its text and is also a simple way to center the text prior to learning more advanced formatting options in Chapter 10. Line 29 is another formatting shortcut, making all text in the field white. Finally, the button name is added to the text field in line 30 and the field is added as a child of the button to serve as its label.

10    function positionButtons() {
11        for (var i:int = 0; i < numButtons; i++) {
12            var radian:Number = deg2rad(angle);
13            angle += angleChange;
14
15            var btn:SatelliteButton = new SatelliteButton();
16            btn.x = Math.cos(radian) * radius;
17            btn.y = Math.sin(radian) * radius;
18            btn.name = "B" + String(i);
19            btn.addEventListener(MouseEvent.CLICK, onClick,
20                                 false, 0, true);
21            btn.mouseChildren = false;
22            mainButton.addChild(btn);
23
24            var tf:TextField = new TextField();
25            tf.width = btn.width;
26            tf.x = -btn.width / 2;
27            tf.y = -btn.height / 4;
28            tf.autoSize = TextFieldAutoSize.CENTER;
29            tf.textColor = 0xFFFFFF;
30            tf.text = btn.name;
31            btn.addChild(tf);
32        }
33    }
34    positionButtons();
35
36    function onClick(evt:MouseEvent) {
37        trace(evt.target.name);
38    }

Lines 39 through 51 are responsible for creating the drag behavior of mainButton. Lines 39 and 40 create a mouse down listener that triggers onStartDrag(), and lines 41 through 44 assign mouse up listeners to both mainButton and the stage. The latter is important because it’s possible while dragging for a mouse up event to not register on the button. Without allowing the stage to catch that event, the draggable object would be stuck to your mouse.

The onStartDrag() function (lines 46 through 48) is a great example of how using currentTarget in an event listener function can be very helpful. As discussed in Chapter 3, the target property will tell you which button received the mouse down event, but it will also make that single button draggable. The currentTarget property, on the other hand, refers to the object to which the listener is attached. That means that no matter which button you mouse down upon, mainButton will move, dragging all its child buttons along.

Finally, the onStopDrag() function (lines 49 through 51) stops all dragging.

39    mainButton.addEventListener(MouseEvent.MOUSE_DOWN, onStartDrag,
40                                false, 0, true);
41    mainButton.addEventListener(MouseEvent.MOUSE_UP, onStopDrag,
42                                false, 0, true);
43    stage.addEventListener(MouseEvent.MOUSE_UP, onStopDrag,
44                           false, 0, true);
45
46    function onStartDrag(evt:MouseEvent):void {
47        evt.currentTarget.startDrag();
48    }
49    function onStopDrag(evt:MouseEvent):void {
50        stopDrag();
51    }
52
53    function deg2rad(degree):void {
54        return degree * (Math.PI / 180);
55    }

This example shows how a little math can spice up even a simple navigation system, but without being too difficult to master. Best of all, this script automatically positions your satellite buttons for you, even if the number of buttons changes. If you’d rather have nine buttons instead of six, so be it! Just change the value in line 1 and the script will evenly space the buttons around the circumference of the circle.

Rotation Toward an Object

Determining points on a circle when you start with an angle requires sine and cosine, as seen in the previous example. However, the opposite of that task requires a different trigonometric method. Determining an angle when starting with point data requires atan2(). The atan2() method is a variation on the arctangent method and is especially useful when you want to use rotation to point something at another location. For instance, the next code example uses a frame event to continuously point a movie clip at the mouse location, no matter where the mouse is on the stage, as simulated in Figure 7-17.

Using atan2(), you can continuously point a movie clip at the mouse no matter where it’s on the stage
Figure 7-17. Using atan2(), you can continuously point a movie clip at the mouse no matter where it’s on the stage

The formula used to calculate the angle for the rotating object is:

Math.atan2(y2 - y1, x2 - x1)

There are two important issues to be aware of when using atan2(). As you can see, the method always takes y point data as its first parameter (instead of x, which is more commonly placed in the first position). Second, the method returns its angle in radians, not degrees.

With that in mind, let’s take a look at the following script, found in the point_at_mouse.fla source file. It begins by creating a new instance of the Hand linkage class from the library, placing the hand and forearm shown in Figure 7-17 in the center of the stage, and adding it to the display list. The listener that follows in lines 6 through 11 calculates the angle of rotation in radians, and then converts it to degrees, the unit required by the movie clip’s rotation property. The conversion takes place in the utility function rad2deg() at the end of the script.

The atan2() method in line 8 subtracts the mouse location from the hand location (in y and x components) to get the angle the hand must use to point at the mouse. Think of the location at which you want to point as the origin of the system. In other words, point back to home base. That will help you remember that the rotating object is point 2, and the mouse (in this case) is point 1.

1    var hand:MovieClip = new Hand();
2    hand.x = stage.stageWidth / 2;
3    hand.y = stage.stageHeight / 2;
4    addChild(hand);
5
6    addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
7    function onLoop(evt:Event):void {
8        var rotationRadians:Number = Math.atan2(hand.y - mouseY,
9                                                hand.x - mouseX);
10        hand.rotation = rad2deg(rotationRadians);
11    }
12
13    function rad2deg(rad:Number):Number {
14        return rad / (Math.PI / 180);
15    }

This example points one movie clip at the mouse, but the effect can be adapted in many ways. One obvious variant is to point a movie clip at another movie clip. Another visually interesting adjustment is to point many instances of a movie clip at the same object. A grid of such pointers, for example, looks interesting because each pointer rotates independently based on its location. This can be seen in Figure 7-18, and will be demonstrated in the next script. Finally, the ultimate effect need not be visual. You can use this technique simply to track things, such as planning the trajectory of a projectile toward a target.

Creating a grid using modulus

Detail of grid_point_mouse.fla. Using atan2(), you can continuously point a movie clip at the mouse no matter where it is on the stage
Figure 7-18. Detail of grid_point_mouse.fla. Using atan2(), you can continuously point a movie clip at the mouse no matter where it is on the stage

The following script, found in the grid_point_mouse.fla source file, points several independent objects at the mouse, but it also lays out the objects in a grid. Using atan2() to point at the mouse has already been discussed in the prior example, so let’s focus on how to create the grid.

Line 1 stores the y position of the first row in the grid, and the variable in line 2 will hold instances of the FLA library linkage class, Arrow. Line 3 starts a loop that increments 70 times to build a grid with as many arrows. Each arrow is created in line 4 and added to the display list in line 10. But the grid layout occurs in lines 5 through 9 through the magic of the modulo operator (%).

The modulo operator, often refer to as “mod,” returns the remainder of a division—any partial value left over when a number can’t be divided into equal parts. For example, 4 divided by 2 is 2, with no remainder. However, 5 divided by 2 leaves a remainder of 1. Modulo can be used to test when a specific number of iterations has occurred, without the need for another variable.

It’s tidy to arrange 70 items in a grid that contains 10 columns and 7 rows. To do this, we can loop over a process 70 times, but we need to know when the end of a row is reached if we are to advance down to the next row. We can’t rely solely on the loop counter because it increments from 0 to 70. However, dividing the loop counter by 10, there will be no remainder at counter values 0, 10, 20, and so on. Therefore, using the modulo operator, we can tell when the remainder is 0 and when we’ve reached the end of a row. The header of Table 7-3 shows the remainders of all numbers 0 through 69. For example, the numbers in the first column all have a remainder of 0, the numbers in the second column all have a remainder of 1, and so on.

Table 7-3. 70 values (i) listed by their remainder when dividing by 10 (i % 10)

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

Line 6 sets the x coordinate of the arrow based on the grid column number, derived using modulo (i % 10). All columns start with an initial offset of 50, and an additional offset of 50 pixels per column is added. The first arrow will be positioned at 50 (based on 50 + (0 * 50)), the second will be positioned at 100 (based on 50 + (1 * 50)), and so on. If i % 10 is 0 (line 6) a new row is required and 50 is added to rowY.

1    var rowY:Number = 0;
2    var myArrow:Arrow;
3    for (var i:int = 0; i < 70; i++) {
4        myArrow = new Arrow();
5        myArrow.x = 50 + ((i % 10) * 50);
6        if (i % 10 == 0) {
7            rowY += 50;
8        }
9        myArrow.y = rowY;
10       addChild(myArrow);
11       myArrow.addEventListener(Event.ENTER_FRAME, onLoop,
12                                false, 0, true);
13    }
14
15    function onLoop(evt:Event):void {
16        var thisArrow:Arrow = Arrow(evt.target);
17        var rotationRadians:Number = Math.atan2(thisArrow.y - mouseY,
18                                                thisArrow.x - mouseX);
19        thisArrow.rotation = rad2deg(rotationRadians);
20    }
21
22    function rad2deg(rad:Number):Number {
23        return rad / (Math.PI / 180)
24    }

Programmatic Tweening

Scripting your own animations from scratch gives you a lot of control and freedom, but it can be time-consuming, too. You may also discover that you’re frequently rewriting similar equations in project after project. If you find yourself spending too much time in this manner, you may want to look into ActionScript tweening classes. A tween is an animation sequence in which the computer interpolates all relevant settings between starting and ending property values. For example, just like you would create a motion tween in Flash Professional’s timeline, you might write a programmatic tween that moves a movie clip from an x position of 100 to an x position of 400.

Adobe’s Tween Class

Until you are comfortable using third-party ActionScript packages, you may want to get up to speed with tweening using Adobe’s Tween class. Built into the ActionScript language, the Tween class is fairly limited but also easy to understand. Here is a look at the class’s signature, and the seven parameters into which you send data when instantiating a tween object:

Tween(obj:Object, prop:String, func:Function, begin:Number,
      finish:Number, duration:Number, useSeconds:Boolean):Tween

The class takes the following arguments (in this order):

  • obj: The object to animate

  • prop: A relevant property to manipulate

  • func: A preexisting easing function to add expressiveness to the animation

  • begin: The beginning value of the property

  • finish: The finishing value of the property

  • duration: The duration of the tween

  • useSeconds: Whether to use seconds or frames as the desired time unit

It also returns a Tween object, so you can store a reference to the tween for additional manipulation. For example, you can stop or start the tween at a later point.

The following script, found in the tween_class.fla source file, provides a simple example of how to use the Tween class. It moves a movie clip from one side of the stage to the other, bouncing the clip into its final destination. Lines 1 through 4 tell the compiler where to find the required classes. Lines 6 through 8 create a movie clip from the FLA library using the Ball linkage class, place it at point (100, 100), and then add it to the display list.

Note

As discussed in Chapter 1 and Chapter 6, even though this code is a simple timeline script, it still needs import statements because the required classes are not part of the flash package. Only classes from this package are automatically imported behind the scenes in Flash Professional timeline scripts.

1    import fl.transitions.Tween;
2    import fl.transitions.easing.Bounce;
3    import fl.transitions.easing.None;
4    import fl.transitions.TweenEvent;
5
6    var ball:MovieClip = new Ball();
7    ball.x = ball.y = 100;
8    addChild(ball);
9
10    var ballXTween:Tween = new Tween(ball, "x", Bounce.easeOut,
11                                     100, 400, 3, true);
12
13    ballXTween.addEventListener(TweenEvent.MOTION_FINISH,
14                                onMotionFinish);
15    function onMotionFinish(evt:TweenEvent):void {
16        var ballAlphaTween:Tween = new Tween(ball, "alpha",
17                                             None.easeOut,
18                                             1, 0.3, 1, true);
19    }

Lines 10 and 11 create a Tween instance to animate ball’s x property. Pay particular attention to the fact that the property is specified in string format. That can take a little getting used to.

The tween will use the Bounce easing function to add expressiveness to the animation while moving the movie clip horizontally from 100 to 400 pixels. As a result, the ball will appear to bounce against its final position. Finally, the tween will conclude in 3 seconds—indicated by the time unit 3, and the true value of the last parameter, useSeconds, ensuring the tween is timed with seconds, not frames.

Lines 13 and 14 add an event listener to the ballXTween object, to trigger the listener function when the animation is finished and the TweenEvent.MOTION_FINISH event is fired. At that point, a new tween is created, to fade the alpha property of the same object from 1 to 0.3. The second tween will take 1 second, and uses no easing to complete the task.

Only the last parameter of the Tween class is optional. (When omitted, useSeconds will be false and will use frames to time the tween, rather than seconds.) Therefore, if you don’t want to use easing, you must specify the None easing class, and either the easeIn or easeOut property. Which you choose will not matter, as no easing will be applied. The names and descriptions of other available easing classes can be found in Table 7-4. All easing classes allow easing in, easing out, and easing both in and out of the tween.

Table 7-4. Easing types found in the fl.transitions.easing package

Easing Class

Description

Back

Easing in begins by backing up and then moving toward the target. Easing out overshoots the target and backtracks to approach it.

Bounce

Bounces in with increasing speed, or out with decreasing speed.

Elastic

Undulates in an exponentially decaying sine wave, accelerating in and decelerating out.

None

Linear motion without easing.

Regular

Normal easing, like that found in the timeline’s simple easing feature, accelerating in and decelerating out.

Strong

Emphasized easing, stronger than that found in the timeline’s simple easing feature, but without additional effects. Accelerates in and decelerates out.

GreenSock’s TweenLite

After gaining a little experience with third-party packages, you’ll very likely want to stop using the built-in Tween class and find a tweening package that you like. Invariably, these heavily optimized products are smaller, faster, and more robust, offering quite a bit that is worthy of your experimentation.

Our favorite is the Tweening Platform by GreenSock. The platform contains several great products, but the one we want to focus on is TweenLite. The tweening library comes in two variations: TweenLite, which is the smallest possible size and is optimized by making a wide array of features optional, and TweenMax, which is basically TweenLite with all of its features pre-enabled, as well as a handful of additional advanced features.

We’ll introduce TweenLite by recreating the tween example from the Adobe’s Tween Class section for comparison, and then building an example banner as an additional project. The main tools of TweenLite are a pair of nice, simple methods: to() and from(). As their names imply, they allow you to tween an object’s properties from their current values to final values, or from initial values to their current values, respectively.

Our first TweenLite example will demonstrate the to() method, which has the following signature:

to(target:Object, duration:Number, vars:Object):TweenLite

It begins with the object to tween, then includes the duration of the tween, and finishes up with an object that contains all other variables you may want to use to manipulate your tween. We’ll show you a few options for the variables object in a moment, but a relevant example is the useFrames property. The duration of the tween is measured in seconds by default, but you can set useFrames to true if you prefer, and the tween duration will be based on the file’s frame rate. The method also returns a TweenLite instance if you want to store a reference to the tween for later use.

All TweenLite examples are found in the tweenLite directory in the source archive, and the following script is in the tweenLite.fla source file, The first six lines are very similar to the Tween class example from the prior section—importing required classes and creating a movie clip to manipulate. Because this is an external library, you must have the Greensock Tweening Platform package in a known class path for the imports to work. For this example, you can place the package’s com folder in the same directory as your FLA file.

Note

With the developer’s kind permission, we’ve included the Tweening Platform with the sample source code from the companion website. As with any software product, however, you would be wise to check periodically with the Greensock website (http://www.greensock.com) to see if any changes to the packages have been made, and update your files accordingly.

1    import com.greensock.TweenLite;
2    import com.greensock.easing.Bounce;
3
4    var ball:MovieClip = new Ball();
5    ball.x = ball.y = 100;
6    addChild(ball);
7
8    TweenLite.to(ball, 3, {x:400, ease:Bounce.easeOut,
9                           onComplete:fadeBall});
10    function fadeBall():void {
11        TweenLite.to(ball, 1, {alpha:0.3});
12    }

In lines 8 and 9, TweenLite to() method is used to tween ball for 3 seconds, from whatever the current location is (100, as set in line 5) to 400. It uses the Bounce easing class and calls the fadeBall() function when the animation is complete.

The way TweenLite handles methods is quite different from the Tween class. Instead of having to create all your own listeners, TweenLite uses callbacks. An ActionScript callback is similar to the everyday use of the term. It’s a mechanism where you can essentially leave a message for an object and ask it to call you back at the function specified when an event occurs. In this case, you’re asking TweenLite to call the fadeBall() function when the tween is complete. When the function is called, another tween is created, this time fading the ball movie clip to 30 percent.

TweenLite also makes it very easy to build a sequence of tweens by using the delay property. In the prior example, the first tween spanned 3 seconds and, upon finishing, called another tween. Rather than relying on events, you can simply create both tweens but delay the second one to occur when the first finishes. This will produce the same effect as the previous example, but illustrates the ability to start your tweens whenever it suits you. To see this in action, simply use the following code to replace lines 8 through 12 of the prior example. This modification can be found in the tweenLite_to_delay.fla source file.

Note

The object syntax for the third parameter of TweenLite’s to() method makes it very easy to tween many properties at once. For example, you could write a tween like this:

TweenLite.to(ball, 3, {x:10,
 y:10, alpha:1, rotation:90,
 ease:Bounce.easeOut});

This tween would alter the x, y, alpha, and rotation properties all in a single structure, making it much easier to use than Adobe’s Tween class. You can kill all properties, or even select properties, any time so you can change the behavior of the tween after creating it.

8    TweenLite.to(ball, 3, {x:400, ease:Bounce.easeOut});
9    TweenLite.to(ball, 1, {alpha:0.3, delay:3, overwrite:false});

Note that when taking this approach, you’re essentially asking the tween to reassign itself. Just like for a variable, you may want a new behavior, or you may not. If you don’t want a tween to cancel out a prior tween referencing the same object, you must use a property called overwrite to control how the tweens interrelate. Setting the property to false will treat the tweens independently. The result is a sequence of tweens but without relying on events. The next example uses this technique.

Creating a simple banner using TweenLite

With a little experience under your belt, let’s make a banner. We’ll explore two key TweenLite concepts in this exercise: the from() method, and the ability to add advanced features through a plug-in mechanism.

The nice thing about using the from() method is that you can precreate a layout and TweenLite will automatically build it up using your specified from settings. For example, Figure 7-19 shows what the FLA file looks like when you write your script. This is actually the final state of the banner, so you can adjust your layout until you’re satisfied. Once you’re happy with the banner, it’s time to itemize the properties you want to work with and their initial values. The following script is found in the tweenLite_from_banner.fla source file.

A mock banner advertisement animated with TweenLite
Figure 7-19. A mock banner advertisement animated with TweenLite

The first property we’ll use is called tint, and it’s not part of the TweenLite default configuration. It is part of TweenLite’s bigger brother package, TweenMax, but TweenLite is optimized to be as small as possible and doesn’t include any non-essential features. However, you don’t need to move up to TweenMax if you only want to use a few features and keep everything really small. TweenLite has a plug-in system that allows you to activate specific plug-ins on an as-needed basis. You have to do this only once and the plug-in features will be available to the rest of your file thereafter.

Lines 1 through 4 import the needed classes, including the TweenPlugin class that manages plug-ins, and the specific plug-in we need, TintPlugin. Line 6 activates the TintPlugin. It will then be available throughout the life of the project. Lines 9 through 17 are the from() tweens, each of which lasts for 1 second.

Line 8 fades the background up from black. Lines 9 through 16 scale up the four balls from 0 to final size. They use an Elastic ease so the tweens spring forward and back a few times around their final scale values. However, each tween is delayed a bit to build a sequence. The first ball starts a half-second after the tint fade begins, the second tween starts one and one-half seconds later, and so on. The last ball springs into place three seconds after the process begins. This timing is amassed from a two-second delay and a one-second duration. At the same time, the word “AS3” finishes sliding in from the left.

1    import com.greensock.TweenLite;
2    import com.greensock.plugins.TweenPlugin;
3    import com.greensock.plugins.TintPlugin;
4    import com.greensock.easing.Bounce;
5
6    TweenPlugin.activate([TintPlugin]);
7
8    TweenLite.from(bg, 1, {tint:0x000000});
9    TweenLite.from(ball0, 1, {scaleX:0, scaleY:0,
10                             ease:Elastic.easeOut, delay:0.5});
11    TweenLite.from(ball1, 1, {scaleX:0, scaleY:0,
12                             ease:Elastic.easeOut, delay:1.5});
13    TweenLite.from(ball2, 1, {scaleX:0, scaleY:0,
14                             ease:Elastic.easeOut, delay:1.75});
15    TweenLite.from(ball3, 1, {scaleX:0, scaleY:0,
16                             ease:Elastic.easeOut, delay:2});
17    TweenLite.from(as3, 1, {x:-100, ease:Elastic.easeOut, delay:2});

Note

In this introduction, we’ve only scratched the surface of what the GreenSock Tweening Platform can do. Visit http://www.greensock.com for details, documentation, interactive examples, performance comparisons of other tweening engines, and more.

Reproducing Timeline Tweens with ActionScript

The last thing we want to mention in this chapter is a companion website post about a feature that’s a bit out of the ordinary. As such, we intend it to be an additional resource for your continued study outside this book. In addition to scripting motion solely with code, it’s also possible to rebuild a Flash Professional timeline motion tween using ActionScript.

At the very least, this is an interesting workflow between designer and developer—allowing a designer to carefully tweak an animation using traditional interface tools, and then turning the file over to a developer that can make the entire process more dynamic with ActionScript. At best, it’s a way for any Flash user to turn restrictive timeline tweens into code-based animations that are vastly easier to reuse and adapt.

This process requires that a traditional timeline tween be created first, and then Flash can break down the steps needed to reproduce the tween and write them to an XML document. ActionScript can then load the document, parse the instructions, and recreate the tween on the fly. The companion website (http://www.LearningActionScript3.com) has a full tutorial, including sample files, in a post called “Recreating Timeline Tweens with ActionScript,” so be sure to check it out.

What’s Next?

Though this chapter details a variety of ActionScript animation techniques, it only begins to cover the subject of motion through code. The basic building blocks are here, however, 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 Professional interface and approach code-only projects with a little more latitude. When working with visual assets, we’ve so far relied heavily on symbols created within Flash and stored in a file’s library.

It’s true that we’ve sneaked a dynamically created vector in here and there, such as in the second particle system in this chapter, when lines were drawn between particles in close proximity. Despite that, thus far we’ve typically instantiated objects from a file’s library using a linkage class. We’ll continue to do that any time complex artwork 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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.54.239