Lately I've been playing around with some code-generated drawing and animation. The following is a tutorial describing how I created a tree that sways in the wind. The user generates a small but powerful source of wind by clicking and dragging around the stage. Here is what the final movie looks like:
Drawing the Tree
The tree movie consists of two classes — the main movie or Tree class which generates the tree and handles events, and the Branch class which represents each branch of the tree. Tree.as looks like this:
// Tree.as
package {
import flash.display.*;
import flash.geom.Point;
public class Tree extends MovieClip{
private var trunk:Branch;
public function Tree(){
trunk = new Branch(-90, new Point(stage.stageWidth/2, stage.stageHeight));
addChild(trunk);
}
}
}
At this stage the Tree class is pretty basic. It creates a new branch called trunk at an angle of -90 degrees (straight up), and centers it horizontally at the bottom of the movie.
Branch.as looks like this:
// Branch.as
package {
import flash.display.*;
import flash.geom.Point;
public class Branch extends Shape{
// class vars
private static const baseLength:Number = 100;
// instance vars
private var angle:Number;
private var endPoint:Point;
public function Branch(initialAngle:Number, origin:Point){
angle = initialAngle;
x = origin.x;
y = origin.y;
endPoint = getEndPoint();
drawBranch();
}
private function drawBranch():void{
graphics.clear();
graphics.moveTo(0, 0);
graphics.lineStyle(10, 0x000000);
graphics.lineTo(endPoint.x, endPoint.y);
}
// Using angle and baseLength as polar coordinates get the endPoint of the line as cartesian coordinates.
private function getEndPoint():Point{
return Point.polar(baseLength, degToRad(angle));
}
private static function degToRad(deg:Number):Number{
return deg/57.3;
}
private static function radToDeg(rad:Number):Number{
return rad*57.3;
}
}
}
Branch() takes an initial angle and a point. The point is the branch's origin — where it connects to the rest of the tree or the ground in the case of the trunk. The function drawBranch() uses the Shape class's graphics property to draw out the graphical representation of the branch. This is simply a line drawn from the branches origin to an end point. The endPoint is found using the Point class's polar() method to convert the polar coordinates defined by angle and baseLength, to cartesian coordinates. This is done in getEndPoint(). degToRad() is a utility function that converts a given number of degrees into radians. The sister funciton to degToRad() is radToDeg() which unsurprisingly converts radians to degrees. These are particularly useful functions since some actionScript classes use radians and others use degrees.
Compiling and running Tree will draw a line on the screen.
Not too interesting yet, but this is about to change. The clever thing about what we are about to do is that we won't have to do much to Tree.as to draw the rest of the tree. The Branch class will take care of that with a little bit of recursion. Once each branch is done initializing it will create two more slightly smaller branches at different angles. Updates are in bold.
// Branch.as
package {
import flash.display.*;
import flash.geom.Point;
public class Branch extends Shape{
// class vars
private static const baseLength:Number = 100;
private static const minScale:Number = 0.2;
private static const slope:Number = 0.8;
// instance vars
private var angle:Number;
private var endPoint:Point;
private var globalEndPoint:Point;
private var thisParent:MovieClip
private var children:Array = [];
public function Branch(initialAngle:Number, scale:Number, origin:Point, parentClip:MovieClip){
angle = initialAngle;
x = origin.x;
y = origin.y;
thisParent = parentClip;
scaleX = scaleY = scale;
endPoint = getEndPoint();
globalEndPoint = localToGlobal(endPoint);
drawBranch();
thisParent.addChild(this);
if(scale*slope > minScale){
sproutBranches(scale*slope);
}
}
private function sproutBranches(scale:Number):void{
// new branch angles are augmented by a random +- 10 degrees
var branchLeft:Branch = new Branch(angle - 30, scale, globalEndPoint, thisParent);
var branchRight:Branch = new Branch(angle + 30, scale, globalEndPoint, thisParent);
children.push(branchLeft);
children.push(branchRight);
}
private function drawBranch():void{
graphics.clear();
graphics.moveTo(0, 0);
graphics.lineStyle(10, 0x000000);
graphics.lineTo(endPoint.x, endPoint.y);
}
// Using angle and baseLength as polar coordinates get the endPoint of the line as cartesian coordinates.
private function getEndPoint():Point{
return Point.polar(baseLength, degToRad(angle));
}
// convert degerees to radians
private static function degToRad(deg:Number):Number{
return deg/57.3;
}
// convert radians to degrees
private static function radToDeg(rad:Number):Number{
return rad*57.3;
}
}
}
One very important thing to take note of is the last few lines of the Branch constructor. Here we call sproutBranches(), a function that attaches two new branches to the current branch. Here we check the current branch's scale against the minScale constant — the minimum allowed size for a branch. If a branch's scale has reached the lower limit for branch size, it will not call sproutBranches(). It will not create any new child branches. If we did not do this check flash runtime would get caught in an infinite loop and would soon crash.
The sproutBranches() function creates two new branches at + and - 30 degrees from the angle of the current branch. They are then added to the children array for later reference.
We've also added globalEndPoint which will be used to keep track of each branch's endPoint relative to the root coordinate plane. This is passed on to child branches to tell them where to set their origins.
We will need to change the parameters passed to trunk in Tree.as. Here we are telling trunk to render at full scale and setting its parent to the root MovieClip. We can also remove the addChild(tree) line since the Branch constructor takes care of adding branches to the stage.
// Tree.as trunk = new Branch(-90, 1, new Point(stage.stageWidth/2, stage.stageHeight), this);
Compile and run this iteration. You should see a more densely populated tree.
That's not bad but it doesn't look very natural. We could improve this by adding a little randomness to the branches. Change lines 37 and 38 of Branch.as to look like this:
// Branch.as var branchLeft:Branch = new Branch(angle - 30 + Math.random()*20 - 10, scale, globalEndPoint, thisParent); var branchRight:Branch = new Branch(angle + 30 + Math.random()*20 - 10, scale, globalEndPoint, thisParent);
Now all of the the branches should be rotated + or - 30 degrees from their parent branches plus and additional random amount somewhere between 10 and -10. This looks more natural:
Generating Wind
I could easily spend all day tweaking the size and shape of this tree but then we'd never get to animate it. Time to move on.
The first thing we're going to do is write some event handlers. One handler will trigger the "generation of wind" when the user clicks somewhere on the stage. Another handler will do the wind generation by moving the branches on ENTER_FRAME events. And finally, another handler will remove the wind generation event handler when the user releases the mouse button.
// Tree.as
package {
import flash.display.*;
import flash.geom.Point;
import flash.events.*;
public class Tree extends MovieClip{
private var trunk:Branch;
public function Tree(){
trunk = new Branch(-90, 1, new Point(stage.stageWidth/2, stage.stageHeight), this);
stage.addEventListener(MouseEvent.MOUSE_DOWN, startWind);
stage.addEventListener(MouseEvent.MOUSE_UP, endWind);
}
private function startWind(e:MouseEvent):void{
addEventListener(Event.ENTER_FRAME, generateWind);
}
private function endWind(e:MouseEvent):void{
removeEventListener(Event.ENTER_FRAME, generateWind);
}
private function generateWind(e:Event):void{
trunk.bend();
}
}
}
The other piece we need to add in this step is the bend() method of the Branch class:
// Branch.as
public function bend():void{
angle = radToDeg(Math.atan2(mouseY + endPoint.y, mouseX + endPoint.x));
endPoint = getEndPoint();
globalEndPoint = localToGlobal(endPoint);
drawBranch();
}
This method finds the angle of an imaginary line between the mouse and the endPoint of the branch, moves the endPoint so that it lies on that imaginary line, then redraws the branch. It will take a few frames but the branch will end up pointing at the mouse.
Next, we want this method to propagate through the whole tree. This part is a little bit magical. After the bend() function we will reposition each of the branch's children to the the branch's endPoint and then call bend() on each. Remember that each branch has at most only 2 child branches, but since the bend() method is the same for each child branch as it is for the trunk, it will in turn call bend() on the child branches of each of those branches and so on until there are no child branches left. I've broken this task out into a function called positionChildren():
// Branch.as
public function bend():void{
angle = radToDeg(Math.atan2(mouseY + endPoint.y, mouseX + endPoint.x)) ;
endPoint = getEndPoint();
globalEndPoint = localToGlobal(endPoint);
drawBranch();
positionChildren();
}
private function positionChildren():void{
globalEndPoint = localToGlobal(endPoint);
for each(var child:Branch in children){
child.x = globalEndPoint.x;
child.y = globalEndPoint.y;
child.bend();
}
}
Now all the branches point toward the mouse when the user clicks on the stage.
This movement happens a little too quickly. Also, if the mouse is supposed to be the point of wind generation then they should be moving away from it rather than towards it. Let's slow it down and reorient the branches by modifying the bend() function of the Branch class:
// Branch.as
public function bend():void{
var windAngle:Number = radToDeg(Math.atan2(mouseY + endPoint.y, mouseX + endPoint.x)) - 180;
if(windAngle - angle < -180){
windAngle += 360;
} else if(windAngle - angle> 360){
windAngle -= 360;
}
if(Math.abs(windAngle - angle) > 1){
angle += (windAngle - angle)/8;
endPoint = getEndPoint();
globalEndPoint = localToGlobal(endPoint);
drawBranch()
}
positionChildren();
}
First we flip the angle we're working with (the angle between the mouse and the endpoint of the branch) and store it in a variable called windAngle. This will make the branches point away from the mouse rather than toward it. It is the target angle that we want the branch's angle to eventually reach. In the following if() statement we fudge the numbers a bit so that the difference between the target angle and the current angel is always more than -180 and less than 180. This is to prevent erratic behavior at certain angles. For example, if the target angle were 10 and the branch's current angle were -10 we would want the difference to be considered to be 20 degrees (the short way around the circle) rather than -340 degrees (the long way around).
The next step determines how quickly the branch rotates to the target angle. We reset the branch's angle to a number that is somewhere between the current angle and windAngle. We adjust the angle by 1/8 of the difference between angle and windAngle. This is similar to adding a motion tween with an ease out property to a frame in the timeline.
Next lets add resistance. We'll add an another ENTER_FRAME event listener to the Tree class. On every frame the event handler resist() will call a method of the Branch class called returnToRest(). returnToRest() will work in opposition to bend() and attempt to move the branch back to it's original orientation.
// Tree.as
package {
import flash.display.*;
import flash.geom.Point;
import flash.events.*;
public class Tree extends MovieClip{
private var trunk:Branch;
public function Tree(){
trunk = new Branch(-90, 1, new Point(stage.stageWidth/2, stage.stageHeight), this);
stage.addEventListener(MouseEvent.MOUSE_DOWN, startWind);
stage.addEventListener(MouseEvent.MOUSE_UP, endWind);
addEventListener(Event.ENTER_FRAME, resist);
}
private function resist(e:Event):void{
trunk.returnToRest();
}
private function startWind(e:MouseEvent):void{
addEventListener(Event.ENTER_FRAME, generateWind);
}
private function endWind(e:MouseEvent):void{
removeEventListener(Event.ENTER_FRAME, generateWind);
}
private function generateWind(e:Event):void{
trunk.bend();
}
}
}
The first change to the Branch class is to store the angel that the branch starts out at. This is the variable restAngle. Along with angle set it to initialAngle in the constructor.
//Branch.as
public var restAngle:Number;
public function Branch(initialAngle:Number, scale:Number, origin:Point, parentClip:MovieClip){
angle = restAngle = initialAngle;
...
The returnToRest() function works similarly to bend() in that once it is called on trunk it propagates through the entire tree. It uses the same technique that we used to bend the branch away from the mouse, to bend it back to restAngle. returnToRest() makes use of the function unbendChildren() which does essentially the same thing as positionChildren(). It moves child branches to the current branch's endPoint and calls returnToRest() on each of them.
// Branch.as
public function returnToRest():void{
if(Math.abs(restAngle - angle) > 1){
angle += (restAngle - angle)/8;
endPoint = getEndPoint();
globalEndPoint = localToGlobal(endPoint);
// redraw
drawBranch();
}
unbendChildren();
}
private function unbendChildren():void{
globalEndPoint = localToGlobal(endPoint);
for each(var child:Branch in children){
child.x = globalEndPoint.x;
child.y = globalEndPoint.y;
child.returnToRest();
}
}
Compile and run the movie and you should have a tree that bends away from the mouse when you click and drag around the stage.
It seems to me that smaller branches closer to the source of wind should be affected more than those that are further away from it. I created a strength variable in the bend() function that is used to influence how much a branch will bend on each frame. The value of strength is the distance between the mouse and the branch's endPoint. This is found by using the pythagorean theorem (a2 + b2 = c2) on distX and distY.
// Branch.as
public function bend():void{
var windAngle:Number = radToDeg(Math.atan2(mouseY + endPoint.y, mouseX + endPoint.x)) - 180;
var distX:Number = mouseX + endPoint.x;
var distY:Number = mouseY + endPoint.y;
var strength:Number = Math.sqrt(distX*distX + distY*distY);
if(windAngle - angle < -180){
windAngle += 360;
} else if(windAngle - angle> 360){
windAngle -= 360;
}
if(Math.abs(windAngle - angle) > 1){
angle += (windAngle - angle)/strength;
endPoint = getEndPoint();
globalEndPoint = localToGlobal(endPoint);
drawBranch()
}
positionChildren();
}
Additionally, larger branches should not bend as much as smaller ones since they are thicker and less flexible. The variable strength can be truncated based on the branch's scale:
var strength:Number = (Math.sqrt(distX*distX + distY*distY)*scaleX*scaleX)/5;
Multiplying the distance by scaleX twice exaggerates the difference in how much a smaller branch bends compared to how much a larger branch bends. Dividing it all by 5 decreases the overall strength of the wind.
I have one final adjustment before calling it a day. The wind is a little too steady for my taste. Adding some turbulence will make it a little more realistic. Update the generateWind() function of Tree to look like this:
// Tree.as
private function generateWind(e:Event):void{
var turbulence:Number = Math.random()*40 - 20;
trunk.bend(turbulence);
}
turbulence is a random number between -20 and 20. Pass this to bend(). In bend() add turbulence to windAngle:
// Branch.as
public function bend(turbulence:Number):void{
var windAngle:Number = radToDeg(Math.atan2(mouseY + endPoint.y, mouseX + endPoint.x)) - 180;
windAngle += turbulence;
...
Don't forget to pass it on to positionChildren() at the end of bend()...
positionChildren(turbulence);
...and to bend() again in positionChildren():
child.bend(turbulence);
The final classes should look like this:
// Tree.as
package {
import flash.display.*;
import flash.geom.Point;
import flash.events.*;
public class Tree extends MovieClip{
private var trunk:Branch;
public function Tree(){
trunk = new Branch(-90, 1, new Point(stage.stageWidth/2, stage.stageHeight), this);
stage.addEventListener(MouseEvent.MOUSE_DOWN, startWind);
stage.addEventListener(MouseEvent.MOUSE_UP, endWind);
addEventListener(Event.ENTER_FRAME, resist);
}
private function resist(e:Event):void{
trunk.returnToRest();
}
private function startWind(e:MouseEvent):void{
addEventListener(Event.ENTER_FRAME, generateWind);
}
private function endWind(e:MouseEvent):void{
removeEventListener(Event.ENTER_FRAME, generateWind);
}
private function generateWind(e:Event):void{
var turbulence:Number = Math.random()*40 - 20;
trunk.bend(turbulence);
}
}
}
// Branch.as
package {
import flash.display.*;
import flash.geom.Point;
public class Branch extends Shape{
// class vars
private static const baseLength:Number = 100;
private static const minScale:Number = 0.2;
private static const slope:Number = 0.8;
// instance vars
private var angle:Number;
private var endPoint:Point;
private var globalEndPoint:Point;
private var thisParent:MovieClip
private var children:Array = [];
public var restAngle:Number;
public function Branch(initialAngle:Number, scale:Number, origin:Point, parentClip:MovieClip){
angle = restAngle = initialAngle;
x = origin.x;
y = origin.y;
thisParent = parentClip;
scaleX = scaleY = scale;
endPoint = getEndPoint();
globalEndPoint = localToGlobal(endPoint);
drawBranch();
thisParent.addChild(this);
if(scale*slope > minScale){
sproutBranches(scale*slope);
}
}
private function sproutBranches(scale:Number):void{
// new branch angles are augmented by a random +- 10 degrees
var branchLeft:Branch = new Branch(angle - 30 + Math.random()*20 - 10, scale, globalEndPoint, thisParent);
var branchRight:Branch = new Branch(angle + 30 + Math.random()*20 - 10, scale, globalEndPoint, thisParent);
children.push(branchLeft);
children.push(branchRight);
}
public function bend(turbulence:Number):void{
var windAngle:Number = radToDeg(Math.atan2(mouseY + endPoint.y, mouseX + endPoint.x)) - 180;
windAngle += turbulence;
var distX:Number = mouseX + endPoint.x;
var distY:Number = mouseY + endPoint.y;
var strength:Number = (Math.sqrt(distX*distX + distY*distY)*scaleX*scaleX)/5;
if(windAngle - angle < -180){
windAngle += 360;
} else if(windAngle - angle> 360){
windAngle -= 360;
}
if(Math.abs(windAngle - angle) > 1){
angle += (windAngle - angle)/strength;
endPoint = getEndPoint();
globalEndPoint = localToGlobal(endPoint);
drawBranch()
}
positionChildren(turbulence);
}
private function positionChildren(turbulence:Number):void{
globalEndPoint = localToGlobal(endPoint);
for each(var child:Branch in children){
child.x = globalEndPoint.x;
child.y = globalEndPoint.y;
child.bend(turbulence);
}
}
private function drawBranch():void{
graphics.clear();
graphics.moveTo(0, 0);
graphics.lineStyle(10, 0x000000);
graphics.lineTo(endPoint.x, endPoint.y);
}
public function returnToRest():void{
if(Math.abs(restAngle - angle) > 1){
angle += (restAngle - angle)/8;
endPoint = getEndPoint();
globalEndPoint = localToGlobal(endPoint);
// redraw
drawBranch();
}
unbendChildren();
}
private function unbendChildren():void{
globalEndPoint = localToGlobal(endPoint);
for each(var child:Branch in children){
child.x = globalEndPoint.x;
child.y = globalEndPoint.y;
child.returnToRest();
}
}
// Using angle and baseLength as polar coordinates get the endPoint of the line as cartesian coordinates.
private function getEndPoint():Point{
return Point.polar(baseLength, degToRad(angle));
}
// convert degerees to radians
private static function degToRad(deg:Number):Number{
return deg/57.3;
}
// convert radians to degrees
private static function radToDeg(rad:Number):Number{
return rad*57.3;
}
}
}
Now the tree quivers in the wind as well.
Bonus
Here are a few screenshots I took during the development of this movie. These are all unexpected results from testing and debugging. This demonstrates how tweaking one or two variables can result in vastly different patterns. There is much room for exploration here.
This article was originally published on sethmabbott.com.
Every once in a while we are asked to make a game for a client. I recently worked on a game that centered—as many games do—around a bouncing ball. I thought this would be a good opportunity to put together the first Killswitch multi-part flash tutorial. In this tutorial we'll build a simple but highly customizable billiards game. Credit goes to Tonypa's vector tutorial for some of the concepts and techniques on which this game is based. There is also a helpful article on Euclidean vectors on Wikipedia.
In this first installment we'll get things started with vectors—the basic building block of the game—and get a ball bouncing around inside a confined space.
So, What's a Vector?
This isn't drawing with vectors that we're talking about here. This is physics. A vector is essentially a point (x and y coordinates) and a direction. It can be used to describe the position, direction and velocity of an object, describe forces like wind or gravity, describe obstacles like walls, determining collisions and so on.
For this game I've created a Vector class which has a number of useful methods that are helpful for manipulating vectors. Ultimately they boil down to things like the pythagorean theorem, sine and cosine—trigonometry stuff.
The constructor takes 4 attributes: x and y coordinates, deltaX (horizontal movement component) and deltaY (vertical movement component).
var myVector:Vector = new Vector(1, 1, 4, 3);
Vector Playground
Let's try it out. Download a finished version of this movie for reference if you like.
You'll want to download the Vector.as file.
Create a new flash (AS3) project. Name it something like “vectorPlayground.fla”. Save the project in the same directory as Vector.as or be sure to set the classpath to the directory of Vector.as (in publish settings). Directions on how to do this can be found in Adobe's Flash documentation.
Create a Sprite called “canvas” and add it to the stage. We'll use the graphics package to draw the vectors.
var canvas:Sprite = new Sprite(); addChild(canvas);
Write a function for drawing vectors:
function drawVector(vector:Vector, color:uint):void{
canvas.graphics.lineStyle(1, color);
canvas.graphics.moveTo(vector.x, vector.y);
canvas.graphics.lineTo(vector.x + vector.deltaX, vector.y + vector.deltaY);
}
This function takes a vector and a color for the lines it will draw.
Now create a vector and draw it on the canvas:
var myVector:Vector = new Vector(10, 10, 50, 300); drawVector(myVector, 0x3F3F39);
try out some of the Vector methods:
// set the vector's angle in degrees myVector.setAngle(45); drawVector(myVector, 0xFF55BB); // a pink line // set the vector's length myVector.setLength(50); drawVector(myVector, 0x5555FF); // a short blue line drawn on top of the pink one // resets the vectors length and angle respectively myVector.setPolar(200, 10); drawVector(myVector, 0x55FF55); // a green line // the getIntersection() method is used to find the point where two vectors intersect. var vector2:Vector = new Vector(100, 10, -20, 200); drawVector(vector2, 0xDD2200); // a dark red line var intersection:Point = vector2.getIntersection(myVector); // draw a circle at the intersection point (x, y, radius) canvas.graphics.drawCircle(intersection.x, intersection.y, 2);
Ok, enough of that for now. It's time start building the pool game.
The Game
Download a finished version of this movie for reference if you like. Preview the finished movie.
Let's start with a clean FLA. Call it “poolParty.fla”. Create a folder named “classes” in the same directory as pollParty.as and put Vector.as in there. Make sure that you set the classpath to the classes directory. Set the size of the document to 250x380. Set the background color to something dark (pool table green perhaps) because we'll be playing with the (white) cue ball to start.
I've created a Ball class that is essentially a circle that has its own vector. In a later installment we'll spice it up a bit with some nicer graphics. This iteration of the Ball class has a method that draws a circle at the x and y coordinates of the Vector. The other method of note is moveAlongVector().
// Ball.as
public function moveAlongVector():void{
drawMe();
// put a cap on how fast the ball can move.
if(vector.getLength() > radius){
vector.setLength(radius);
}
vector.x += vector.deltaX;
vector.y += vector.deltaY;
x = vector.x;
y = vector.y;
}
This method draws the ball at its current location and then sets the vectors coordinates to the endpoint of the vector and moves the sprite to that point. Calling this repeatedly animates the ball along a line defined by its vector.
Download Ball.as and save it in the classes folder.
Create a ball and add it to the stage. The constructor takes x and y coordinates, deltaX and deltaY, a radius and a color:
// a global radius for all balls var radius:Number = 7; var cueBall:Ball = new Ball(180, 50, 1, 4, radius, 0xFFFFFF); addChild(cueBall);
This places a ball on the stage and sets its vector pointing down and to the right. The Ball constructor function draws the ball for us with a call to the private method drawMe().
Let's make it move. In poolParty.fla set the frame rate to something high like 60fps. Write an event listener to move the ball along its vector:
addEventListener(Event.ENTER_FRAME, updateMovement);
function updateMovement(e:Event):void{
cueBall.moveAlongVector();
}
Test this and you should see the ball glide off the table.
Create some borders for the table by making vectors that run along the edges of the stage. I set a margin variable (roughly the diameter of the ball) to set off the borders so there will be room to add in pockets later.
var margin:Number = 16;
// borders
var top:Vector = new Vector(margin, margin, 250 - margin*2, 0);
var right:Vector = new Vector(250 - margin, margin, 0, 380 - margin*2);
var bottom:Vector = new Vector(margin, 380 - margin, 250 - margin*2, 0);
var left:Vector = new Vector(margin, margin, 0, 380 - margin*2);
var walls:Array = [top, right, bottom, left];
You may find it reassuring to know where your vectors are. Feel free to draw them in using the drawVector() function that we used earlier. Don't forget to add the canvas first:
var canvas:Sprite = new Sprite();
canvas.graphics.lineStyle(1, 0x000000);
addChild(canvas);
for each(var w in walls){
drawVector(w, 0xFF00EE);
}
In our updateMovement() function we'll want to add something to check for collisions. Because a ball might hit more than one thing at a time (other balls for example) we'll call this function determineFinalBounce() and pass it the ball and walls as arguments:
function updateMovement(e:Event):void{
cueBall.moveAlongVector();
determineFinalBounce(cueBall, walls);
}
The determineFinalBounce() function itself looks like this:
function determineFinalBounce(ball:Ball, obstacles:Array):void{
var vector = ball.getVector();
var radius = ball.getRadius();
var willBounce:Boolean = false;
// check walls for collisions and bounce accordingly
for each(var o in obstacles){
var vectorToObstacle:Vector = shortestDistance(vector, o);
if(vectorToObstacle.getLength() <= radius){
bounce(vector, vectorToObstacle, radius);
}
}
}
This function checks all of the obstacles (walls) to see if the ball is close enough to any wall such that it should bounce off of it.
You'll notice that there are two functions missing from the picture still: shortestDistance() and bounce(). Let's look at shortestDistance() first.
shortestDistance() is used to figure out the point of the wall that is closest to the ball.
function shortestDistance(v1:Vector, v2:Vector):Vector{
// vector from start of v2 to start of v1
var v2ToV1:Vector = new Vector(v2.x, v2.y, v1.x - v2.x, v1.y - v2.y);
// create vector from ball to closest part of v2
var shortest:Vector = v2ToV1.project(v2.normalLeft());
// move start of shortest to start of v1.
shortest.x = v1.x;
shortest.y = v1.y;
// rotate shortest 180°
shortest.deltaX *= -1;
shortest.deltaY *= -1;
return shortest;
}
shortestDistance() uses two Vector methods that we haven't seen before:
normalLeft() returns a vector that runs perpendicular to the given vector. Think of it as the same vector rotated 90° counterclockwise.
project() returns a vector that is a “projection” of one vector onto another. Imagine one vector casting a shadow onto the other.
The whole function looks something like this if we drew all of the vectors involved line by line:
if the length of the vector returned by shortestDistance() is less than the radius of the ball, then we know that it has come in contact with the wall. Once we have figured out which wall the ball has most immediately come into contact with, we need to make it bounce off of that wall.
bounce() takes the vector of the ball, shortest (the vector representing the shortest distance between the ball and the wall), and the ball's radius as arguments.
// given a vector and the shortest distance between vector's endpoint and an obstacle
// move the vector back to the point where it makes contact (assuming a radius),
// and reflect its path.
function bounce(v1:Vector, v2:Vector, radius:Number):void{
// the amount and direction to back up
var reverse:Vector = new Vector(v2.x, v2.y, -v2.deltaX, -v2.deltaY);
reverse.setLength( (radius+1) - v2.getLength() );
var reverseEP:Point = reverse.getEndpoint();
//surface off of which reflection will occur
var surface:Vector = v2.normalLeft();
var proj1:Vector = v1.project(v2);
var proj2:Vector = v1.project(surface);
proj1.deltaX *= -1;
proj1.deltaY *= -1;
var reflection:Vector = proj1.sum(proj2);
reflection.x = reverseEP.x;
reflection.y = reverseEP.y;
if(proj1.dotProduct(v2) > 0){
reflection.deltaX = v1.deltaX;
reflection.deltaY = v1.deltaY;
}
v1.resetFromVector(reflection);
}
This function does the following:
It finds the point at which the ball makes contact with the wall. It does this by creating a vector reverse, starting at the center of the ball and at an angle of 180° from the balls current vector. The length is set to the difference between the radius and the shortest distance to the wall. The endpoint of reverse is the point where the ball is when it makes contact with the wall.
Then the ball's vector is reflected at an angle found by summing the projection of the ball's vector onto a line parallel to the wall, and the projection of the ball's vector onto a line perpendicular to the wall. sum() is a method of Vector that simply adds the deltaX and deltaY components of two vectors returning a vector with an angle and length somewhere between the two.
There are two other new methods here. The first is dotProduct() which returns a number. I won't go into the details of how the dot product is found. The important part is that if the dot product of two vectors is positive it means that they are pointing toward each other, if it is negative then they are pointing away. bounce() checks the dot product of v2 (shortest) and the reversed projection of v1 onto v2. If it is greater than 0 then we know that the ball was moving away from the wall to begin with, and that reflecting it would be a bad idea because it would turn the ball back toward the wall, which would result in unnatural behavior.
The function looks something like this:
Add the shortestDistance() and bounce() functions to poolParty.fla and test it out. You should see your ball bouncing around the screen like an old-school screen saver.
Now, I know someone is asking “Hang on, why do we need to mess around with all of these vectors? Why not just check the ball's coordinates against the borders of the table and tell it to bounce back when it's gone too far? That would be a lot easier.” That is true, it would be easier and it would work. But the vector technique allows for borders that are not strictly horizontal or vertical. It allows the game designer to build a table with 3, 5, 6 or however many sides they want. Go ahead and try it. Maybe you want to rotate the table a bit or give it a three-dimensional feel with some pseudo-perspective. We will also be using the vectors to determine the correct angle of bouncing when two or more balls collide with another.
That does it for this stage of the the tutorial. Next time we'll add some more balls and smack 'em around.
Lately I've been looking at data. Not always so much data in itself but how data is presented. We are presented with far more information on a daily basis than we can possibly store or comfortably process. Much of it gets overlooked or tossed aside. There is no place where this is more prominent than on the Web. I can't think of how many times I've dismissed a site during a search simply because it was too much trouble find what I was looking for. We don't have time to sift through overbearing gridlines, confusing navigation, lack of whitespace and pages of thousands of links, images and special offers, each of which appear to be the most important thing on the page.
In Envisioning Information Edward Tufte spends a considerable amount of time discussing ways in which various charts, graphs, maps, timetables and illustrations have made attempts at "escaping flatland" &emdash; representing the complex data of our four-dimentional world on a two-dimentional surface. The more effective examples tell their story in an intuitive and sometimes profound way. They not only make the mundane beautiful, they make complex data sets easier to understand.
Consider the Tag Cloud
Actually, I'd prefer that people would forget tag clouds. I'm tired of tag clouds. Granted, a tag cloud is intended to digest data into a more immediately understandable form, but in most cases it comes out as the sort of thing that makes typographers cry (fig 1). They take a potentially useful tool for navigation and searching, and mangle it into a bowl of alphabet soup through which you must sift in order find what you are really looking for.

fig. 1
Here's an exercise in which alternative ways of looking at tags are considered. Maybe it will inspire something better than tag clouds. The data is a list of tags for an archive of fictional articles.
First I'd like to point out that this is primarily an exercise. I'm going to be examining details about a set of data that visitors to a Web site may or may not have interest in. The point is to illustrate how representation of data can highlight different characteristics of that data, and to strive for immediacy of understanding of the data.
Good Digestion
It does seem to be important to many site designers to convey how many things have been tagged by a given tag, and how each tag's frequency relates to that of others. Lets back up a bit and start with the common default way to do this which is to attach numbers to the list of tags (fig. 2). In this example tags are listed in the order they were created.

fig. 2
This is a bit more precise information than a tag cloud presents but it isn't as immediate. There is no apparent order. It is clear that there are more things tagged with Firefox than with Mongrel but you have to work a little (yes, very little) to get that information. It takes a bit longer to figure out which is the greatest and which is the least. Consider how long it would take to figure out which tag is closest to the average.
No, you aren't likely to need to know which tag is closest to the average, but this illustrates the point that the information is not readily consumed. It is like a raw slab of meat. If you ate it now it wouldn't taste very good and it could make you very sick.
Order out of Chaos
The fact that these tags are listed in the order they were created is lost on us. We have no way of knowing that. That may not be the most useful piece of information so lets set it aside for a while and highlight something else.

fig. 3
Here the tags adhere to a clear hierarchy but it still takes a moment to digest. Consider how long it takes to figure out which pair of adjacent tags has the greatest disparity in frequency between them.
Go Long
The problem with numbers is that they are abstract. It is easy to picture 5 apples (I see 5 apples arranged like the dots on six-sided die), but it is more difficult to picture 29 apples. It is less concrete. This is where what Tufte refers to as "small multiples" comes in handy. (1)

fig. 4
Instead of displaying numbers we're now showing actual taggings. Each dot represents a tag on an article. Small multiples provide a reading of the data on a small scale (one dot to the next), and come together as a whole to tell a story on a larger scale.
The list is beginning to behave like a bar graph, showing relative frequency from one tag to the next, but it is better than that. A bar graph can show relationships in value between multiple things, but without numbers those relationships are vague. Their scale is relative. Here the relationships are more explicit, and you don't have to read anything or do any math. Cognition is immediate. While it would be tedious to count each dot in order to know the exact number of something, you do get a general sense of what's going on.
Keep Going
The dots have freed us to make another improvement. This list isn't very long, but it could be. Searching for a specific topic in that list could become tedious if it is not alphabetized. We will make that change and a few others.
Each article can have multiple tags. So clicking on Firefox will bring up a list if articles, some which may be tagged with other things. It would be nice to show that relationship. Our next iteration (fig.5) highlights all relevant taggings. It would also be nice to see which tag is currently selected. This one is interactive.
fig. 5 (interactive)
Clicking on Firefox causes all dots on its bar to turn orange as well as any dots (taggings) that are associated with articles that have been tagged with Firefox. This tells us (very quickly) that of the articles tagged with Firefox, three are also tagged with Thunderbird., none are tagged with Python or Leopard, etc.
The animation is arguably a bit redundant, but I like the way it looks.
Keep Going!
Have we not learned all there is to learn about these humble tags? Never! These tags still have dark little secrets yet to be gleaned. We are working with two (physical) dimensions but we can impose another on flatland-time.
fig. 6 (interactive)
If we stretch data out along a timeline we can learn more about it. I've added some subtle striping and elongated the dots to emphasize concurrent taggings. We can see that early on in the life of this site Duck Typing was a hot topic but its popularity gradually dwindled, Thunderbird had a short but decent run some time ago, and Python has been making itself known of late for some mysterious reason. Our mundane navigation buttons are starting to take on a life of their own.
All anthropomorphism aside, this arrangement is giving us a lot of information very quickly, and without any numbers. But it comes at a cost. At this point we've nearly tripled the area these tags take up, turning turning our lowly navigation into an ever increasingly space hungry monster. We've also understated the frequency relationship between tags a bit. Perhaps we've gone too far but the exercise is not in vain.
Do Good to Data. Do it Well.
In clarifying data, the goal is not simply to simplify. There is only so much fat that can be cut before data becomes further obfuscated. Sometimes, to tell the story well, we need to reveal more information, but it should be done in a way that does not complicate the reading. It should make clear what was once vague.
Further Reading
I highly recommend the following resources for a more detailed discussion on information design.
- Ben Fry
- Edward Tufte
- Accessible Data Visualization with Web Standards, A List Apart
- Bret Victor, Magic Ink: Information Software and the Graphical Interface
Footnotes
- Arguably these are not small multiples as Edward Tufte describes them because these boxes are all the same, but I contend that each box is unique compared to its neighbors because they each represent a unique relationship to an article.


