Getting started with Actionscript 3 Page 4/4 Previous page 
[ July 06, 2006 ] Milan Toth (MilGra)
A 4 chapter long, in-depth introduction to the new characteristics of Actionscript 3 and the Flash player 9 framework.
The author will guide you through all the new features by constructing a real application step by step. A must read for all Flash enthusiasts.

Getting started with AS3 - Fourth Part - Moving the ball with camera input

A very cool and spectacular thing comes: our ball will be controlled by the camera input. We need a new class named MotionDetectorLogic, which checks for differences in a Video object, in which it should check for differences, and it will receive a Bitmap object, where it can draw the calculated differences, so we can check our algorythm.

But first, we have to draw an UML diagram, because our code is getting complicated. Watching a good programmer, the difference of the final code and his previously drawn UML diagram is under 40 percent :)

So, MotionDetectorLogic class has a setVideo and a setBitmap function, and we need a getPoints, which calculates the differences between two phases of the Video object, and stores them in an Array. Plus we need a drawDifference function, which draws the differing points, for debugging purposes.

Our UML looks like this:
diagram

MotionDetectorLogic.as:

//begin
//
//
package logic
{
	//
	//
	//
	import flash.display.BitmapData;
	import flash.display.Bitmap;
	import flash.media.Video;
	//
	import flash.geom.Rectangle;
	import flash.geom.ColorTransform;
	//

ColorTransform is for color transformation, because we would like to show differences with fadeing green blocks. Class Rectangle is used by BitmapData.

	//
	//
	//
	public class MotionDetectorLogic
	{
		//
		//
		//
		private var root:Object;
		//
		private var difColTR:ColorTransform;
		private var actBMP:BitmapData;
		private var oldBMP:BitmapData;
		private var difBMP:BitmapData;
		private var myBMP:Bitmap;
		private var myVID:Video;
		//
		private var SENSITIVITY:int = 700000;
		private var BLOCKSIZE:int = 10;

actBMP is the actual, oldBMP is the previous state of Video, diffBMP stores the differences. SENSITIVITY is the sensitivity of the color detection, BLOCKSIZE is the stepping.

		//
		//
		//
		public function MotionDetectorLogic ( myRoot:Object ):void
		{
			//
			root = myRoot;
			//
			difColTR = new ColorTransform( 1 , 1 , 1 , .7 , 0 , 0 , 0 , 0 );
			//
			actBMP = new BitmapData( 320 , 240 , false , 0x000000 );
			oldBMP = new BitmapData( 320 , 240 , false , 0x000000 );
			difBMP = new BitmapData( 320 , 240 , true , 0x000000 );
			//
			addLog( "new MotionDetectorLogic " );
			//
		}

		//
		//
		//
		public function setVideo ( vidOBJ:Video ):void
		{
			//
			myVID = vidOBJ;
			//
			addLog( "MotionDetectorLogic.setVideo " + vidOBJ );
			//
		}

		//
		//
		//
		public function setBitmap ( bmpOBJ:Bitmap ):void

		{
			//
			myBMP = bmpOBJ;
			myBMP.bitmapData = difBMP;
			//
			addLog( "MotionDetectorLogic.setBitmap " + bmpOBJ );
			//
		}

difBMP has to be transparent, and we created an "Empty" Bitmap class in the controller script, so we need to assign our BitmapData here.

		//
		//
		//
		public function getPoints ( ):Array

		{
			//
			actBMP.draw( myVID );
			//
			var diffPoints:Array = [ ];
			//
			for ( var a:int = 0 ; a < actBMP.width ; a += BLOCKSIZE )

			 for ( var b:int = 0 ; b < actBMP.height ; b += BLOCKSIZE )

			 {

			 	//
			 	var oldPx:uint = oldBMP.getPixel( a , b );
			 	var newPx:uint = actBMP.getPixel( a , b );
			 	//
			 	if ( ( oldPx - SENSITIVITY ) > newPx || 
				( oldPx + SENSITIVITY ) < newPx )

			 		diffPoints.push( { x:a , y:b } );
			 	//
			 }

			//
			oldBMP = actBMP.clone( );
			drawDifference( diffPoints );
			//
			//addLog( "MotionDetectorLogic.getPoints " + diffPoints );
			//
			return diffPoints;
			//
		}

This function does the real job, we "draw" the Video object to actBMP, then check points with BLOCKSIZE stepping, then check if the color of the actual pixel of newBMP differs from the proper pixel in oldBMP, and if yes, we store pixel coordinates in diffPoints. Before return, we draw up diffBMP.

		//
		//
		//
		public function drawDifference ( diffPoints:Array ):void
		{
			//
			difBMP.colorTransform( difBMP.rect , difColTR );
			//
			for ( var a:int = 0 ; a < diffPoints.length ; ++a )

			{
				//
				var p:Object = diffPoints[a];
				var rect:Rectangle = new Rectangle( p.x , p.y
 , BLOCKSIZE , BLOCKSIZE );
				difBMP.fillRect( rect , 0xff00ff00 );
				//
			}

			//
			//addLog( "MotionDetectorLogic.drawDifference" );
			//
		}

First setting diffBMP-s alpha, then stepping through all points, and make a stepping-sided square.

		//
		//
		//
		public function addLog ( logText:String ):void
		{

			//
			this.root.addLog( logText );
			//
		}

		//
		//
		//
	}
	//
	//
	//
}
//
//
//end

FootballGame.as:

//begin
//
//
package

{
	//
	//
	//
	import flash.media.Camera;
	import flash.media.Video;
	import flash.display.Bitmap;
	import flash.display.MovieClip;
	import flash.display.Loader;
	import flash.display.StageScaleMode;
	import flash.display.StageAlign;
	import flash.media.Sound;
	import flash.net.URLRequest;
	import flash.events.*
	import flash.utils.*;
	//
	import logic.MotionDetectorLogic;
	import logic.InteractivePointLogic;
	import flash.display.Sprite;

We have a lot to import, we need a Cam, Video, or previously written MotionDetectorLogic, and things from the previous tutorials.

	//
	//
	//
	public class FootballGame extends Sprite
	{
		//
		//
		private var moveTimer:Timer;
		//
		private var hasSkin:Boolean;
		private var hasSound:Boolean;
		private var ballSkinLO:Loader;
		private var ballSkinSP:Sprite;
		private var ballPopSND:Sound;
		private var ballPoint:InteractivePointLogic;
		//
		public var RADIUS:Number = 20;
		public var WIDTH:int = 320;
		public var HEIGHT:int = 240;
		//
		private var xspeed:Number;
		private var yspeed:Number;
		private var oldx:Number;
		private var oldy:Number;
		//
		private var motDetOBJ:MotionDetectorLogic;
		private var movieVID:Video;
		private var movieBMP:Bitmap;
		private var movieCAM:Camera;

Define constants needed for Cam, Video, Bitmap, new constants with RADIUS, WIDTH, HEIGHT.

		//
		//
		//
		public function FootballGame ( )
		{
			//
			stage.frameRate = 25;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP;
			//
			movieVID = new Video( );
			movieBMP = new Bitmap( );
			movieCAM = Camera.getCamera( );
			movieVID.attachCamera( movieCAM );
			addChild( movieVID );
			addChild( movieBMP );
			//
			motDetOBJ = new MotionDetectorLogic( this );
			motDetOBJ.setVideo( movieVID );
			motDetOBJ.setBitmap( movieBMP );
			//
			loadSkin( );
			loadSound( );
			//
			addLog( "new FootballGame" );
			//
		}

Background drawing is gone, we have a Video object instead.

		//
		//
		//
		public function loadSkin ( ):void
		{
			//
			ballSkinSP = new Sprite( );
			addChild( ballSkinSP );
			//
			var urlRequest:URLRequest = new URLRequest( "soccerball.gif" );
			ballSkinLO = new Loader( );
			ballSkinLO.contentLoaderInfo.addEventListener( Event.COMPLETE , 
skinLoaded );
			ballSkinLO.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR ,
 ioErrorHandler );
			ballSkinLO.load( urlRequest );
			ballSkinSP.addChild( ballSkinLO );
			//
			addLog( "FootballGame.loadSkin" );
			//
		}

		//
		//
		//
		public function loadSound ( ):void
		{
			//
			var urlRequest:URLRequest = new URLRequest( "pop.mp3" );
			ballPopSND = new Sound( );
			ballPopSND.addEventListener( Event.COMPLETE , soundLoaded );
                        ballPopSND.addEventListener( IOErrorEvent.IO_ERROR , 
ioErrorHandler );
			ballPopSND.load( urlRequest );
			//
			addLog( "FootballGame.loadSoun" );
			//
		}

		//
		//
		//
		public function ioErrorHandler ( eventOBJ:Event ):void
		{
			//
			addLog( "ioError: " + eventOBJ );
			//
		}

		//
		//
		//
		public function skinLoaded ( eventOBJ:Event ):void

		{
			//
			ballSkinLO.x = -RADIUS;
			ballSkinLO.y = -RADIUS;
			ballSkinLO.width = 2*RADIUS;
			ballSkinLO.height = 2*RADIUS;
			hasSkin = true;
			initLogic( );
			//
			addLog( "FootballGame.skinLoaded" );
			//
		}

		//
		//
		//
		public function soundLoaded ( eventOBJ:Event ):void
		{
			//
			hasSound = true;
			initLogic( );
			//
			addLog( "FootballGame.soundLoaded" );
			//
		}

		//
		//
		//
		public function initLogic ( ):void
		{
			//
			if ( hasSkin && hasSound )

			{
				//
				ballPoint = new InteractivePointLogic( this );
				ballPoint.xpos = 20;
				ballPoint.ypos = 20;
				ballPoint.xspeed = Math.random( )*5;
				ballPoint.yspeed = Math.random( )*5;
				ballPoint.setSkin( ballSkinSP );
				ballPoint.setSound( ballPopSND );
				//
				moveTimer = new Timer( 25 );
				moveTimer.addEventListener( TimerEvent.TIMER , step );
				moveTimer.start( );
				//
			}

			//
			addLog( "FootbalGame.initLogic " + hasSkin + " " + hasSound );
			//
		}
		//
		//
		//
		public function step ( eventOBJ:Event ):void


		{
			//
			var diffARR:Array = motDetOBJ.getPoints( );
			//
			for ( var a:int = 0 ; a < diffARR.length ; ++a )

			{
				//
				var dx:Number = ballPoint.xpos - diffARR[a].x;
				var dy:Number = ballPoint.ypos - diffARR[a].y;
				var dr:Number = Math.sqrt( dx*dx + dy*dy );
				//
				if ( dr < RADIUS )

				{
					//
					ballPoint.xspeed = dx;
					ballPoint.yspeed = dy;
					break;
					//
				}
				//
			}
			//
			ballPoint.step( );
			//
		}

step function asks differences from MotionDetectorLogic, then does a simple collosion detection: if distance between the ball's center and every difference point is under RADIUS, then we have a collosion: then it passes new xspeed and yspeed coordiantes to the ball, then stops the for loop.

		//
		//
		//
		public function dragBall ( ):void
		{
			//
			addEventListener( MouseEvent.MOUSE_MOVE , moveBall );
			moveTimer.stop( );
			//
			addLog( "FootbalGame.dragBall" );
			//
		}

		//
		//
		//
		public function releaseBall ( ):void
		{
			//
			removeEventListener( MouseEvent.MOUSE_MOVE , moveBall );
			ballPoint.xspeed = xspeed;
			ballPoint.yspeed = yspeed;
			moveTimer.start( );
			//
			addLog( "FootbalGame.releaseBall" );
			//
		}

		//
		//
		//
		public function moveBall ( eventOBJ:MouseEvent ):void
		{
			//
			ballPoint.xpos = mouseX;
			ballPoint.ypos = mouseY;
			ballPoint.updateSkin( );
			//
			xspeed = mouseX - oldx;
			yspeed = mouseY - oldy;
			//
			oldx = mouseX;
			oldy = mouseY;
			//
			addLog( "FootbalGame.moveBall" + mouseX + " " + mouseY );
			//
		}

		//
		//
		//
		public function addLog( logText:String ):void
		{
			//
			trace( logText );
			//
		}

		//
		//
		//
	}

	//
	//
	//
}
//
//
//end

InteractivePointLogic:

//begin
//
//
package logic
{
	//
	//
	//
	import flash.display.Sprite;
	import flash.display.Loader;
	import flash.media.Sound;
	import flash.events.*;
	//
	//
	//
	public class InteractivePointLogic
	{

		//
		//
		//
		private var root:Object;
		private var gravity:Number = 0.5;
		private var skinSP:Sprite;
		private var ballSND:Sound;
		//
		public var xpos:Number;
		public var ypos:Number;
		public var xspeed:Number;
		public var yspeed:Number;
		//
		//
		//
		public function InteractivePointLogic ( myRoot:Sprite )

		{
			//
			root = myRoot;
			addLog( "new InteractivePointLogic" );
			//
		}

		//
		//
		//
		public function setSkin ( mySP:Sprite ):void

		{
			//
			skinSP = mySP;
			skinSP.addEventListener( MouseEvent.MOUSE_DOWN , mouseDownHandler );
			skinSP.addEventListener( MouseEvent.MOUSE_UP , mouseUpHandler );
			//
			addLog( "InteractivePointLogic.setSkin " + mySP );
			//
		}

		//
		//
		//
		public function updateSkin ( ):void
		{
			//
			skinSP.x = xpos;
			skinSP.y = ypos;
			skinSP.rotation += xspeed;
			//
			//addLog( "InteractivePointLogic.updateSkin" );
			//
		}

		//
		//
		//
		public function setSound ( mySND:Sound ):void
		{
			//
			ballSND = mySND;
			//
			addLog( "InteractivePointLogic.setSound " + mySND );
			//
		}

		//
		//
		//
		public function step ( ):void

		{
			//
			yspeed += gravity;
			//
			if ( xpos + xspeed > root.WIDTH - root.RADIUS )

			{
				//
				ballSND.play( );
				xspeed *= -.9;
				//
			}

			//
			if ( xpos + xspeed < 0 + root.RADIUS )

			{
				//
				ballSND.play( );
				xspeed *= -.9;
				//
			}

			//
			if ( ypos + yspeed > root.HEIGHT - root.RADIUS - 20 )

			{
				//
				ballSND.play( );
				yspeed -= gravity;
				yspeed *= -.9;
				//
			}

			//
			if ( ypos + yspeed < 0 + root.RADIUS )

			{
				//
				ballSND.play( );
				yspeed *= -.9;
				//
			}

			//
			xpos += xspeed;
			ypos += yspeed;
			//
			updateSkin( );
			//
			//addLog( "InteractivePointLogic.step" );
			//
		}

		//
		//
		//
		public function mouseDownHandler ( eventOBJ:MouseEvent ):void
		{

			//
			root.dragBall( );
			//
		}

		//
		//
		//
		public function mouseUpHandler ( eventOBJ:MouseEvent ):void
		{
			//
			root.releaseBall( );
			//
		}

		//
		//
		//
		private function addLog ( logText:String ):void
		{
			//
			root.addLog( logText );
			//
		}

		//
		//
		//
	}

	//
	//
	//
}
//
//
//end

Not much changed: new constants appeared ( father has been renamed to root, because these classes are "simple" classes, they aren't inherited from DisplayObject ).

Check it here : motionball

Source: motionball

If the code is too sensitive, set SENSITIVITY to a higher valuse. If you want, you can comment drawDifference out.

Have a good time, i do headkicking professionally.

MilGra

 


 
 
Name: Milan Toth (MilGra)
Location: Koka, Hungary
Age: 24
Flash experience: 4 years
Job: flash developer/web designer
Website: http://www.tetrapod.ini.hu/
 
 
| Homepage | News | Games | Articles | Multiplayer Central | Reviews | Spotlight | Forums | Info | Links | Contact us | Advertise | Credits |

| www.smartfoxserver.com | www.gotoandplay.biz | www.openspace-engine.com |

gotoAndPlay() v 3.0.0 -- (c)2003-2008 gotoAndPlay() Team -- P.IVA 03121770048