Recipe 11.6. Using
Trigonometry
Problem
You want to do some advanced animation,
involving rotation, circular motion, or oscillation.
Solution
Use the built-in math functions Math.sin( ), Math.cos( ),
and Math.atan2( ).
Discussion
Recipes 11.2
and 11.4
touched on the use of the sine and cosine functions, but they can
be used for many other useful effects, such as moving objects in
circular or oval paths, smoothly back and forth around a position,
or rotating to a particular angle. Both Math.sin( ) and
Math.cos( ) are based on the properties of a right triangle
(a triangle that has one 90-degree angle). Without getting into a
trigonometry lesson, if you feed either function a series of
increasing numbers, they will return values that go smoothly back
and forth from -1 to 0, 1, 0, and back to -1, continuously. The
following code snippet demonstrates this:
for(var i:Number = 0; i < 10; i += 0.1) {
trace(Math.sin(i));
}
This traces a long list of numbers. If you
examine those numbers, you'll see that they start at 0, go up to
0.999, back down to -0.999, back up, and so on. You can now
multiply that by another number, say 40, and get a list of values
from -40 to 40. If you use this in an enterFrame handler, or
timer-based method, and apply the result to an object's position,
you can get it to oscillate back and forth, or up and down, as the
following example shows:
package {
import flash.display.Sprite;
import flash.events.Event;
public class Oscillation extends Sprite {
private var _sprite:Sprite;
private var _angle:Number = 0;
private var _radius:Number = 100;
public function AS3CB( ) {
_sprite = new Sprite( );
_sprite.graphics.beginFill(0x0000ff, 100);
_sprite.graphics.drawCircle(0, 0, 25);
_sprite.graphics.endFill( );
_sprite.x = 0;
_sprite.y = 100;
addChild(_sprite);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
public function onEnterFrame(event:Event):void {
_sprite.x = 200 + Math.sin(_angle) * _radius;
_angle += .05;
}
}
}
Here, _angle is the variable holding
the increasing value fed to Math.sin( ). The result is
multiplied by the _radius variable, which is set at 100.
This causes the sprite to go back and forth 100 pixels.
If you use Math.cos( ) and do the same
thing with the sprite's y position, you have circular
motion:
public function onEnterFrame(event:Event):void {
_sprite.x = 200 + Math.sin(_angle) * _radius;
_sprite.y = 200 + Math.cos(_angle) * _radius;
_angle += .05;
}
To make more of an oval shaped path, just use a
different radius value on each axis. For example, set
_xRadius to 100, _yRadius to 50, and do the
following:
public function onEnterFrame(event:Event):void {
_sprite.x = 200 + Math.sin(_angle) * _xRadius;
_sprite.y = 200 + Math.cos(_angle) * _yRadius;
_angle += .05;
}
Now, if you create separate angles and amounts
to add to each angle, you can get a very random-looking motion.
First, create separate variables for each axes' factors:
private var _xAngle:Number = 0;
private var _yAngle:Number = 0;
private var _xSpeed:Number = .13;
private var _ySpeed:Number = .09;
private var _xRadius:Number = 100;
private var _yRadius:Number = 50;
Then apply those to the motion code:
public function onEnterFrame(event:Event):void {
_sprite.x = 200 + Math.sin(_xAngle) * _xRadius;
_sprite.y = 200 + Math.cos(_yAngle) * _yRadius;
_xAngle += _xSpeed;
_yAngle += _ySpeed;
}
One possible use for this example is to simulate
a fly, randomly buzzing around a room.
Another very useful trig function is
Math.atan2( ). The main use for this is in finding the angle
between two points. It takes two parameters: the distance between
the two points on the y-axis, and the distance between them
on the x-axis. It then returns the angle, in radians,
between the points.
A common scenario for using Math.atan2( )
is in making an object (a sprite, for example) point at the mouse.
The y distance is mouseY _sprite.y, and
the x distance is mouseX _sprite.x.
Math.atan2( ) returns an angle. Convert that to degrees and
use it to set _sprite.rotation. Of course, you'll need
some sort of sprite graphic that shows which direction it is
rotating. The next example creates the classic "following eyes," a
little desktop toy that follows the mouse around the screen and has
been created for just about every graphical operating system out
there (actually, this example creates only a single eye, but it
demonstrates the principle):
package {
import flash.display.Sprite;
import flash.events.Event;
public class FollowingEye extends Sprite {
private var _sprite:Sprite;
public function AS3CB( ) {
_sprite = new Sprite( );
_sprite.graphics.beginFill(0xffffff, 100);
_sprite.graphics.drawCircle(0, 0, 25);
_sprite.graphics.endFill( );
_sprite.graphics.beginFill(0x000000, 100);
_sprite.graphics.drawCircle(20, 0, 5);
_sprite.graphics.endFill( );
_sprite.x = 100;
_sprite.y = 100;
addChild(_sprite);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
public function onEnterFrame(event:Event):void {
var dx:Number = mouseX - _sprite.x;
var dy:Number = mouseY - _sprite.y;
var radians:Number = Math.atan2(dy, dx);
_sprite.rotation = radians * 180 / Math.PI;
}
}
}
The setup code draws an extra circle on the
right edge of the first. When you are doing this kind of rotation,
align your graphics so that "zero degrees" is facing to the right
like this. The enterFrame handler calculates the two
distances and the resulting angle, converts to degrees and assigns it to the eye's rotation.
|