Introduction to collision detection & 2D physics

Bookmark and Share

This is an introductory tutorial on collision detection and physics. Creating 2D physics is often daunting to the beginner flash programmer but as you will soon see it is really not that hard so long as you break everything down into small steps. Here we are going to create a world and populate it with a realistic bouncing ball:

The Flash plugin is required to view this object.

The concepts required here may look daunting but all that needs to be required are the following:

  • Velocity
  • Wall Collision Detection
  • Friction
  • Gravity

Defining the world

Lets start off by defining our bounds and our world as a class like so:

01
package
02
{
03
	import flash.display.Sprite;
04
	import flash.events.Event;
05
	import flash.geom.Rectangle;
06
 
07
	[SWF(width="530", height="397", frameRate="30", backgroundColor="#EEEEEE")]
08
	public class BallWorld extends Sprite
09
	{
10
		public function BallWorld()
11
		{
12
			var world:World = new World(530,397);
13
			addChild(world);
14
		}
15
	}
16
}
17
 
18
import flash.display.Sprite;
19
import flash.events.Event;
20
import flash.geom.Rectangle;
21
import flash.events.MouseEvent;
22
 
23
class World extends Sprite
24
{
25
 
26
	public function World(w:Number, h:Number)
27
	{
28
		var backing:Sprite;
29
		backing = new Sprite();
30
		backing.graphics.lineStyle(1,0xAAAAAA);
31
		backing.graphics.beginFill(0xFFFFFF);
32
		backing.graphics.drawRect(0,0,400,300);
33
		backing.x = w/2 - backing.width/2;
34
		backing.y = h/2 - backing.height/2;
35
		addChild(backing);
36
 
37
		var ball:Ball;
38
		ball = new Ball(20);
39
		ball.x = w/2;
40
		ball.y = h/2;
41
		addChild(ball);
42
 
43
	}
44
}
45
 
46
class Ball extends Sprite
47
{
48
	public var radius:Number = 20;	//	circle radius
49
 
50
	public function Ball(rad:Number)
51
	{
52
		//	Apply params
53
		radius = rad;
54
 
55
		//	Draw Ball
56
		graphics.lineStyle(1,0xAAAAAA);
57
		graphics.beginFill(0xA9C065);
58
		graphics.drawCircle(0,0,radius);
59
		graphics.endFill();
60
 
61
	}
62
}

The Flash plugin is required to view this object.

Here we start with a World class that contains a backing sprite and a Ball Sprite which we have placed in the middle of the bounds. This tute is all about creating some realistic movement based on basic physics so lets get the ball to move.

Velocity

Within the physical world moving objects have a velocity which represents a distance moved over time. In Actionscript, we have a frame ticker which represents time so once every frame we can add the velocity to the coordinate position of our sprite to give the appearance of movement. Velocity in physics is given as a vector but in Actionscript we can provide it as two separate values for each coordinate. Lets add velocity to our ball:

01
class Ball extends Sprite
02
{
03
	public var vx:Number = 0;		//	velocity x
04
	public var vy:Number = 0;		//	velocity y
05
	public var radius:Number = 20;	//	circle radius
06
 
07
	public function Ball(rad:Number)
08
	{
09
		//	Apply params
10
		radius = rad;
11
 
12
		//	Draw Ball
13
		graphics.lineStyle(1,0xAAAAAA);
14
		graphics.beginFill(0xA9C065);
15
		graphics.drawCircle(0,0,radius);
16
		graphics.endFill();
17
 
18
		//	Add Listeners
19
		addEventListener(Event.ENTER_FRAME, onFrame);
20
	}
21
 
22
	private function onFrame(e:Event):void
23
	{
24
		//	Apply Velocity to Position
25
		x += vx;
26
		y += vy;
27
 
28
	}
29
}

Notice nothing much has happened. Our Ball is still sitting in the center of the stage and not moving. The reason it isn’t moving is because we have given it an initial velocity of 0 for both the x and y directions. Lets give this a number value of lets say 5. Add the following to our World class:
01
...
02
var ball:Ball;
03
ball = new Ball(20);
04
ball.x = w/2;
05
ball.y = h/2;
06
 
07
//	Initial Velocity
08
ball.vx = 5;
09
ball.vy = 5;
10
...

Which gives us this:

The Flash plugin is required to view this object.

Bingo! the ball is moving but it is hardly realistic. Firstly the ball leaves the bounds almost immediately so we need to provide some collision detection and to constrain it to the given bounds.

Wall Collision Detection

Let’s add the following handler and variable to our Ball class:

01
private var bounds:Rectangle;
02
...
03
public function Ball(rad:Number)
04
{
05
...
06
	//	Add Listeners
07
	addEventListener(Event.ENTER_FRAME, onFrame);
08
	addEventListener(Event.ADDED, onAdded);
09
}
10
...
11
private function onAdded(e:Event):void
12
{
13
	world = parent as World;
14
	bounds = world.bounds;
15
}

This is going to listen for when we add our ball to the world and at that instance get the properties of our world and apply them to the ball animation.

now add the following to our frame routine:

01
private function onFrame(e:Event):void
02
{
03
	//	Use a temp var for x and y to increase performance
04
	var tx:Number = x;
05
	var ty:Number = y;
06
 
07
	//	Collision Detection with right wall
08
	if(tx + vx > bounds.right - radius)
09
	{
10
		tx = bounds.right - radius;
11
		vx *= -1;
12
	}
13
 
14
	//	Collision Detection with left wall
15
	if(tx + vx < bounds.left + radius) 	{
16
 		tx = bounds.left + radius;
17
 		vx *= -1;
18
	}
19
 
20
  	//	Collision Detection with floor
21
	if(ty + vy > bounds.bottom - radius)
22
	{
23
		ty = bounds.bottom - radius;
24
		vy *= -1;
25
	}
26
 
27
	//	Collision Detection with ceiling
28
	if(ty + vy < bounds.top + radius)
29
	{
30
		ty = bounds.top + radius;
31
		vy *= -1;
32
	}
33
 
34
	//	Apply Velocity to tx and ty
35
	tx += vx;
36
	ty += vy;
37
 
38
	//	Render the change
39
	x = tx;
40
	y = ty;
41
 
42
}

Lastly we need to alter our World class to provide us with the bounds property containing the bounds to constrict the ball to. So lets move our backing instance to become a private instance variable and access its getBounds method for the bounds of the world.
01
class World extends Sprite
02
{
03
...
04
	private var backing:Sprite;
05
...
06
	public function get bounds():Rectangle
07
	{
08
		return backing.getBounds(this);
09
	}
10
}

Within the frame script we are now checking to see if the object has moved beyond the boundary given by the bounds Rectangle. If the bounds have been exceeded for any given direction the direction is reversed based on the boundary exceeded.

Here we also use a temporary variable to make it so that x is only set at the end of the frame script and is only necessary for performance reasons but is a good habit to get into since it is good to keep data and render separate.

The Flash plugin is required to view this object.

So now we have our ball flying away within our bounding box but the movement doesn’t look particularly real as we haven’t added any of the common physical forces to our object such as friction or gravity.

Adding Friction

To add air friction we simply need to multiply the velocity by a given amount between 0 and 1 every frame ie:

1
//	Air Dampening
2
vx *= .99;
3
vy *= .99;

This leaves us with something that looks like the following:

The Flash plugin is required to view this object.

Gravity

The last thing to do is add gravity to simulate the natural pull towards the ground by simply adding to the downward velocity every frame:

01
//	Air Dampening
02
vx *= .99;
03
vy *= .99;	
04
 
05
//	Gravity
06
vy += 1;	
07
 
08
//	Apply Velocity to tx and ty
09
tx += vx;
10
ty += vy;
11
 
12
//	Render the change
13
x = tx;
14
y = ty;

After moving the gravity and friction properties to belong to the World we are left with the following full source code:
001
package
002
{
003
	import flash.display.Sprite;
004
	import flash.events.Event;
005
	import flash.geom.Rectangle;
006
 
007
	[SWF(width="530", height="397", frameRate="30", backgroundColor="#EEEEEE")]
008
	public class BallWorld extends Sprite
009
	{
010
		public function BallWorld()
011
		{
012
			var world:World = new World(530,397);
013
			addChild(world);
014
		}
015
	}
016
}
017
 
018
import flash.display.Sprite;
019
import flash.events.Event;
020
import flash.geom.Rectangle;
021
import flash.events.MouseEvent;
022
 
023
class World extends Sprite
024
{
025
	private var backing:Sprite;
026
	public var friction:Number;
027
	public var gravity:Number;
028
 
029
	public function World(w:Number, h:Number)
030
	{
031
		friction = 0.95;
032
		gravity = 1;
033
 
034
		backing = new Sprite();
035
		backing.graphics.lineStyle(1,0xAAAAAA);
036
		backing.graphics.beginFill(0xFFFFFF);
037
		backing.graphics.drawRect(0,0,400,300);
038
		backing.x = w/2 - backing.width/2;
039
		backing.y = h/2 - backing.height/2;
040
		addChild(backing);
041
 
042
		var ball:Ball;
043
		ball = new Ball(20);
044
		ball.x = w/2;
045
		ball.y = h/2;
046
 
047
		//	Initial Velocity
048
		ball.vx = 5;
049
		ball.vy = 5;
050
 
051
		addChild(ball);
052
 
053
	}
054
 
055
	public function get bounds():Rectangle
056
	{
057
		return backing.getBounds(this);
058
	}
059
}
060
 
061
class Ball extends Sprite
062
{
063
	public var vx:Number = 0;		//	velocity x
064
	public var vy:Number = 0;		//	velocity y
065
	public var radius:Number = 20;	//	circle radius
066
	private var bounds:Rectangle;
067
	private var friction:Number;
068
	private var gravity:Number;
069
 
070
	public function Ball(rad:Number)
071
	{
072
		//	Apply params
073
		radius = rad;
074
 
075
		//	Draw Ball
076
		graphics.lineStyle(1,0xAAAAAA);
077
		graphics.beginFill(0xA9C065);
078
		graphics.drawCircle(0,0,radius);
079
		graphics.endFill();
080
 
081
		//	Add Listeners
082
		addEventListener(Event.ENTER_FRAME, onFrame);
083
		addEventListener(Event.ADDED, onAdded);
084
	}
085
 
086
	private function onAdded(e:Event):void
087
	{
088
		var world:World = parent as World;
089
		bounds = world.bounds;
090
		friction = world.friction;
091
		gravity = world.gravity;
092
	}
093
 
094
	private function onFrame(e:Event):void
095
	{
096
		//	Use a temp var for x and y to increase performance
097
		var tx:Number = x;
098
		var ty:Number = y;
099
 
100
		//	Collision Detection with right wall
101
		if(tx + vx > bounds.right - radius)
102
		{
103
			tx = bounds.right - radius;
104
			vx *= -1;
105
		}
106
 
107
		//	Collision Detection with left wall
108
		if(tx + vx < bounds.left + radius) 		{ 			tx = bounds.left + radius; 			vx *= -1; 		} 		 		//	Collision Detection with floor 		if(ty + vy > bounds.bottom - radius)
109
		{
110
			ty = bounds.bottom - radius;
111
			vy *= -1;
112
		}
113
 
114
		//	Collision Detection with ceiling
115
		if(ty + vy < bounds.top + radius)
116
		{
117
			ty = bounds.top + radius;
118
			vy *= -1;
119
		}
120
 
121
		//	Air Dampening
122
		vx *= friction;
123
		vy *= friction;	
124
 
125
		vy += gravity;
126
 
127
		//	Apply Velocity to tx and ty
128
		tx += vx;
129
		ty += vy;
130
 
131
		//	Render the change
132
		x = tx;
133
		y = ty;
134
 
135
	}
136
}

As you can see by adding gravity we substantially increase the reality factor of the motion.

The Flash plugin is required to view this object.

This entry was posted in Actionscript, Flash, Game development, Scripted Animation and tagged , . Bookmark the permalink. Both comments and trackbacks are currently closed.