- Home
- Tutorials
- Flash
- Intermediate
- Camera-motion controlled ball
Camera-motion controlled ball

Camera-motion controlled ball
Milan Toth
Milan Toth is the Chief Flash Developer of Jasmin Media Group, he created one of the world's biggest flash media server system. He loves Eclipse and OS X, AS3 and JAVA, sci-fi and horror, metal and electronic.
We need a motion detector logic first. We have to capture the actual state of the camera input, and compare it to the previous state, searching for color differences. If color difference occurs, there was motion, and we have to store its coordinate. We can't compare every pixel, because it is very cpu-demanding, we have to use a little stepping.
So our first class will be the MotionDetector class. Create it in Flex/Eclipse.
package
{
import flash.geom.Point;
import flash.media.Video;
import flash.media.Camera;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.Bitmap;
public class MotionDetector
{
// the subject of motion detection
private var video:Video;
// to store previous state
private var oldData:BitmapData;
// to store actual state
private var newData:BitmapData;
// stepping blocks
private var blockSize:Number;
// detection sensitivity
private var sensitivity:Number;
public function MotionDetector ( argVideo:Video ,
argBlockSize:Number ,
argSensitivity:Number )
{
video = argVideo;
blockSize = argBlockSize;
sensitivity = argSensitivity;
oldData = new BitmapData( video.width , video.height , false );
newData = new BitmapData( video.width , video.height , false );
}
public function getDifferences ( ):Array
{
// capturing new state
newData.draw( video );
var differences:Array = [ ];
// looping through points with stepping
for ( var px:int = 0 ; px < newData.width ; px += blockSize )
{
for ( var py:int = 0 ; py < newData.height ; py += blockSize )
{
//getting previous and actual pixel color data
var oldPixel:uint = oldData.getPixel( px , py );
var newPixel:uint = newData.getPixel( px , py );
// checking difference threshold
if ( Math.abs( newPixel - oldPixel ) > sensitivity )
{
// if bigger than sensitivity, storing point
differences.push( new Point( px , py ) );
}
}
}
// save previous state
oldData.copyPixels( newData , newData.rect , new Point( 0 , 0 ) );
return differences;
}
}
}
Nothing magical happens here, we check every nth pixel of the bitmapdatas, and store differences above threshold.
Let's see the main class. We have to add a lot to the code from the previous examples.
FirstCircle.as :
package
{
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.ColorTransform;
import flash.media.Video;
import flash.media.Camera;
import flash.events.Event;
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
public class FirstCircle extends Sprite
{
// circle to difference point collosion radius
private static var RADIUS:Number = 20;
// motion detection stepping
private static var BLOCKS:Number = 5;
// motion detection sensitivity
private static var SENSITIVITY:Number = 700000;
// bitmapdata to show detected differences for better understanding
private var helperData:BitmapData;
// colortransform to darken old difference points
private var helperTransform:ColorTransform;
private var circle:MovingCircle;
private var detector:MotionDetector;
public function FirstCircle( )
{
// default video size
var video:Video = new Video( 320 , 240 );
// bitmap to show helperData
var helper:Bitmap = new Bitmap( );
// getting default camera
var camera:Camera = Camera.getCamera( );
helperData = new BitmapData( 320 , 240 , true , 0x000000 );
helperTransform = new ColorTransform( 1 , 1 , 1 , .7 , 0 , 0 , 0 , 0 );
helper.bitmapData = helperData;
video.attachCamera( camera );
addChild( video );
addChild( helper );
detector = new MotionDetector( video , BLOCKS , SENSITIVITY );
stage.addEventListener( Event.ENTER_FRAME , init );
}
public function init ( event:Event ):void
{
stage.removeEventListener( Event.ENTER_FRAME , init );
stage.addEventListener( Event.ENTER_FRAME , step );
circle = new MovingCircle( );
addChild( circle );
circle.init( );
}
public function step ( event:Event ):void
{
var differences:Array = detector.getDifferences( );
var circlePosition:Point = new Point( circle.x , circle.y );
// loop through difference points
for ( var a:String in differences )
{
// check distance from circle
var dr:Number = Point.distance( circlePosition , differences[a ] );
// if any point is closer than RADIUS
if ( dr < RADIUS )
{
var dx:Number = circlePosition.x - differences[a ].x;
var dy:Number = circlePosition.y - differences[a ].y;
circle.xspeed = dx;
circle.yspeed = dy;
// stop check
break;
}
}
// darkening prevous state of helperData
helperData.colorTransform( helperData.rect , helperTransform );
// loop through difference points
for ( var b:String in differences )
{
var helpRectangle:Rectangle = new Rectangle( differences[b].x ,
differences[b].y ,
BLOCKS ,
BLOCKS );
// drawing a green BLOCKS edge sized rectangle at every point
helperData.fillRect( helpRectangle , 0xff00ff00 );
}
}
}
}
As you see we initialized the video object and the camera here in the main class, so MotionDetector is independent from the camera input, or everything else we want to do with the video. We also created a helper bitmap data to make differences visible to human eye.
After init, we use stage's ENTER_FRAME event further : like motion stepping in MovingCircle class, we trigger motion and collosion detection and difference drawing here. If we would like a cleaner code, we should trigger MovingCircle's step function from here, and delete frame listening from inside.
MovingCircle class stays the same, only two new lines needed for our little program : i made the soccerball smaller in the constructor to make the game more "playable".
package
{
import flash.media.Sound;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.Sprite;
import flash.display.Bitmap;
public class MovingCircle extends Sprite
{
// for realistic movement we use gravity
public static var gravity:Number = 1;
public var xspeed:Number;
public var yspeed:Number;
// we want to "throw" the ball, that's why we need previous positions
// to calculate new speed components
private var oldx:Number
private var oldy:Number;
// gfx will be our "skin"
// sfx will be our sound
private var gfx:Bitmap;
private var sfx:Sound;
// that's how we embed external assets
// we have to define individual class names for them
[ Embed ( source = "soccerball.gif" ) ] private var soccerGFXClass:Class;
[ Embed ( source = "pop.mp3" ) ] private var soccerSFXClass:Class;
public function MovingCircle ( )
{
// instantiating embedded assets
gfx = new soccerGFXClass( );
sfx = new soccerSFXClass( );
addChild( gfx );
// setting gfx smaller
gfx.width = 40;
gfx.height = 40;
// repositioning skin to middle to make bouncing more precise
gfx.x = - width / 2;
gfx.y = - height / 2;
}
// initialization, called after parent addChild
public function init ( ):void
{
// x , y and stage are inherited properties
// watching our width and height not to cross "wall"
x = width / 2 + Math.random( ) * ( stage.stageWidth - width / 2 );
y = height / 2 + Math.random( ) * ( stage.stageHeight - height / 2 );
xspeed = 5 + Math.random( ) * 10;
yspeed = 5 + Math.random( ) * 10;
// starting triggering step function based on enterframe event
addEventListener( Event.ENTER_FRAME , step );
// adding mouseevents to make ourself draggable
addEventListener( MouseEvent.MOUSE_DOWN , dragCircle );
addEventListener( MouseEvent.MOUSE_UP , releaseCircle );
}
private function dragCircle ( event:MouseEvent ):void
{
// when mousedown, we starting to listen to mouse movement of the stage
// to catch stage mouse coordinates
stage.addEventListener( MouseEvent.MOUSE_MOVE , moveCircle );
// we stop stepping during dragging
removeEventListener( Event.ENTER_FRAME , step );
}
private function releaseCircle ( event:MouseEvent ):void
{
// removeing stage mousemove listening
stage.removeEventListener( MouseEvent.MOUSE_MOVE , moveCircle );
// start stepping
addEventListener( Event.ENTER_FRAME , step );
}
private function moveCircle ( event:MouseEvent ):void
{
// storing previous coordinates
oldx = x;
oldy = y;
x = event.stageX;
y = event.stageY;
// calculating new speed components
xspeed = x - oldx;
yspeed = y - oldy;
}
public function step ( event:Event ):void
{
// setting position
x += xspeed;
y += yspeed;
// adding gravity to y component
yspeed += gravity;
// rotating for realistic movement
rotation += xspeed;
// bouncing ball at stage edges
if ( x + xspeed > stage.stageWidth - width / 2 )
{
xspeed *= -1;
sfx.play( ); // playing sound
}
else if ( x + xspeed < width / 2 )
{
xspeed *= -1;
sfx.play( ); // playing sound
}
if ( y + yspeed > stage.stageHeight - height / 2 )
{
yspeed *= -1;
sfx.play( ); // playing sound
}
else if ( y + yspeed < height / 2 )
{
yspeed *= -1;
sfx.play( ); // playind sound
}
}
}
}
Finally, we have to make our movie's size 320x240. Project menuitem - Properties - Actionscript Compiler - Additional compiler arguments :
-default-size 320 240
Everything should work fine now. Check it under attachments.
Spread The Word
Article Series
This article is part 2 of a 2 part series. Other articles in this series are shown below:
-
Camera-motion controlled ball
Attachments
2 Responses to "Camera-motion controlled ball" 
|
said this on 01 Aug 2007 10:27:19 AM CST
Wow...this gave me a BSOD on my Vista computer!
Everything went like...BOOM! |
|
said this on 24 Apr 2008 9:52:31 PM CST
Awesome. Worked great on my Macbook with iSight, once I got the camera working.
|


Author/Admin)