• home
  • forum
  • my
  • kt
  • download
  • Home / 2D Graphics / Flash / Games /

    8 Ball Pool

    Author: 2008-10-08 13:03:16 From:

    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

    discuss this topic to forum

    relation tutorial

    No relevant information

    Category

      3D (20)
      Math Physics (14)
      3rd Party (5)
      Navigation (60)
      Actionscripting (26)
      Optimization (16)
      Animation (32)
      Projector (9)
      Audio (46)
      Special Effects (112)
      Backend (25)
      Text Effects (65)
      Drawing (18)
      Tips and Techniques (41)
      Dynamic Content (25)
      Tricks (6)
      Games (95)
      Utilities (21)
      Getting Started (91)
      Video (24)
      Interactivity (43)
      Web Design (29)

    New

    Hot