We’re just going to keep right on going with the racquetball court and paddle program you made for the first project in this module. Since this is supposed to be a racquetball game, we need to have a ball to slap around, don’t we?
The goals of this project include
This is where things will get interesting, because we want to
do two different things with the ball. First, we want to get it moving
across the screen. Then, we want to make it react appropriately to
different conditions as it is moving.
For instance, if it’s at the top, left, or right edges of the Stage, we want to make it rebound, since those represent the "walls" of our racquetball court. We also want to make it rebound off the paddle. Finally, if it happens to make it all the way to the bottom of the Stage, we want to reset the game, since the player let the ball go past him.
Vector motionmoving a sprite in a straight line but at some particular angleis surprisingly hard to create and make smooth on a computer. This is because, at least with Director, motion for a sprite can take place only on the minimum level of one pixel at a time, which means that we’re more or less constrained to strictly horizontal or vertical motion, or motion in diagonals of 45º, because to produce motion along any other angle, we would have to either move fractional pixels, which is impossible, or do some interesting mathematical tricks to virtually "scale up" the size of the Stage.
We’re in luck, because James Newton (who wrote the Advanced Guide for Director 8.5) created a behavior called Vector Motion, and it’s in your Library Palette right now.
As the ball is moving, we’ll need to test its location and respond appropriately to what we learn.
You might surmise that we’ll put this latter set of tests into an enterFrame or exitFrame
event script, and that’s correct. However, we need to get the ball
rolling, as it were, and so the first script we’ll create is the one
that sets the initial parameters for motion. Here it is:
fHorizSend = fHorizDelta / 10.0
fVertSend = fVertDelta / 10.0
sendSprite ( me.spriteNum, #VectorMotion_SetVector, [fHorizSend, fVertSend] )
END ChooseVector
Homina homina homina. Don’t panic. I’m right here.
on ChooseVector me, fHMultiplier, fVMultiplier
This is a behavior handler definition. The word "me" is something you’ve seen before and might be wondering about, and I’ll tell you all about it in Part 4. For now, just remember that that "me" always has to be there when you’re doing handlers in a behavior. The other two words, fHMultiplier and fVMultiplier, are parameters. You remember parameters from Part 1. We’ve got them there because we’re going to be passing parameters to this handler once we’re all finished with our ball script. You can guess by the simplified Hungarian Notation prefixes on them that they are floating-point, or decimal, parameters that will be passed.
fHorizDelta = float ( random ( 20, 40 ) ) * fHMultiplier
Okay, here we’re doing some complicated-looking operations to put a value into some variable called fHorizDelta. Let’s look at what’s happening inside the parentheses first.
random is another Lingo keyword; it returns a randomly selected integer from within a range that we specify. In this case, we are looking for a random number somewhere between 20 and 40, inclusive. Thus our random call might give us 32 the first time, 27 the second time, 40 the third time, and so on, but the random number we get will never be less than 20 nor greater than 40.
I chose this range myself after doing some lengthy experimentation with this project, because otherwise you’d’ve had to do it and it would end up being even more stressful than the first part of this module.
Now that we have the random number, plucked from the range of values from 20 to 40, we pass it through the float keyword. This forces the number we get to be a decimal, or floating-point number. In other words it takes the random number (say 23) and turns it into 23.0.
After that, you can see we’re multiplying that number by our passed parameter. The next line does almost exactly the same thing, but with different variables:
fVertDelta = float ( random ( 20, 40 ) ) * fVMultiplier
Again, we’re taking a random number from 20 to 40, turning it into a decimal, and multiplying it by a passed parameter. Then we’re putting the result into the variable fVertDelta.
Next we do some division.
fHorizSend = fHorizDelta / 10.0
This is just to slip the decimal point over by one place in the number we just calculated. If the final result for the variable fHorizDelta ended up being 36.0, this operation would convert it to 3.60. That’s all. We do the same thing to fVertDelta,
fVertSend = fVertDelta / 10.0
plugging the result into another variable. The next line, though, is definitely a little on the odd side:
sendSprite ( me.spriteNum, #VectorMotion_SetVector, [fHorizSend, fVertSend] )
What the holy cannoli does that mean? Well, as you can see, we’ve taken the variables fHorizSend and fVertSend, which we just got done calculating, and placed them inside brackets. You recall brackets from the last module. They’re special characters in the world of Lingo, denoting a list.
But what about the #VectorMotion_SetVector part just before that?
Believe it or not, this and our little list of variables are also parameters, very special parameters we’re sending to another piece of code in our Director movie. But where is this code to be found?
Locate the Vector Motion behavior script in your Cast window and double-click it to open it. About two-thirds of the way down in that script, you will see a handler that begins with the line
on VectorMotion_SetVector me, scaledVector, scaleFactor, theLoc
Aha, that’s the handler we’re invoking, it seems, with some extra parameters attached. (You don’t really need to worry about those right now.) But this handler does not begin with a pound sign, so why are we using one in our script?
The reason is that we are invoking this handler using a very special Lingo command, sendSprite. This command is used whenever you want to specifically send a command to a given sprite, a command that invokes a specified handler in a behavior attached to that sprite. The general format for a sendSprite call is
sendSprite ( spriteNumber, #handlerToRun, parameters )
Here, spriteNumber is the number of the channel that the sprite occupies in Director’s Score. #handlerToRun is the name of the handler you want the sprite to run; it is preceded by the pound sign to indicate that fact. (Macromedia could have set things up so that you didn’t have to use the pound sign, but having it there is a good thing really, because it serves as a visual reminder that we are doing some interface messaging to a behavior. The reason this distinction matters will become quite clear to you in Part 4. For now, just accept that it’s there and it’s a requirement.) Finally there are parameters, if any, that we send with the rest of the sendSprite message.
So now when you look at the line
sendSprite ( me.spriteNum, #VectorMotion_SetVector, [fHorizSend, fVertSend] )
you can infer that we are sending an interface message to this sprite (me.spriteNum), telling it to run the handler named VectorMotion_SetVector, and passing two variables we calculated in a Director list format ([fHorizSend, fVertSend]). In this fashion, you can see that behaviors attached to sprites can talk to each other. This is a great way for us to be able to have our program talk to itself when we want it to, and to get it to do some really interesting things.
else
end if
END ReturnVectorDirection
This script just takes a parameter passed to it, fNumber, and determines if that number is greater than or less than zero. If it’s more than zero, we return (pass back) the value 1.0; if fNumber is less than zero, we return
—1.0. We’ll be making use of this script to ensure that when the ball
bounces it continues to head in more or less the same direction it was
before. So if it’s coming from the lower left and needs to bounce off
the top edge of the Stage, the ball will move toward the lower right
after the bounce.
if lVector = [ 0, 0 ] then
else
end if
me.ChooseVector( fHMult, 1.0 )
else if sprite(me.spriteNum).right > the stage.drawRect.right then
else if sprite(me.spriteNum).top < the stage.drawRect.top then
else if intersect ( sprite(me.spriteNum).rect, sprite(1).rect ) <> rect ( 0, 0, 0, 0 ) then
else if sprite(me.spriteNum).bottom > the stage.drawRect.bottom then
end if
END exitFrame
Okay, take a couple deep breaths. This isn’t anywhere near as bad as it looks.
lVector = sendSprite ( me.spriteNum, #VectorMotion_GetVector )
You already know, now, what sendSprite does. All we’re doing here is asking the Vector Motion behavior to run the handler called VectorMotion_GetVector. This will return another Director list, this one containing a description of the direction in which the ball sprite has been programmed to move. We put that value into the variable named lVector.
if lVector = [ 0, 0 ] then
We’re determining here whether the lVector list we just got from the sendSprite call is [0, 0]. If it is, this means that the ball sprite has not yet been programmed to move. If that turns out to be the case, the next few lines are executed, which pick a random horizontal direction to get the ball moving and then set up the initial motion parameters for the ball. Here’s how it works:
if random ( 2 ) = 1 then
fHMult = —1.0
else
fHMult = 1.0
end if
We take a random value for the number 2, which will always be either 1 or 2, and if the value is 1 we set a variable to be negative 1. Otherwise, the value for random ( 2 ) was 2, and so we set the variable to be positive 1 instead. (As you can see, the positive or negative number is also a decimal.)
me.ChooseVector( fHMult, 1.0 )
Here we make a direct internal call to the ChooseVector script we created earlier, and pass along the fHMult variable whose value we just randomly set to either —1.0 or 1.0, along with another hard-set value, 1.0. By passing along a random negative or positive number in the first slot, we allow the ChooseVector script to cause the ball to tend either left or right across the screen as it begins moving. This is because a positive value for the horizontal motion variable will make the ball move more or less rightward, while a negative value will make it move more or less leftward. The positive value in the second position causes the vertical motion parameter to be positive, which will make the ball move generally downward. A negative value here would make the ball move upward.
We want the game to start with the ball moving downward, since it will be heading toward the paddle. However we also want to let it move randomly to the left or right, so the game never begins with the ball moving in exactly the same direction every time.
However, everyting in the if…then control structure will be skipped if lVector is not equal to [0, 0], because in that case the ball is already in motion and we need to figure out where it is and what to do next. That’s what the rest of this exitFrame event script does.
if sprite(me.spriteNum).left < the stage.drawRect.left then
This you recognize from your paddle script. We’re checking to see if the left edge of the ball sprite is at or beyond the left edge of the Stage. If it is, we need to make it bounce.
fVMult = me.ReturnVectorDirection( lVector[2] )
This calls the other little utility script we made. We grab the number at position 2 of the lVector list, which represents the current vertical motion of the ball. We then pass that number to our ReturnVectorDirection script, which determines whether the number we send it is more or less than zero, and kicks back a value based on that determination. This number turns into the vertical multiplier we’ll send along to our ChooseVector script, which will make the ball continue moving in more or less the same vertical direction, but which will reverse its horizontal direction. Thus if it was moving toward the bottom of the Stage when it hit the left wall, it will continue moving more or less downward, but it will bounce toward the right as well.
me.ChooseVector( 1.0, fVMult )
This line calls our ChooseVector script with a positive number for the horizontal parameter, which will make the ball move rightwards on the Stage, and with our previously calculated number for the vertical modifier, which will allow it to continue moving in the same vertical direction it was going toward before it hit the left "wall."
else if sprite(me.spriteNum).right > the stage.drawRect.right then
fVMult = me.ReturnVectorDirection( lVector[2] )
me.ChooseVector( —1.0, fVMult )
This set of lines does exactly the same as those preceding, but for the right edge of the Stage. Note that this time the horizontal modifier is a negative number, which will cause the ball to bounce to the left.
else if sprite(me.spriteNum).top < the stage.drawRect.top then
fHMult = me.ReturnVectorDirection( lVector[1] )
me.ChooseVector( fHMult, 1.0 )
Here again we’re doing something similar, this time for the top of the Stage. This time we want the ball to keep moving in more or less the same horizontal direction, which is why we determine whether the number in lVector’s first position is positive or negative, but we want its vertical direction to change so that it will bounce.
else if intersect ( sprite(me.spriteNum).rect, sprite(1).rect ) <> rect ( 0, 0, 0, 0 ) then
fHMult = me.ReturnVectorDirection( lVector[1] )
me.ChooseVector( fHMult, —1.0 )
This one might not look quite as you expected, but we’re just using Lingoesque terminology to determine if the ball has actually hit the player’s paddle. We do this by testing the intersect of the rectangles for the paddle sprite and the ball sprite. The intersect test returns the rectangle defined by the area where the two sprites overlap. If the area is defined by rect ( 0, 0, 0, 0 ), this means the sprites are not overlapping at all; they’re not even touching. If the intersect value is anything else, we know that the ball has hit the paddle, and we do another calculation to make it bounce back upward.
else if sprite(me.spriteNum).bottom > the stage.drawRect.bottom then
go ( the frame + 1 )
end if
This is the final test in our exitFrame event script; it determines if the bottom of the ball sprite is at or below the bottom of the Stage. If it is, it’s because the player missed the ball, and we need to reset the game. We do this by jumping to frame 2, which is why I had you make the paddle occupy the first two frames of the Score, while the ball occupies only frame 1.
This will cause your playback head to jump back to frame 1 after it’s gone into this second frame. What this does is force a beginSprite
event to happen all over again with the ball sprite, which will reset
its location onscreen and start the next round of racquetball.