Game Code


Pinball uses a lot of code you've already seen in earlier chapters. The game requires frame-independent collision detection for ball-ball and ball-line collisions, and employs the appropriate collision-reaction code for both of these. The bulk of the code you haven't yet seen, and may not be able to easily understand at first glance, is the code that handles the following functions:

  • Creating the table geography in memory specifically, the angled lines (including the flippers)

  • Detecting if a collision has occurred between the ball and a flipper at the instant a flipper is flipped

In this section we will discuss several pieces of the game, including the two functions listed above. It is assumed that you can easily follow the remaining ActionScript, such as the functions that play sounds, increment the score, detect collisions, and capture key presses.

graphics/cd_icon.gif

Before we look at the ActionScript, let's take a quick look around the pinball.fla file in the Chapter16 directory. This file has a frame setup that's similar to the one used in all the other games in this book. The game itself is on the Game frame in the movie clip called game. Move to the Game frame label and double-click the movie clip to view the contents. There are three layers in this movie clip: Actions, Assets, and Table. The Table layer contains the background graphics for the pinball table. The Assets layer contains all of the other assets, including game graphics, menu button, and score text field. The Actions layer contains the ActionScript used to make this game.

ActionScript Not Found in a Function

The ActionScript in this frame is contained mostly within functions. However, there is also an area where we initialize some variables, objects, and arrays that is not within a function. This ActionScript is used to initialize the variables, arrays, and objects that only need to be created one time. There is also an onEnterFrame event, but we will discuss that in the next section. Here is this loose ActionScript:

 1   inPlay = false;  2   soundOn = true; 3   makeLinesVisible = false; 4   left_tester._visible = false; 5   right_tester._visible = false; 6   ball = {}; 7   ball.clip = this.pb; 8   ball.x = ball.clip._x; 9   ball.y = ball.clip._y; 10  ball.radius = ball.clip._width/2; 11  ball.mass = 1; 12  ball.xmov = 0; 13  ball.ymov = 0; 14  airDecay = .99; 15  gravity = .2; 16  depth = 100; 17  runPatch = 0; 18  bumperArray = []; 19  buildMap(); 

First, a variable called inPlay is set with a value of false. This means that the game has not yet begun. When the game is started, inPlay will be set to true. When the game is over, it will again be set to false. Next, we set the soundOn variable to true. This variable, as you know by now, controls the playing of sounds. If soundOn is false, then no sounds will be played. In line 3 we set a variable called makeLinesVisible with a value of false. This is used for testing, debugging, and graphics-placement reasons. When it's true, the lines that are drawn in memory (the flippers, rails, and launch bank) are shown on the screen. When it's false, these lines are not shown. I set this variable to true when developing this game (before I had the graphics from an artist) so that I could see the physical borders for all of the objects. After I placed the flippers, triangles, rails, walls, and launch bank, I took a screen shot and sent this wireframe of the pinball game to the graphic artist. He created assets that I could place over the wireframe. Once I verified that the placement of the graphics was good, I set the makeLinesVisible variable to false so that all we see are the intended pinball graphics. As you may remember from the collision-detection and collision-reactions chapters (which detailed the development of the createLine() function), each line is contained within a dynamically created movie clip. These lines are the only dynamically created movie clips in this game.

In lines 4 and 5 we set the visibility of two movie clips to false. You can see these two white movie clips on the stage, above (that is, on top of) the left and right triangles. Most of the time we have what appears to be perfect collision detection and collision reactions. However, there can be odd behavior when a ball collides at an interface between two lines, so when the ball collides with any of the three vertices of either of the two triangles, the ball can be sent into the triangle instead of away from it. Therefore, we include these movie clips and use hitTest() as a fail-safe so that the ball never has to get stuck inside. (For the very same reason, we will use a similar patch() function in Chapter 18, "9-Ball.") Future revisions of this game will hopefully include a better solution to this edge-effect problem.

graphics/16fig03.gif

In lines 6 13 we create an object called ball to store information about the ball. The instance name of the ball movie clip is pb. Next, we create a variable called airDecay. This controls the percentage of slowdown of the ball as it moves along the table. The gravity variable is set next, with a value of .2. In every frame, .2 will be added to the ball's y velocity. In line 16 we create a variable called depth with a value of 100. This gives us a starting depth for placing movie clips dynamically. The only movie clips we will be adding dynamically are the lines that can either be visible or invisible. In line 17 we set runPatch to 0, to start its counter. As in the 9-ball game, we don't execute the patch() function every frame, but rather once every 20 or 30 frames. We then create an array called bumperArray. This array will be used to store objects that represent the several bumpers on the screen. We will see more of this in the addBumper() function. Finally, buildMap() is called. This function places the walls and objects in memory and then starts the game.

The onEnterFrame Event

At the bottom of the Actions frame is an onEnterFrame event. This event handles calculation of the ball's new position, collision detection, capturing of key presses, and rendering of the ball's new position onscreen.

Here is the function:

 1   this.onEnterFrame = function() { 2      //now=getTimer(); 3      if (inPlay) { 4         if (animate) { 5            animatePlunger(); 6         } 7         collided = false; 8         getTempPositions(); 9         captureKeys(); 10        if (!collided) { 11           bankCollisionDetect(); 12        } 13        if (!collided) { 14           bumperCollisionDetect(); 15        } 16        checkForWalls(); 17        render(); 18        patch(); 19     } 20     //trace(getTimer()-now); 21  }; 

First, notice the two lines of ActionScript that have been commented out lines 2 and 20. They serve the same purpose as the ones seen in the 9-ball game. When uncommented, they trace the amount of time spent n each frame. With this trace you can better analyze the efficiency of your ActionScript. If too much time is being spent on each frame, then you know that you may have to simplify the ActionScript or possibly remove a few objects from the stage.

The bulk of the ActionScript in this event is contained within a large if statement that simply checks to see if inPlay is true. If it is true, the actions within are executed; otherwise, nothing happens. In lines 4 6 we check to see if a variable called animate is true. If it is, that means we are supposed to be animating the plunger. When the plunger has reached its destination (when being fired), then animate is set to false. Next, we set a variable called collided to false. When a collision with a line, circle, or flipper is detected, we then set collided to true. We want to avoid multiple collision detections and reactions in one frame, and by setting this variable we can do that. In line 8 we set the temporary position of the ball in memory by calling getTempPositions(). In the next line we capture key presses by calling captureKeys(). This function checks to see if the Ctrl key (Windows), Command key (Mac), Shift key, or spacebar is pressed. You will see more about this function later.

In the final lines of the onEnterFrame event, we call collision-detection routines (if no collision has yet been found), check for wall collisions, and then call the patch() function.

buildMap()

This function places all of the objects and defines the walls in memory. What is done in this function is crucial and also confusing at times. In this function, we

  • Initialize some variables that will be used throughout the game, such as score and numBalls.

  • Create variables that store the positions of the pinball table's four walls.

  • Create the several lines in memory needed for the flippers, rails, triangles, and launch bank. (This is the confusing part.)

  • Add bumpers to the map in memory.

Here is the function:

 1   function buildMap() { 2      numBalls = 3; 3      score = 0; 4      lastScoreLevel = 0; 5      lastBigScoreLevel = 0; 6      startx = 250; 7      starty = 50; 8      width = 225; 9      height = 300; 10     var heightFromBottom = 100; 11     //left triangle 12     var diagLength = 70; 13     var diagAng = 60; 14     var cosAng = Math.cos(diagAng*Math.PI/180); 15     var sinAng = Math.sin(diagAng*Math.PI/180); 16     //left triangle, inner wall 17     var x = startx+20; 18     var y = starty+height-heightFromBottom-55; 19     l = createLine(x, y, diagAng, diagLength, 2,        left_triangle, "laser"); 20     var sx = l.x2; 21     var sy = l.y2; 22     //left triangle, left wall 23     var x = l.x1; 24     var y = l.y1+diagLength*sinAng*.6; 25     l = createLine(x, y, 269, diagLength*sinAng*.6, 1); 26     //left triangle, bottom wall 27     var ex = l.x1; 28     var ey = l.y1; 29     var ang = Math.atan2(ey-sy, ex-sx)*180/Math.PI; 30     l = createLine(sx, sy, ang, 42, 1); 31     //right triangle 32     var diagAng = 300; 33     var cosAng = Math.cos(diagAng*Math.PI/180); 34     var sinAng = Math.sin(diagAng*Math.PI/180); 35     //right triangle, inner wall 36     var x = startx+width-diagLength*cosAng-20; 37     var y = starty+height-heightFromBottom-        diagLength*sinAng-55; 38     l = createLine(x, y, diagAng, diagLength, 2,        right_triangle, "laser"); 39     var sx = l.x1; 40     var sy = l.y1; 41     //right triangle, right wall 42     var x = l.x2; 43     var y = l.y2-diagLength*sinAng*.6; 44     l = createLine(x, y, 90.1, diagLength*sinAng*.6, 1); 45     //right triangle, bottom wall 46     var ex = l.x1; 47     var ey = l.y1; 48     var ang = Math.atan2(ey-sy, ex-sx)*180/Math.PI; 49     l = createLine(ex, ey, ang+180, 42, 1); 50     //launch bank 51     launchBank = createLine(startx+width+20, starty+20, 230,         30, 1); 52     //flippers and rails 53     var length = 80; 54     var angle = 30; 55     var xstep = length*Math.cos(angle*Math.PI/180); 56     var ystep = length*Math.sin(angle*Math.PI/180); 57     l = createLine(startx, starty+height-heightFromBottom,        angle, length, .5); 58     createFlipper(l.x2, l.y2, "left"); 59     var x = startx+width-xstep; 60     var y = starty+height-heightFromBottom+ystep; 61     l = createLine(x, y, 360-angle, length, .5); 62     createFlipper(l.x1, l.y1, "right"); 63     //bumpers 64     bounciness = 10; 65     addBumper(tempB1._x, tempB1._y, tempB1._width/2,        bounciness, tempB1); 66     addBumper(tempB2._x, tempB2._y, tempB2._width/2,        bounciness, tempB2); 67     addBumper(tempB3._x, tempB3._y, tempB3._width/2,        bounciness, tempB3); 68     addBumper(tempB4._x, tempB4._y, tempB4._width/2, 2,        tempB4, true); 69     addBumper(tempB5._x, tempB5._y, tempB5._width/2, 2,        tempB5, true); 70     addBumper(tempB6._x, tempB6._y, tempB6._width/2, 2,        tempB6, true); 71     addBumper(tempB7._x, tempB7._y, tempB7._width/2, 2,        tempB7, true); 72     addBumper(tempB8._x, tempB8._y, tempB8._width/2, 2,        tempB8, true); 73     addBumper(tempB9._x, tempB9._y, tempB9._width/2, 2,        tempB9, true); 74     --numBalls; 75     initializeShot(); 76  } 

First the numBalls variable is set. This stores the number of balls you start with when a new game begins. (When you reach certain score amounts, you are awarded another ball by the incrementing of this variable.) Then the score is set to 0, since when you first begin the game, you have not yet earned any points. The next two variables lastScoreLevel and lastBigScoreLevel are used to measure relative score. They start at 0. Whenever the score is incremented (using the addScore() function), we check to see if the score is a certain amount of points higher than each of these two variables. If it is, then we do … something, like award an extra ball or extra points. Then, for whichever of the two variables' values was reached, we set that variable to the current score, so the counting starts over again. The end result is that a new ball can be awarded every, say, 19,000 points, and some other bonus is given at, say, every 2000 points.

The next four variables (lines 6 9), startx, starty, width, and height, define the table's dimensions. They give the position of the upper-right corner of the table and then the table's width and height. The variable heightFromBottom is the amount of space we want to have between the bottom of the table and the top of the rails. That variable is used as a relative starting place to add the triangles, rails, and flippers.

Now we start adding lines in memory. In lines 11 30 we add the left triangle to the table. First we set a variable called diagLength, which is the length of the side of the triangle that faces toward the middle of the table (the right side). Next we set diagAng, which is the angle that this side of the triangle should be, with respect to the x-axis. This angle is 60°. We then calculate and store the values of the sine and cosine of this angle. Those values will be used for creating the other sides of the triangle. In lines 17 and 18 we store the x and y starting positions of this line we are about to create. The y position is calculated by finding the y position of the bottom wall (starty + height) and stepping back by heightFromBottom, and then stepping back again by an arbitrary amount of 55. (I chose the number 55 because to my eye it made the triangle look better.) The x position is found by starting with the left wall (startx) and moving to the right a little bit say, 20.

In line 19 we create the line by calling the createLine() function. This is the same function used in Chapter 5, "Collision Detection," and Chapter 6, "Collision Reactions," to create lines, and is used here to create several lines on the pinball table. We have modified it slightly to do things like play a sound when a line is hit. We will not look at those differences in this chapter; they are simple and can be understood at a glance. But let's look at the parameters of the createLine() function on line 19. Here are all seven of them, in order:

x The starting x position of the line.

y The starting y position of the line.

angle The angle at which the line will point (measured from the x-axis).

length The length of the line.

decay A normalized factor, showing the percentage by which the ball's speed will be changed when a collision occurs. This is usually between 0 and 1. A value of .5 would mean a 50 percent decrease in speed. However, on the inner triangle lines, we want there to be a dramatic bounce, so we use a value of 2, which sends the ball away at 200 percent of its collision speed.

clip This is an optional parameter for playing a movie clip when this line is collided with. For most lines in this game, this parameter will be left out. (When the ball collides with the inner line of the triangle, we want the animation to play, so we include a reference to the appropriate movie clip. But we don't want any animation when the ball collides with either of the other two sides of the triangle, so we don't give the clip an instance name when creating those lines.)

sound This also is an optional parameter. It specifies the name of a sound you want to play when a line is collided with. In this game, this parameter will only be used for the two inner-triangle lines. When the line is collided with, it calls the playSound() function and passes in the string name of the sound, which is then played in the soundfx movie clip.

You should also notice in line 19 that when using the createLine() function, we also set a reference, called l, to the object that represents that line. That way we can easily access values on that object while creating the next line. In the lines that follow (all the way to line 51) we create the rest of the lines in this triangle and also those in the right triangle and in the launch bank. Notice that in line 51, when creating the launch bank, we set a reference called launchBank to the object that represents it. We do this because after we launch the ball, we disable the launchBank so that we don't have to (needlessly) look for collisions with it the ball should not collide with it. When it's time for the ball to be launched again, launchBank is turned back on.

In lines 53 62 we add the two rails and flippers. To create a flipper, we must execute the createFlipper() function and pass in the x and y starting positions for it, as well as which flipper it will be (left or right). We will look at the createFlipper() function next.

Next, in lines 64 73, we add nine bumpers to the table. At first glance, you will notice only three bumpers. But in the quest to keep the pinball from entering the left and right triangles (due to edge effects), we add a tiny bumper on each of the two triangles' vertices that accounts for six bumpers to add to the three that you see at regular size on the stage. The three at the top of the stage are named tempB1, tempB2, and tempB3. The others are named tempB4 through tempB9. We add a bumper in memory by calling the addBumper() function.

Like the createLine() function, the addBumper() function has a lot of parameters. Here they are (in order):

x The x position of the center of the bumper.

y The y position of the center of the bumper.

radius The radius of the bumper.

reflection speed The speed at which the ball should be deflected away from the bumper. The speed at which the ball was moving when it collided with the bumper has no effect on the reflection speed. The angle of the reaction does depend on the incident angle, but not the incident speed.

clip This is a reference to the bumper movie clip. It is used to play an animation (in our case, only for the three bumper instances on our table that we have chosen to animate).

turn invisible This is an optional parameter. If true, then the bumper is turned invisible. This is useful for the tiny bumpers that are on the left and right triangles. If left blank, or if false, then the bumpers are left visible.

In line 74 we decrement the numBalls variable. This game player will get the correct number of balls; it is just that the ball that is currently on the plunger is not counted in the numBalls variable. We decrement this variable because we are adding the ball to the table in the next line, initializeShot(). From now until the end of the game, the numBalls variable will only be decremented when a ball falls down the trap.

createFlipper()

This function takes x and y positions passed in, as well as a direction (either left or right), and creates a flipper in memory at that position. The ActionScript is straightforward enough that I don't think you need to see it dissected line by line. But it's worth noting how we detect collisions with the flipper. There are three possible states of the flipper: down, moving up, and up. When the flipper is down or up, there is a line in memory (exactly like all of the other lines we've created) that checks for collisions. At the instant that the flipper is flipping up, we disable the lines in memory that describe the flipper and do something a little tricky: We use a wedge-shaped movie clip and perform a hitTest() between the ball and the wedge. If the hitTest() is true, then we calculate a realistic reaction. As you know if you've read the collision-detection chapter, hitTest() is not good for all that many things. In this case it is only barely acceptable. (And just as soon as I have the time, I will create a frame-independent ball-rotating line collision-detection routine.)

When creating the left and right flippers, this function also creates references to the line objects that represent each flipper, leftFlipper and rightFlipper. These references can be used by other functions to read properties of the flipper (such as its angle) or to change the flipper's properties, such as during a flip action.

Keeping Track of Weaknesses

So far, you have learned of two weaknesses in this game. It is important to remember what they are if you are going to try to make this a better game.

  • At the interface between two lines, such as the vertices of the triangles, there are odd edge effects. These can cause the ball to do things we don't expect or want.

  • During a flip of the flipper we use hitTest(), along with a wedge-shaped movie clip, to detect if a collision is taking place.

initializeShot()

This function is called at the end of the buildMap() function, and also whenever the ball falls down the trap. It is used to enable the launch bank, initialize some variables, and position the pinball above the plunger. When this function is executed, the pinball is ready to be launched.

 1   function initializeShot() { 2      inPlay = true; 3      launchBank.enabled = true; 4      launchBank.counter = 0; 5      rightWallOn = false; 6      shotYet = false; 7      ball.xmov = 0; 8      ball.ymov = 0; 9      ball.x = cradle._x; 10     ball.y = cradle._y-100; 11     ball.tempx = ball.x; 12     ball.tempy = ball.y; 13     shotPower = 0; 14     shotMax = 30; 15  } 

In line 2 we set inPlay to true. This is so that the collision-detection routines get executed (think back to the onEnterFrame event). We then enable the launch bank. Remember that we set a reference to the launch bank in line 51 of the buildMap() function? Well, this is why! This way, we can easily enable or disable it.

graphics/tip_icon.gif

One thing to note about disabling a line is that it re-enables itself when its counter property reaches 5. This was added to give the ball enough time (after a flipper collision) to move out of the way before looking for more collisions with that same flipper. Also notice that there are actually two right walls the far-right wall (to the right of the plunger) and the right wall that is at the edge of the main part of the table. This inner right wall is what we actually consider the right wall. In line 5 we set rightWallOn to false. When it's false, we do not detect collisions with this wall. This is so that we can let the pinball pass through it when launched. After the pinball has passed through it, we set rightWallOn to true. This is done from the checkForWalls() function.

Next we set a variable called shotYet to false. This variable is set to true as soon as the ball bounces off the launch bank. When it's true, the plunger will not respond if you press the spacebar. In lines 7 12 we set the initial velocity of the ball to 0 and position the ball above the plunger. We then set the power range for the plunger. Its minimum power is 0 and maximum is 30. This refers to the initial speed of the ball as it is launched.

flip()

This function accepts two parameters, which and dir. The which parameter tells which of the flippers to flip, either left or right. The dir parameter specifies whether to flip up or down.

What this function does is simple. When a flipper is moving either up or down, the function must redraw the flipper line in memory so that the ball will be able to react to the flipper's new rotation. Also, when it's flipping up or down, this function tells the graphic asset that represents the flipper on the stage (either left_paddle or right_paddle) to go to the correct frame to display the flipper at the proper rotation. Further, when it's flipping up, this function also runs the checkCollision() function, which checks to see if the ball is currently colliding with the wedge-shaped movie clip. If it is, then a reaction is calculated.

checkCollision()

When this function is called, it simply checks for a collision between the ball and the wedge-shaped movie clip, using hitTest(). If the collision is occurring, then the following steps happen:

  1. The angle of the imaginary line formed between the stationary rotating point of the flipper and the ball's current position is found using Math.atan2(). This is the angle that the flipper would have been at when a collision occurred.

  2. The angle at which the ball was moving is used with the angle found in step 1 to find the deflection angle for the ball.

  3. This deflection angle is run through a simple filter. Through testing, I found that sometimes the ball came off the flipper moving at a horizontal angle straight toward the other flipper or at an extreme angle toward the wall. So there are some if statements to check to see if the angle is within a certain range. If the angle is out of that accepted range, then the value of the angle gets changed.

  4. The speed at which the ball gets deflected depends on the ball's distance from the flipper's stationary rotation point. If the ball collides at the far end of the flipper, then it should fly away at a maximum speed. If the ball is hit really close to the rotation point, then it should fly away at a lower speed.

I mentioned earlier in the section that this function also disables the flipper when a collision is detected. We disable the flipper so that on the same frame or the next frame, the flipper line itself does not collide with the ball. The flipper will enable itself after just a few frames.



Macromedia Flash MX Game Design Demystified(c) The Official Guide to Creating Games with Flash
Macromedia Flash MX Game Design Demystified: The Official Guide to Creating Games with Flash -- First 1st Printing -- CD Included
ISBN: B003HP4RW2
EAN: N/A
Year: 2005
Pages: 163
Authors: Jobe Makar

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net