By Lon (Alonzo) Hosford
This example is based on Keith Peters ground breaking book on animation for ActionScript 3, Foundation Actionscript 3.0 Animation: Making Things Move.
Keith lays out the basic fundamentals of motion. A great start point example to understand velocity, friction, acceleration and direction is the Asteroids game style ship moving through space. I also reproduced a similar example using HTML5 canvas you can view here: HTML5 Canvas Based Animation Asteriods Space Ship Movement
Many similar examples just assume you are going to use the keyboard to control the ship. This example decouples the ship acceleration and movement from keyboard. However we then double back and use the keyboard to control the ship. You will see in the MXML Application code, the keyboard events call methods on the ArrowShip class. However you could provide buttons for mouse clicks or even go crazy with a multi-touch application.
The example is created in Flex Builder Running Eclipse but can be adapted to AIR or Flash. Download the example code. You can also use Flash CS3 and CS4. You need create a Flash document in the src directory and set AsteriodsShipMovement as you document class. You also need to include the Flex 3 library, the Flex.swc file, into your Actionscript 3 settings. Tareq AlJaber article shows how that is done on page two of his article Embedding metadata with Flash. For your convenience you can download a Flash CS4 file that is set up, but still you may need to check the Actionscript 3 settings if the movie will not play because you might have a non-standard installation. Download the Flash CS4 example.
You can use any graphic for the ship. More on how to do that is noted on the comments for ArrowShip class code.
I added lots of comments in the code. The following is an overview of each code piece.
The AsteroidsShipMovement class is the main class to start the example. Line 17 is where you set the size and speed of your Flash movie.
The background is a simple shape. You can embellish to your preference. See line 34.
The ship needs a mask to define when it leaves view. That can be any size but generally is the same size as the stage or smaller. Thus your flying area can be smaller. The mask must be a rectangle. See line 38.
AsteroidsShipMovement.as
package { . . . import com.alh.ui.ships.ArrowShip; import com.alh.ui.ships.Ship; import flash.display.MovieClip; import flash.display.Shape; import flash.display.Sprite; import flash.display.StageScaleMode; import flash.events.KeyboardEvent; import flash.geom.Rectangle; import flash.ui.Keyboard; // SET GAME SIZE AND SPEED HERE [SWF(width=600, height = 600, frameRate = "30")] public class AsteriodsShipMovement extends Sprite { // Basic game background - set your preference here public static const backgroundColor:Number = 0x0000ff; public static const backgroundBorderColor:Number = 0x666666; public static const backgroundBorderWidth:Number = 2; // Create the ship private var arrowShip:ArrowShip; public function AsteriodsShipMovement() { // Set stage options initStage(); // Create a background var backgroundRect:Shape = getRectShape(backgroundColor, backgroundBorderColor, backgroundBorderWidth, stage.stageWidth, stage.stageHeight) addChild(backgroundRect); // Create the boundaries for the arrowShip, create the arrowShip. var shipMaskRect:Shape = getRectShape(0x000000, 0x000000, backgroundBorderWidth, stage.stageWidth, stage.stageHeight) arrowShip = new ArrowShip(shipMaskRect); addChild(arrowShip); arrowShip.x = shipMaskRect.width / 2; arrowShip.y = shipMaskRect.height / 2; // Use keyboard events to control the ship stage.addEventListener( KeyboardEvent.KEY_DOWN, keyPressed); stage.addEventListener( KeyboardEvent.KEY_UP, keyReleased); } /** * Set any stage options per your needs * */ private function initStage():void { stage.scaleMode = StageScaleMode.NO_SCALE; } /** * Handler for KeyboardEvent.KEY_DOWN * */ private function keyPressed(evt:KeyboardEvent):void { // Either accelerate or turn the ship switch (evt.keyCode) { case Keyboard.UP: arrowShip.accelerate(Ship.START); break; case Keyboard.LEFT: arrowShip.turn(Ship.LEFT, Ship.START); break; case Keyboard.RIGHT: arrowShip.turn(Ship.RIGHT, Ship.START); break; } } /** * Handler for KeyboardEvent.KEY_UP * */ private function keyReleased(evt:KeyboardEvent):void { // Either stop accelerating or stop turning the ship switch (evt.keyCode) { case Keyboard.UP: arrowShip.accelerate(Ship.STOP); break; case Keyboard.LEFT: arrowShip.turn(Ship.LEFT, Ship.STOP); break; case Keyboard.RIGHT: arrowShip.turn(Ship.RIGHT, Ship.STOP); break; } } /** * Utility to draw a rectangle Shape object * */ private function getRectShape(bgColor:uint, borderColor:uint, borderSize:uint, width:uint, height:uint):Shape { var newShape:Shape = new Shape(); newShape.graphics.beginFill(bgColor); newShape.graphics.lineStyle(borderSize, borderColor); newShape.graphics.drawRect(0, 0, width, height); newShape.graphics.endFill(); return newShape; } } }
The ArrowShip class is a subclass of the Ship class. The Ship class does all the common functions for ship objects. The ArrowShip class is basically the skin. You subclass the Ship class and add your ship image on line 11.
ArrowShip.as
. . . package com.alh.ui.ships { import flash.display.DisplayObject; public class ArrowShip extends Ship { [Embed(source="/assets/ArrowShip.png")] private var ShipImg:Class; private var a:int; /** * Constructor * param maskRect represents the container boundaries for the ship and its mask * */ public function ArrowShip(maskRect:DisplayObject) : void { super(maskRect); super.addShipImage(new ShipImg(),270); } } }
The Ship class implements the animation direction, velocity, friction, and acceleration principles set out in Keith Peters’ book, Foundation Actionscript 3.0 Animation: Making Things Move!.
Instead of including Keyboard movement in the class as many animators do, the accelerate and turn methods were included. The UI or any code can call these methods to control the ship. On a more advanced level you could create an interface to further specify these methods in all types of objects that move forward and turn.
Ship.as
. . . package com.alh.ui.ships { import flash.display.DisplayObject; import flash.display.MovieClip; import flash.display.Sprite; import flash.events.Event; /** * Generic class for a space ship moving through space * This class is intended to be abstract * */ public class Ship extends MovieClip { private var _shipDirectionOffset:Number = 0; // Offset front of ship in artwork in degrees. // Artwork may have the ship facing in different // directions and this makes the adjustment // 0 = artwork ship faces right. 270 faces up. private var _speed:Number = 0.3; // Acceleration increment private var _rotateSpeed:Number = 3; // Turning speed in degrees private var _vx:Number = 0; // Velocity for x (direction and speed) private var _vy:Number = 0; // Velocity for y(direction and speed) private var _friction:Number = 0.95; // Percent reduction in _vx and _vy private var _accelerate:Boolean = false; // True if increasing _vx and _vy, false if not private var _turnLeft:Boolean = false; // Right turn direction request private var _turnRight:Boolean = false; // Object has right turn direction request public static const START:int = 0; // Start moving or turning public static const STOP:int = 1; // Stop moving or turning public static const LEFT:int = 0; // Turn left public static const RIGHT:int = 1; // Turn right /** * Constructor * param maskRect represents the container boundaries for the ship and its mask * */ public function Ship(maskRect:DisplayObject) : void { mask = maskRect; // Register handler for Event.ENTER_FRAME addEventListener(Event.ENTER_FRAME, enterFrameEventHandler, false, 0, true); } /** * Add the ship image and its directional offset * param imgClass is the artwork * param shipDirectionOffset see _shipDirectionOffset for notes * */ internal function addShipImage(imgClass:DisplayObject, shipDirectionOffset:Number):void { _shipDirectionOffset = shipDirectionOffset; // Add in ship image var shipSprite:Sprite = new Sprite(); shipSprite.addChild(imgClass); // Add ship sprite and center addChild(shipSprite); shipSprite.x = shipSprite.width / 2* -1; shipSprite.y = shipSprite.height / 2 * -1; } /** * Accelerates ship or stops acceleration * param startStopState valid values START and STOP * */ public function accelerate(startStopState:int):void { // Set the accelerating state _accelerate = startStopState == START; } /** * Turn ship * param turnDirection valid values LEFT and RIGHT * param startStopState valid values START and STOP * */ public function turn(turnDirection:int, startStopState:int):void { // Set the left turn state if (turnDirection == LEFT) { _turnLeft = startStopState == START; } // Set the right turn state if (turnDirection == RIGHT) { _turnRight = startStopState == START; } } /** * Event.ENTER_FRAME event handler * */ protected function enterFrameEventHandler(e:Event) : void { // Acceleration is on if (_accelerate ) { // Conversion of rotation degrees (rotation + _shipDirectionOffset) and speed to velocity _vy += Math.sin(degreesToRadians(rotation + _shipDirectionOffset)) * _speed; _vx += Math.cos(degreesToRadians(rotation + _shipDirectionOffset)) * _speed; } // Acceleration is off else { // reduce velocity by friction _vy *= _friction; _vx *= _friction; } // Change direction right if (_turnRight) { rotation += _rotateSpeed; } // Change direction left else if (_turnLeft) { rotation += -_rotateSpeed; } // Move position by velocity y += _vy; x += _vx; // Exiting right side of mask if (x - width / 2 > mask.width) { x = 0; } // Exiting left side of mask else if (x + width / 2 < 0) { x = mask.width; } // Exiting top of mask if (y - height / 2 > mask.height) { y = 0; } // Exiting bottom of mask else if (y + height / 2 < 0) { y = mask.height; } } /** * Change the default friction value * */ protected function set friction(friction:Number):void { _friction = friction; } /** * Change the default speed value * */ protected function set speed(speed:Number):void { _speed = speed; } /** * Change the default rotateSpeed value * */ protected function set rotateSpeed(rotateSpeed:Number):void { _rotateSpeed = rotateSpeed; } /** * Utility to convert Actionscript degrees (0-360) to Trig radians (57.2958 degrees). * */ private function degreesToRadians(degrees:Number) : Number { return degrees * Math.PI / 180; } } }