Newest Articles

MegaCombs
Flash Media Player
XML Driven Pie Chart
Base Defender
Hangman Game
8 Ball Pool


Popular Articles

True Fullscreen Flash Mode
Create Pong
Base Defender
Hangman Game
3d Rotating Image Cube
Catapult Game


Random Articles

Image Slider
12 Hour Clock with Hands
Focus in on an Image
Growing Tree using Recursion
RSS feed scroller
XHTML Image Mapper


Links

Shapes the Game
Reddit
Newgrounds
TWiT
Link to SwfSpot
Swf Spot

Contact me on Google+



rss feed

8 Ball Pool

8 Ball Pool
AddThis Social Bookmark Button
Description: Learn how to create an 8 ball pool game
Author: John Bezanis
Added: July 9th 2008
Version: Flash 8


Pool is an interesting project for flash because it demonstrates multiple objects interacting with each other, as well as being a fun game to create and play. This tutorial assumes some experience with drawing on the stage, symbols, and basic actionscript knowledge.
We start by creating a new document of dimensions 600x400. Draw a pool table on the stage. One method to assure the table is symmetrical is to draw one quadrant of the table and then duplicate it.

Once the table is drawn, create a new layer and draw a rectangle to fit the area of the green (the inner bounds of the bumpers). Select the rectangle and right click -> convert to symbol. Name this area hitarea and in the properties tab, set the instance name to tablearea. If it isn't exactly the size of the inner square, you can reshape it now. In the properties tab, set Color->Alpha to 0% to make the hitarea box invisible.

Next, we will create the balls. Start by drawing a circle. Set the height and width to 20. Right click -> convert to symbol and name it balls. Set the linkage identifier to balls. Open the symbol and move it to position -10,-10 to center it. In the actions tab, type stop(); to keep it at this frame.

Alter the graphic so it looks like a 1 ball. At frame 2 on the timeline, right click -> insert keyframe. Change this graphic to appear like a 2 ball. Repeat through the 15 ball, and at frame 16, draw a cue ball. Go back to the main stage and delete the ball off of the stage.

Next up, draw the poolstick. Draw the poolstick and convert it to a movie clip. Name it poolstick and give it the linkage identifier poolstick. Set the Y position to 0 and make sure it is centered along the x axis. Go back into the main stage and delete it from the screen.

The image tray is two parts. First, draw a rectangle. Next, drag the movieclip hit area from the library onto the stage and resize it so it is the size of the rectangle. Set the alpha to 0 and give it the instance name trayarea.

Finally, we create a rack em button. Just draw a graphic, convert it to symbol, set the type to button, and set the instance name to newgame.
Add the following code to the main stage and your pool game is good to go:
  1. //Pool by John Bezanis for swfspot.com
  2. //Conversion for degrees to radians, calculated once for increased performance
  3. degreestoradians = Math.PI/180;
  4. //total balls sunk
  5. ballssunk = 0;
  6. //Amount of space in between balls when racked and in the tray
  7. spacer = .2;
  8. //multiply the poolstick's power by 50 percent of its distance from the ball
  9. powermultiplier = .5;
  10. //Percentage balls are slowed down at each frame
  11. friction = .965;
  12. //Determine the interval of time between checking the ball movement/collisions in milliseconds
  13. //a lower number gives better accuracy but is more cpu intense
  14. //if this number is adjusted, friction and powermultiplier should also be adjusted.
  15. movetime = 20;
  16. //Define a function to shuffle an array
  17. Array.prototype.shuffle = function() {
  18.   for (i=0; i<this.length; i++) {
  19.     this.push(this.splice(Math.floor(Math.random()*this.length), 1));
  20.   }
  21. };
  22. //Main function that adds the balls to the table
  23. function newGame() {
  24.   //Set the position of the rack
  25.   rackx = tablearea._x+tablearea._width*.75;
  26.   racky = tablearea._y+tablearea._height/2;
  27.   //attach the 8 ball at the center of the rack
  28.   this.attachMovie('balls', 'ball8', this.getNextHighestDepth(), {_x:rackx, _y:racky});
  29.   //go to the frame with the 8 ball graphic
  30.   ball8.gotoAndStop(8);
  31.   //initialize the x and y veocity to 0
  32.   ball8.xvel = 0;
  33.   ball8.yvel = 0;
  34.   //set the ball to not sunk
  35.   ball8.sunk = 0;
  36.   //create an array with all of the other balls
  37.   ballArray = [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15];
  38.   //shuffle the array, shuffling the position of the balls
  39.   ballArray.shuffle();
  40.   //calculate the horizontal difference between two rows of balls
  41.   horizdiff = Math.sqrt(Math.pow(ball8._width+spacer, 2)-Math.pow((ball8._height+spacer)/2, 2));
  42.   //this array contains the coordinates of the balls within the rack
  43.   ballcoords = [[rackx-horizdiff*2, racky], [rackx-horizdiff, racky-(ball8._height+spacer)/2], [rackx-horizdiff, racky+(ball8._height+spacer)/2], [rackx, racky-(ball8._height+spacer)], [rackx, racky+(ball8._height+spacer)], [rackx+horizdiff, racky-(ball8._height+spacer)*1.5], [rackx+horizdiff, racky-(ball8._height+spacer)*.5], [rackx+horizdiff, racky+(ball8._height+spacer)*.5], [rackx+horizdiff, racky+(ball8._height+spacer)*1.5], [rackx+horizdiff*2, racky-(ball8._height+spacer)*2], [rackx+horizdiff*2, racky-(ball8._height+spacer)], [rackx+horizdiff*2, racky], [rackx+horizdiff*2, racky+(ball8._height+spacer)], [rackx+horizdiff*2, racky+(ball8._height+spacer)*2]];
  44.   //loop through all of the balls, creating them and positioning them in the rack
  45.   for (ballpos=0; ballpos<14; ballpos++) {
  46.     this.attachMovie('balls', 'ball'+ballArray[ballpos], this.getNextHighestDepth(), {_x:ballcoords[ballpos][0], _y:ballcoords[ballpos][1]});
  47.     //initialize the x and y velocity to 0
  48.     eval('ball'+ballArray[ballpos]).xvel = 0;
  49.     eval('ball'+ballArray[ballpos]).yvel = 0;
  50.     //set the ball to not sunk
  51.     eval('ball'+ballArray[ballpos]).sunk = 0;
  52.     //change the graphic to represent the ball
  53.     eval('ball'+ballArray[ballpos]).gotoAndStop(ballArray[ballpos]);
  54.   }
  55.   //attach the cue ball
  56.   this.attachMovie('balls', 'ball0', this.getNextHighestDepth(), {_x:tablearea._x+tablearea._width/4, _y:tablearea._y+tablearea._height/2});
  57.   //frame 16 is thge cue ball frame
  58.   ball0.gotoAndStop(16);
  59.   //allow the user to move the cue ball around the left quarter of the table
  60.   scratch();
  61.   //process the balls on the table on each interval, which its length is determined by 'movetime'
  62.   var ballinterval:Number = setInterval(this, "moveBalls", movetime);
  63. }
  64. function moveBalls() {
  65.   //loop through each ball, moving it according to its velocity
  66.   for (curball=0; curball<=15; curball++) {
  67.     //if the ball is sunk, move it along the bottom tray
  68.     if (eval('ball'+curball).sunk) {
  69.       //move the ball towards the right of the tray until it hits the end or another ball
  70.       eval('ball'+curball)._x = Math.min(trayarea._x+trayarea._width-eval('ball'+curball)._width/2-eval('ball'+curball)._width*(eval('ball'+curball).sunk-1)-spacer*(eval('ball'+curball).sunk-1), eval('ball'+curball)._x+5);
  71.     } else {
  72.       //move the ball according to its velocity
  73.       eval('ball'+curball)._x += eval('ball'+curball).xvel;
  74.       eval('ball'+curball)._y += eval('ball'+curball).yvel;
  75.       //decrease the velocity to account for friction
  76.       eval('ball'+curball).xvel *= friction;
  77.       eval('ball'+curball).yvel *= friction;
  78.       //if the ball is moving very slowly, stop its movement completely
  79.       if (Math.abs(eval('ball'+curball).xvel)<.02 && Math.abs(eval('ball'+curball).yvel)<.02) {
  80.         eval('ball'+curball).xvel = 0;
  81.         eval('ball'+curball).yvel = 0;
  82.       }
  83.       //Ball bounces off of the right bumper
  84.       if (eval('ball'+curball)._x+eval('ball'+curball)._width/2>tablearea._x+tablearea._width) {
  85.         //if the ball is high enough or low enough to fall into a pocket, sink it
  86.         if (eval('ball'+curball)._y-eval('ball'+curball)._height*.75<tablearea._y || eval('ball'+curball)._y+eval('ball'+curball)._height*.75>tablearea._y+tablearea._height) {
  87.           sinkBall(curball);
  88.         } else {
  89.           //reverse the velocity and adjust for the distance past the bumper
  90.           eval('ball'+curball).xvel *= -1;
  91.           eval('ball'+curball)._x -= (eval('ball'+curball)._x+eval('ball'+curball)._width/2-(tablearea._x+tablearea._width));
  92.         }
  93.         //ball bounces off of the left bumper
  94.       } else if (eval('ball'+curball)._x-eval('ball'+curball)._width/2<tablearea._x) {
  95.         //if the ball is high enough or low enough to fall into a pocket, sink it
  96.         if (eval('ball'+curball)._y-eval('ball'+curball)._height*.75<tablearea._y || eval('ball'+curball)._y+eval('ball'+curball)._height*.75>tablearea._y+tablearea._height) {
  97.           sinkBall(curball);
  98.         } else {
  99.           //reverse the velocity and adjust for the distance past the bumper
  100.           eval('ball'+curball).xvel *= -1;
  101.           eval('ball'+curball)._x -= (eval('ball'+curball)._x-eval('ball'+curball)._width/2)-tablearea._x;
  102.         }
  103.       }
  104.       //ball bounces off of the bottom bumper
  105.       if (eval('ball'+curball)._y+eval('ball'+curball)._height/2>tablearea._y+tablearea._height) {
  106.         //if the ball is left or right enough to fall into a pocket, or in the center pocket area, sink it
  107.         if (eval('ball'+curball)._x-eval('ball'+curball)._width*.75<tablearea._x || eval('ball'+curball)._x+eval('ball'+curball)._width*.75>tablearea._x+tablearea._width || (eval('ball'+curball)._x-eval('ball'+curball)._width*.75<tablearea._x+tablearea._width/2 && eval('ball'+curball)._x+eval('ball'+curball)._width*.75>tablearea._x+tablearea._width/2)) {
  108.           sinkBall(curball);
  109.         } else {
  110.           //reverse the velocity and adjust for the distance past the bumper
  111.           eval('ball'+curball).yvel *= -1;
  112.           eval('ball'+curball)._y -= (eval('ball'+curball)._y+eval('ball'+curball)._height/2-(tablearea._y+tablearea._height));
  113.         }
  114.         //ball bounces off of the top bumper 
  115.       } else if (eval('ball'+curball)._y-eval('ball'+curball)._height/2<tablearea._y) {
  116.         //if the ball is left or right enough to fall into a pocket, or in the center pocket area, sink it
  117.         if (eval('ball'+curball)._x-eval('ball'+curball)._width*.75<tablearea._x || eval('ball'+curball)._x+eval('ball'+curball)._width*.75>tablearea._x+tablearea._width || (eval('ball'+curball)._x-eval('ball'+curball)._width*.75<tablearea._x+tablearea._width/2 && eval('ball'+curball)._x+eval('ball'+curball)._width*.75>tablearea._x+tablearea._width/2)) {
  118.           sinkBall(curball);
  119.         } else {
  120.           //reverse the velocity and adjust for the distance past the bumper
  121.           eval('ball'+curball).yvel *= -1;
  122.           eval('ball'+curball)._y -= (eval('ball'+curball)._y-eval('ball'+curball)._height/2)-tablearea._y;
  123.         }
  124.       }
  125.     }
  126.   }
  127.   //check for ball collisions
  128.   //Check the distances between balls
  129.   for (i=0; i<=15; i++) {
  130.     //If the cue ball has been sunk and is draggable, do not check it for collisions
  131.     if (ball0.onPress) {
  132.       i++;
  133.     }
  134.     a = this["ball"+i];
  135.     for (j=i+1; j<=15; j++) {
  136.       b = this["ball"+j];
  137.       //determine the distance on the x and y planes
  138.       var dx = b._x-a._x;
  139.       var dy = b._y-a._y;
  140.       //use the pythagorean theorem to determine the distance between two balls
  141.       var dist = Math.sqrt(dx*dx+dy*dy);
  142.       //if there is a collision between two balls, process it.
  143.       if (dist<a._width/2+b._width/2) {
  144.         _root.solveBalls(a, b);
  145.       }
  146.     }
  147.   }
  148. }
  149. //grabbed this function from https://www.mochiads.com/community/forum/topic/circular-hit-test-thingy-problem
  150. function solveBalls(ballA, ballB) {
  151.   var x1 = ballA._x;
  152.   var y1 = ballA._y;
  153.   //determine the distance between the two balls along the x and y planes
  154.   var dx = ballB._x-x1;
  155.   var dy = ballB._y-y1;
  156.   //determine the actual distance using the pythagorean theorem
  157.   var dist = Math.sqrt(dx*dx+dy*dy);
  158.   //determine the radius of the two balls averaged together
  159.   radius = ballA._width/4+ballB._width/4;
  160.   //it calculates the distance, i could have passed it to the function but it works this way
  161.   normalX = dx/dist;
  162.   normalY = dy/dist;
  163.   midpointX = (x1+ballB._x)/2;
  164.   midpointY = (y1+ballB._y)/2;
  165.   //Now this calculates the normal and mid points..
  166.   ballA._x = midpointX-normalX*radius;
  167.   ballA._y = midpointY-normalY*radius;
  168.   ballB._x = midpointX+normalX*radius;
  169.   ballB._y = midpointY+normalY*radius;
  170.   //shifts the two balls to a different location so they don't hit each other
  171.   dVector = (ballA.xvel-ballB.xvel)*normalX+(ballA.yvel-ballB.yvel)*normalY;
  172.   dvx = dVector*normalX;
  173.   dvy = dVector*normalY;
  174.   //This calculates the new speeds for the balls
  175.   ballA.xvel -= dvx;
  176.   ballA.yvel -= dvy;
  177.   ballB.xvel += dvx;
  178.   ballB.yvel += dvy;
  179.   //assigns the new values
  180. }
  181. //sink a ball when it hits a pocket
  182. function sinkBall(curball) {
  183.   //if the sunk ball is the cue ball, it causes a scratch
  184.   if (curball == 0) {
  185.     //bring the cue ball back up and let the player move it around
  186.     scratch();
  187.     //else it isn't the cue ball, so move the ball down to the bottom tray
  188.   } else {
  189.     //make sure the ball isn't already sunk
  190.     if (eval('ball'+curball).sunk == 0) {
  191.       //enumerate the number of balls sunk and give this ball that value
  192.       //the value is used to determine how far to the right the ball rolls
  193.       eval('ball'+curball).sunk = (++ballssunk);
  194.       //stop the ball's movement. We will manually move it to the right
  195.       eval('ball'+curball).xvel = 0;
  196.       eval('ball'+curball).yvel = 0;
  197.       //move the ball down into the tray
  198.       eval('ball'+curball)._y = trayarea._y+trayarea._height/2;
  199.       //move the ball to the left side of the tray
  200.       eval('ball'+curball)._x = trayarea._x+eval('ball'+curball)._width/2;
  201.     }
  202.   }
  203. }
  204. //
  205. function scratch() {
  206.   //allow the player to drag the cue ball around the left quarter of the table
  207.   //spacer gives a buffer so the ball will not go in a hole when it is being drug around
  208.   ball0.startDrag(true, tablearea._x+ball0._width/2+spacer, tablearea._y+ball0._height/2+spacer, tablearea._x+tablearea._width/4, tablearea._y+tablearea._height-ball0._height/2-spacer);
  209.   //reset the speed of the cue ball to 0
  210.   ball0.xvel = 0;
  211.   ball0.yvel = 0;
  212.   //the ball has been pressed, place it on the table and show the pool stick
  213.   ball0.onPress = function() {
  214.     //Check the distances between balls
  215.     a = this;
  216.     //don't drop the ball if it is touching another ball
  217.     for (j=1; j<=15; j++) {
  218.       b = this._parent["ball"+j];
  219.       var dx = b._x-a._x;
  220.       var dy = b._y-a._y;
  221.       //determine the distance between 2 balls
  222.       var dist = Math.sqrt(dx*dx+dy*dy);
  223.       if (dist<a._width/2+b._width/2) {
  224.         return;
  225.       }
  226.     }
  227.     //make sure no other balls are moving before dropping the cue ball
  228.     for (i=1; i<=15; i++) {
  229.       if (eval('ball'+i).xvel != 0 || eval('ball'+i).yvel != 0) {
  230.         return;
  231.       }
  232.     }
  233.     //The ball is not touching any other balls and all other balls have stopped moving, so drop it on the table
  234.     ball0.stopDrag();
  235.     //show the pool stick
  236.     showStick();
  237.     //delete this function so the player cannot click the ball anymore
  238.     delete ball0.onPress;
  239.   };
  240. }
  241. //Show the pool stick
  242. function showStick() {
  243.   //if a pool stick already exists, delete it
  244.   if (poolstick) {
  245.     delete poolstick;
  246.   }
  247.   //create a new poolstick
  248.   this.attachMovie('poolstick', 'poolstick', this.getNextHighestDepth(), {_x:ball0._x, _y:ball0._y});
  249.   //initialize the power to 0
  250.   poolstick.power = 0;
  251.   //initialize the distance to pull back the stick to 0
  252.   poolstick.curdist = 0;
  253.   //on each frame move the stick according to the mouse and ball position
  254.   poolstick.onEnterFrame = function() {
  255.     //if the pool stick isn't fading out (it hasn't been clicked and released) move it according to the ball's position
  256.     if (poolstick._alpha == 100) {
  257.       poolstick._x = ball0._x;
  258.       poolstick._y = ball0._y;
  259.     }
  260.     //If the poolstick hasn't been clicked yet, adjust its angle based on the mouse's angle relative to the cue ball
  261.     if (poolstick.onPress) {
  262.       //set the angle
  263.       stickangle = Math.atan(-(_ymouse-ball0._y)/(_xmouse-ball0._x))/(Math.PI/180);
  264.       if ((_xmouse-ball0._x)<0) {
  265.         stickangle += 180;
  266.       }
  267.       if ((_xmouse-ball0._x)>=0 && (-(_ymouse-ball0._y))<0) {
  268.         stickangle += 360;
  269.       }
  270.       poolstick._rotation = -stickangle-90;
  271.       //else the pool stick has been clicked, so keep its angle locked from when it was clicked 
  272.     } else {
  273.       //when the pool stick is clicked, allow the player to bring it back, giving it power
  274.       if (poolstick.onRelease) {
  275.         //the power is based on the distance the mouse is from the ball relative to when it was first clicked
  276.         //keep the power stored for when the stick is released
  277.         //set a cap to how far away from the ball the stick can go
  278.         poolstick.power = Math.min(100, Math.max(0, (Math.sqrt(Math.pow(_xmouse-ball0._x, 2)+Math.pow(_ymouse-ball0._y, 2))-poolstick.clickdistance)));
  279.         //set the distance equal to the amount of power applied
  280.         poolstick.curdist = poolstick.power;
  281.         //else the pool stick has been released. Start bringing the stick towards the ball
  282.       } else {
  283.         //move the stick towards the ball
  284.         poolstick.curdist = Math.max(0, poolstick.curdist-20);
  285.         //if there is no more distance to get to the ball, hit it with the stick
  286.         if (poolstick.curdist == 0) {
  287.           //if this is the first frame the poolstick  has hit the ball, change the ball's velocity
  288.           if (poolstick._alpha == 100) {
  289.             //hit the ball in the direction the pool stick is pointed
  290.             ball0.xvel = Math.sin(poolstick._rotation*degreestoradians)*(poolstick.power*powermultiplier);
  291.             ball0.yvel = -Math.cos(poolstick._rotation*degreestoradians)*(poolstick.power*powermultiplier);
  292.           }
  293.           //start fading out the pool stick
  294.           poolstick._alpha -= 10;
  295.           //if the pool stick is fully invisible, delete it and set an interval that waits until all balls have stopped moving
  296.           if (poolstick._alpha<=0) {
  297.             //called every 30 milliseconds
  298.             setMovementInterval();
  299.             removeMovieClip(poolstick);
  300.           }
  301.         }
  302.       }
  303.     }
  304.     //if the poolstick hasn't been released or clicked yet, move it to the cue ball's edge
  305.     if (poolstick._alpha == 100) {
  306.       //We figure out the direction the poolstick is pointing, and then move it away from the ball according to its angle
  307.       poolstick._x += -Math.sin(degreestoradians*poolstick._rotation)*(ball0._width/2+poolstick.curdist);
  308.       poolstick._y += Math.cos(degreestoradians*poolstick._rotation)*(ball0._width/2+poolstick.curdist);
  309.     }
  310.   };
  311.   //adds the function to drag the poolstick when it is clicked
  312.   poolstickAction();
  313. }
  314. //add a listener to the poolstick to start dragging when it is clicked
  315. function poolstickAction() {
  316.   //the listener that waits for the poolstick to be clicked
  317.   poolstick.onPress = function() {
  318.     //determine the distance of the mouse from the ball, and store it
  319.     poolstick.clickdistance = Math.sqrt(Math.pow(_xmouse-ball0._x, 2)+Math.pow(_ymouse-ball0._y, 2));
  320.     //add a listener that waits for the user to release the mouse
  321.     poolstick.onRelease = poolstick.onReleaseOutside=function () {
  322.       //get rid of the current poolstick listeners
  323.       delete poolstick.onRelease;
  324.       delete poolstick.onReleaseOutside;
  325.       //if the poolstick is not pulled back, recreate the
  326.       if (poolstick.power<=0) {
  327.         //else the poolstick is not pulled back, so recreate the poolstick action
  328.         poolstickAction();
  329.       }
  330.     };
  331.     //delete the poolstick press listener
  332.     delete poolstick.onPress;
  333.   };
  334. }
  335. //create an interval to check if any balls have moved since the last interval
  336. function setMovementInterval() {
  337.   //run the interval every 'movetime' milliseconds
  338.   var movementinterval:Number = setInterval(this, "checkMovement", movetime);
  339. }
  340. //function that is called by the interval
  341. function checkMovement():Void {
  342.   //if the poolstick is still on the table, do not check if the balls have stopped moving
  343.   if (poolstick) {
  344.     return;
  345.   }
  346.   //loop through all of the balls to check for movement
  347.   for (i=0; i<=15; i++) {
  348.     if (eval('ball'+i).xvel != 0 || eval('ball'+i).yvel != 0) {
  349.       return;
  350.     }
  351.   }
  352.   //if the user hasn't scratched, show the poolstick
  353.   if (!ball0.onPress) {
  354.     showStick();
  355.   }
  356. }
  357. //function called when the newgame button is pressed, aka rack 'em
  358. newgame.onPress = function() {
  359.   //if ball0 exists, all of the balls exist, so we first delete all of the balls
  360.   if (ball0) {
  361.     for (i=0; i<=15; i++) {
  362.       //delete all of the balls
  363.       removeMovieClip('ball'+i);
  364.     }
  365.     //delete the main function that runs at the start of each frame
  366.     delete onEnterFrame;
  367.   }
  368.   //if the poolstick exists, delete it
  369.   if (poolstick) {
  370.     removeMovieClip('poolstick');
  371.   }
  372.   //start a new game
  373.   newGame();
  374. };
  375.  

The source code and built swf are available below:

Download the Source File
Download the Built swf File
Comments Currently Disabled