|
Want to thank me for making this book available for free? Just buy Special Edition Using Macromedia Director MX
Advanced Lingo For Games
| |
| Making the Game
This game consists of two behaviors, one for the frame and one for the sprite. The frame behavior is the main one, keeping track of the game state, while the sprite behavior simply registers clicks and perform the animation. Frame BehaviorThe frame behavior uses five parameters and four properties. The parameters are used to determine the sprite range, the empty spot at the start of the game, and the frame that the movie jumps to when the game is over. -- parameters property pFirstSprite, pLastSprite property pOpenSpot, pGameOverFrame property pSlideSound The properties hold the calculated value of the width and height of the pieces. A property will also hold the location of the upper-left puzzle piece as a landmark for the location of the other pieces. The fourth property is one that will become quite common throughout the rest of this book. We name it "pMode" because it determines what mode the game is in. In this case, we will have two modes. One mode will be #normal for when the game is waiting for user input. The other will be #animate for when the puzzle pieces are moving. During the #animate mode, we will not allow the user to click on a puzzle piece. -- properties property pPieceWidth, pPieceHeight property pUpperLeftLoc, pMode The on getPropertyDescriptionList handler has its defaults set for a 5[ts]5, or 24-piece, puzzle. The 25th slot is the open one. Figure 6.3 shows the Parameters dialog box for this behavior.
Figure 6.3
The Parameters dialog box for the "Sliding Puzzle Frame Behavior."
on getPropertyDescriptionList me
list = [:]
-- the first puzzle piece channel
addProp list, #pFirstSprite,[cc]
[#comment: "First Sprite",[cc]
#format: #integer,[cc]
#default: 11]
-- the last puzzle piece channel
addProp list, #pLastSprite,[cc]
[#comment: "Last Sprite",[cc]
#format: #integer,[cc]
#default: 34]
-- the open spot, usually the bottom right corner
addProp list, #pOpenSpot,[cc]
[#comment: "Open Spot (point(x,y))",[cc]
#format: #point,[cc]
#default: point(5,5)]
-- the sound to be played during a slide
addProp list, #pSlideSound,[cc]
[#comment: "Slide Sound",[cc]
#format: #string,[cc]
#default: ""]
-- frame to jump to when the puzzle is solved
addProp list, #pGameOverFrame,[cc]
[#comment: "Game Over Frame",[cc]
#format: #marker,[cc]
#default: #next]
return list
end
When the frame is entered, the on beginSprite handler calculates the values of "pPieceWidth" and "pPieceHeight" based on the first puzzle piece sprite. All pieces should be the same dimensions. The "pUpperLeftLoc" is also set this way. The "pMode" is set to #normal, and then the "on randomizePieces" handler is called. -- set properties and randomize puzzle on beginSprite me -- get the piece width and height from the first piece pPieceWidth = sprite(pFirstSprite).width pPieceHeight = sprite(pFirstSprite).height -- get the location of the upper left piece to use as a landmark pUpperLeftLoc = sprite(pFirstSprite).loc -- start in normal mode, which allows clicks pMode = #normal -- mix it up randomizePieces(me) end To randomize the pieces on the Stage, we shuffle their positions. First, a "posList" variable is created. Then, it is filled with the positions of all the sprites. Next, all the sprites are assigned one position from the list.
-- this handler shuffles the piece positions
on randomizePieces me
-- create a list with all positions
posList = []
repeat with s = pFirstSprite to pLastSprite
add posList, sprite(s).loc
end repeat
-- randomly assign positions to sprites
repeat with s = pFirstSprite to pLastSprite
r = random(posList.count)
sprite(s).loc = posList[r]
deleteAt posList, r
end repeat
end
When a puzzle piece is clicked, the sprite behavior sends a #clickPuzzle message to the frame behavior. This message includes the sprite's number as a second parameter. The first thing that the "on clickPuzzle" handler does is make sure that the "pMode" is #normal. Otherwise, it will ignore the click. This happens when there is animation in progress. You'll see where we set the "pMode" to #animate later in this behavior. The handler then determines the location of the sprite in the puzzle by using its location, the location of the upper-left corner of the puzzle, and the width and height of each piece{1}. This position is stored in the two variables: "x" and "y". Then, the empty space in the puzzle, represented by the "pOpenSpot" property, is compared to the position of the piece to see if the two are next to each other. If so, then the "on move" handler is called with the horizontal and vertical difference between the two positions{2}. This "on move" handler takes care of swapping the piece for the empty slot.
-- the sprite behaviors call this handler with their sprite number
-- it determines if the piece is next to the empty space
-- and moves it there if it is
on clickPuzzle me, spriteNumber
-- make sure we are in normal mode
if pMode <> #normal then exit
-- determine the x and y position of the piece
x = (sprite(spriteNumber).locH-pUpperLeftLoc.locH)/pPieceWidth+1{1}
y = (sprite(spriteNumber).locV-pUpperLeftLoc.locV)/pPieceHeight+1
-- see if the open spot is to the left, right, above or below
if pOpenSpot.locV = y then -- move horizontally
if pOpenSpot.locH = x-1 then -- move left
move(me,spriteNumber,-1,0)
else if pOpenSpot.locH = x+1 then -- move right
move(me,spriteNumber,1,0)
end if
else if pOpenSpot.locH = x then -- move vertically
if pOpenSpot.locV = y-1 then -- move pup
move(me,spriteNumber,0,-1){2}
else if pOpenSpot.locV = y+1 then -- move down
move(me,spriteNumber,0,1){2}
end if
end if
end
The "on move" handler takes the difference between the puzzle piece position and the empty slot as two variables: "dx" and "dy". Variables with these names are often used to indicate the "difference" between two locations. The new location of the piece is calculated. Then, the new "pOpenSpot" property is calculated. Finally, the sprite is sent the #changeLoc message, which kicks off the animation to slide the piece into the empty slot. The "pMode" for the frame behavior is also changed, so clicks are ignored until the animation is done. -- move a sprite into the empty spot on move me, spriteNumber, dx, dy -- determine new location from dx, dy newloc = sprite(spriteNumber).loc + point(dx*pPieceWidth,dy*pPieceHeight) -- change the open spot to be where the piece was pOpenSpot = pOpenSpot - point(dx,dy) -- tell the sprite to animate to the new location sendSprite(spriteNumber,#changeLoc,newloc) -- set the mode, so that no clicks are allowed until animation is done -- play the sound if pSlideSound <> "" then puppetSound pSlideSound -- change the mode to no allow clicks until slide is complete pMode = #animate end When the sprite is done animating, it sends a #resetMode message back to the frame behavior. This does two things. First, it resets the "pMode" to #normal, thus allowing clicks to be recognized again. Second, it calls the "on checkForGameOver" handler to see if the puzzle is complete.
-- when animation is done, sprite will call this to allow more clicks
on resetMode me
pMode = #normal
-- also see if all pieces are now in the correct spots
if checkForGameOver(me) then
go to frame pGameOverFrame
end if
end
To determine if the game is over, the "on checkForGameOver" handler loops through all the sprites. The sprites are arranged in the order of the solved puzzle, with the upper-left corner piece first, the one to the right of it second, and so on. First, it checks to see if the sprite is just to the right of the sprite before it{3}. If this is so, then these two sprites are in order. Otherwise, it checks to see if the next sprite is the start of a new row{4}. If so, then the sprites are still in order. However, if there is any deviation from this, then it means that the sprites are out of order on the screen, and a FALSE is returned.
-- loop through all pieces starting with the second one
-- if each piece come either to the right, or starts a new row,
-- then the puzzle is done
on checkForGameOver me
-- get loc of first sprite
prevLoc = sprite(pFirstSprite).loc
repeat with i = pFirstSprite+1 to pLastSprite
-- get loc of this sprite
nextLoc = sprite(i).loc
-- see if they are next to each other
if nextLoc.locH <> prevLoc.locH + pPieceWidth then{3}
-- or, see if it is the first of the next row
if (nextLoc.LocV <> prevLoc.locV + pPieceHeight) or[cc]
(nextLoc.locH <> pUpperleftLoc.locH) then{4}
-- neither, so must be out of order
return FALSE
end if
end if
-- ready to look at next piece
prevLoc = nextLoc
end repeat
-- made it here, so all pieces must be in order
return TRUE
end
The last function of the frame behavior is the standard looping frame code. -- loop on the frame on exitFrame go to the frame end The Sprite BehaviorThe sprite behavior starts off by simply setting the cursor property of the sprite to 280, which is the finger cursor. This is a good way to indicate, without instructions, that the player is to click on the puzzle pieces. In addition, the sprite behavior also has a "pMode" property. We use the same two values for this "pMode" property as we did in the frame behavior: #normal and #animate. property pNewLoc, pMode on beginSprite me -- use the finger cursor for this sprite sprite(me.spriteNum).cursor = 280 -- normal mode (do nothing) pMode = #normal end When the sprite is clicked, the #clickPuzzle message is sent along to the frame behavior to be dealt with. -- pass mouse clicks along to the frame behavior on mouseUp me sendSprite(0,#clickPuzzle,me.spriteNum) end When the frame behavior determines that the sprite needs to change location, it calls the "on changeLoc" handler shown here. A parameter tells the sprite where the destination is. The "pMode" of the sprite behavior is changed to #animate. -- frame behavior calls this to initiate an animation on changeLoc me, newloc -- record the destination location pNewloc = newloc -- allow the animation pMode = #animate end The "on exitFrame" behavior uses the "pMode" property to determine if it's supposed to be doing anything. If it is, the "on exitFrame" behavior then examines its current position with the "pNewLoc" position, and brings it one pixel closer to the destination.{5} If the destination is reached{6}, a message, #resetMode, is sent back to the frame behavior, and the "pMode" of the sprite behavior changes back to #normal.
on exitFrame me
-- if the destination does not equal the current position
if pMode = #animate then
-- move the location by one pixel in the right direction
curloc = sprite(me.spriteNum).loc
if curloc.locH < pNewLoc.locH then
curloc = curloc + point(1,0){5}
else if curloc.locH > pNewLoc.locH then
curloc = curloc - point(1,0){5}
else if curloc.locV < pNewLoc.locV then
curloc = curloc + point(0,1){5}
else if curloc.locV > pNewLoc.locV then
curloc = curloc - point(0,1){5}
end if
sprite(me.spriteNum).loc = curloc
-- if this ends the animation, tell the frame script
-- so its pMode can be changed to #normal
-- and set the sprite's pMode to #normal too
if curloc = pNewLoc then{6}
sendSprite(0,#resetMode)
pMode = #normal
end if
end if
end
Note that the position of the sprite is changed by one pixel at a time. The example movie on the CD-ROM is set to 120 frames per second. So a 40[ts]40 piece should slide into a new position in one-third of a second. However, if you feel that you need to make the game work smoothly for slower computers, ones that cannot do 120 frames per second, you might want to move the pieces more than one pixel at a time. You can change to four point values in the previous handler to use any number of pixels you want. However, make sure that it uses a number divisible by the dimensions of the pieces. For instance, if the pieces are 40 pixels wide and 40 pixels high, then 1, 2, 4, and 8 work well. However, if the pieces move 3 pixels at a time instead, they will overshoot the destination location because 40 is not divisible by 3. | |