|
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
Most of this game will actually be one large behavior that is attached to each and every jigsaw piece sprite. However, we will start by looking at the frame behavior script. The Frame BehaviorThis script serves three purposes. The first is to register the first and last sprite in the block of sprites that make up the jigsaw pieces. The Frame behavior takes these as parameter properties, and then passes those values on to global variables to use them there. Here is the beginning section of the "Jigsaw Puzzle Frame Behavior." All lines but the last line in the on beginSprite handler deal with setting up the sprite range.
-- these two globals allow the sprite range
-- to be passed to all the sprites for use
global gFirstSprite, gLastSprite
-- these properties are just used to get
-- the sprite range
property pFirstSprite, pLastSprite
-- the only two parameters are to set the sprite range
on getPropertyDescriptionList me
list = [:]
addProp list, #pFirstSprite,[cc]
[#comment: "First Puzzle Piece Sprite",[cc]
#format: #integer,[cc]
#default: 1]
addProp list, #pLastSprite,[cc]
[#comment: "Last Puzzle Piece Sprite",[cc]
#format: #integer,[cc]
#default: 1]
return list
end
Figure 5.4 shows the Parameters dialog box for this behavior. It reflects the list built in the on getPropertyDescriptionList handler.
Figure 5.4
The Parameters dialog box for the sprite behavior.
-- when the frame begins, set up the sprite range globals -- and randomize the pieces on beginSprite me gFirstSprite = pFirstSprite gLastSprite = pLastSprite randomizePieces(me) end The sprite range could have been established many different ways. These two globals could have been properties in the main sprite behavior instead, which would require them to be set exactly the same for every sprite. Having them set in the one and only frame behavior guarantees that they will not be set to different values by accident. The last line of the on beginSprite handler calls the custom "on randomizePieces" handler. This handler loops through all the sprites and sets them to random positions on the Stage{1}. This is the second purpose for this behavior. The behavior makes sure the edges of the rectangles for the sprites are not off of the Stage{2}, and that no piece touches any other on the Stage{3}.
-- move the pieces around the Stage until all are
-- completely on the Stage and none are overlapping
on randomizePieces me
numTries = 0
repeat with i = gFirstSprite to gLastSprite
repeat while TRUE
-- see if it may be locked up because of lack of sprite
numTries = numTries + 1
if numTries > 1000 then
i = gFirstSprite
numTries = 0
end if
-- pick a random location{1}
x = random((the stage).rect.width)
y = random((the stage).rect.height)
sprite(i).loc = point(x,y)
-- see if the sprite is hanging over the edge{2}
if sprite(i).rect.left < 0 then next repeat
if sprite(i).rect.right > (the stage).rect.width then next repeat
if sprite(i).rect.top < 0 then next repeat
if sprite(i).rect.bottom > (the stage).rect.height then next repeat
-- see if the sprite is overlapping another{3}
touchingOtherPiece = FALSE
repeat with j = gFirstSprite to i-1
if j = i then next repeat
if sprite j intersects i then
touchingOtherPiece = TRUE
exit repeat
end if
end repeat
if touchingOtherPiece then next repeat
-- this piece is set, go on to next
exit repeat
end repeat
end repeat
end
A peculiar thing can happen with this handler every once in a while. If the pieces are placed just right, the handler can run out of space on the Stage to place additional pieces. No matter how hard it tries, it cannot find a spot on the Stage where the last remaining piece or pieces can fit. In that case, the "numTries" counter in the handler reaches a high number, like 1,000, and the handler restarts the process by setting the "i" variable back to 0. This is an important part of the handler, because it insures that the handler won't loop infinitely. In addition, you must test your puzzle to make sure enough space is available on the Stage for the pieces to co-exist without their rectangles overlapping. The third and final purpose for this behavior is to do the traditional frame looping. on exitFrame me go to the frame end The Sprite BehaviorThe main code for this game is in the sprite behavior. This behavior needs to be attached to every puzzle piece sprite in the Score. The properties and globals used by the behavior are explained in the comments at the beginning of the script.
-- these two globals are set by the frame script
-- and contain the sprite range
global gFirstSprite, gLastSprite
property pOffset -- click and drag offset
property pDrag -- is the sprite being dragged
property pLinks -- which other sprites are linked to this one?
property pCloseness, pLinkSound, pGameOverFrame
on getPropertyDescriptionList me
list = [:]
-- how near this piece needs to be to another to link it
addProp list, #pCloseness,[cc]
[#comment: "How close does it need to be to other pieces to lock",[cc]
#format: #integer,[cc]
#default: 4]
-- sound to play when a link is made
addProp list, #pLinkSound,[cc]
[#comment: "Link Sound",[cc]
#format: #string,[cc]
#default: ""]
-- frame to jump to when game is over
addProp list, #pGameOverFrame,[cc]
[#comment: "Game Over Frame",[cc]
#format: #marker,[cc]
#default: #next]
return list
end
Figure 5.5 shows the Parameters dialog box for this behavior. It reflects the list built in the on getPropertyDescriptionList handler.
Figure 5.5
The Parameters dialog box for the frame behavior.
Each sprite keeps track of the others that are linked to it by the use of the "pLinks" property. This property is a list of these sprite numbers. This list also includes the sprite's own number. This list is set up in the on beginSprite handler. -- start by including self in links on beginSprite me pLinks = [me.spriteNum] end One of the special effects of this game is the cursor change. We want the cursor to change to an open hand when the user passes the cursor over a piece. We also want the cursor to change back when it isn't over any pieces. The on mouseEnter and on mouseLeave handlers take care of this. The cursor number for an open hand is 260. There are many ways to change the cursor in Director. For games like this, it's often useful to set the cursor property of a sprite for rollovers. This way, if the game ends or the user selects an option that takes him or her suddenly to another frame, the cursor is sure to switch back to normal as soon at the sprite is gone. on mouseEnter me -- open hand cursor sprite(me.spriteNum).cursor = 260 end on mouseLeave me -- reset cursor sprite(me.spriteNum).cursor = -1 end When the user starts a drag, the cursor needs to change to the closed hand cursor, which is number 290. In addition to setting the "pDrag" property and the "pOffset" property (as you saw in the two previous chapters), we will also call the "on moveToFrontZ" handler, which allows the piece to be drawn on top of all the others on the Stage as the user drags it. -- start a drag on mouseDown me pDrag = TRUE -- closed hand cursor sprite(me.spriteNum).cursor = 290 -- get click offset pOffset = the clickLoc - sprite(me.spriteNum).loc -- use locZ property to move this piece and all links to front moveToFrontZ(me) end When the user drops the piece being dragged, the cursor changes back to an open hand. In addition, the sprite, and all the sprites it is already linked to, are checked to see if they are in a position that would link them to more sprites. A repeat with...in loop is used to cycle through all the values in the "pLinks" property. The sprites are all set to the new location at the end of the drag. Then a message is sent to each sprite, which includes the current one, to execute the "on checkForNewLinks" handler. Notice that the "on restrictLoc" handler is called as a function with the mouse location. This "on restrictLoc" handler makes sure the location value is within the limits of the Stage, thus preventing someone from dragging a piece off the Stage, where they could never pick it up again. The on mouseUp handler also calls "on moveToNormalZ" to make sure that the sprites do not stay on top of others during future drags.
on mouseUp me
-- if not being dragged, then ignore
if not pDrag then exit
-- open hand cursor
sprite(me.spriteNum).cursor = 260
pDrag = FALSE
-- set all linked pieces to current position
-- check each piece to see if any can be linked to them
repeat with s in pLinks
newLoc = restrictLoc(me,the mouseLoc) - pOffset
sprite(s).loc = newLoc
sendSprite(s,#checkForNewLinks)
end repeat
-- move all sprites back to normal locZ
moveToNormalZ(me)
end
-- send mouseUpOutsides to mouseUp
on mouseUpOutside me
mouseUp(me)
end
The on exitFrame handler takes care of all the dragging. It uses the same "on restrictLoc" handler to restrict the mouse location. This handler also loops through all the values in "pLinks" to make sure they follow the sprite being dragged.
-- drag piece and all links
on exitFrame me
if pDrag then
-- drag piece
newLoc = restrictLoc(me,the mouseLoc) - pOffset
sprite(me.spriteNum).loc = newLoc
-- drag links as well
repeat with s in pLinks
sprite(s).loc = newLoc
end repeat
end if
end
Here is the "on restrictLoc" handler. It looks at the horizontal and vertical values of the location point and makes sure they are within the limits of the Stage. For extra assurance, we will make sure it isn't within 10 pixels of the edge of the Stage.
-- make sure a point is within the Stage
on restrictLoc me, loc
if loc.locH < 10 then loc.locH = 10
if loc.locH > (the stage).rect.width-10 then[cc]
loc.locH = (the stage).rect.width-10
if loc.locV < 10 then loc.locV = 10
if loc.locV > (the stage).rect-10.height then[cc]
loc.locV = (the stage).rect.height-10
return loc
end
After a drag has been completed, the "on checkForNewLinks" handler is called for every sprite involved in the drag. It loops through all the sprites, making sure to ignore ones that are already linked to the current sprite. The "on checkForNewLinks" handler calls the "on closeEnough" handler to see if a piece is in the proper position to be linked to the current sprite. It then plays a sound and calls "on addLink" to add this new sprite to the "pLinks" property. The handler then sets the position of all the sprites linked together to the current sprite's position{4}, thus locking all the sprites together. Lastly, the Òon checkForNewLinksÓ handler calls the "on checkGameDone" handler to see if the game is over.
-- see if this sprite touches any others
on checkForNewLinks me
repeat with s = gFirstSprite to gLastSprite
-- make sure the piece isn't already linked
if getOne(pLinks,s) then next repeat
-- see if the piece is close enough to be linked
if closeEnough(me,s) then
-- play sound
if pLinkSound <> "" then puppetSound pLinkSound
-- add to list of links
addLink(me,sprite(s).pLinks)
-- set all old and new linked pieces to the right location
repeat with ss in pLinks
sprite(ss).loc = sprite(me.spriteNum).loc{4}
end repeat
-- check to see if puzzle is done
checkDoneGame(me)
-- need look no further
exit
end if
end repeat
end
The "on closeEnough" handler checks three conditions to determine if two sprites are correctly positioned to be linked. First, it checks the horizontal difference, then, the vertical difference, and finally, it compares the two sprites' rectangles to see if they overlap. In fact, we add 1 pixel on every side to expand the bounding rectangle of the current sprite to make sure sprites that are just barely touching will be considered close enough. In every comparison, the "pCloseness" property is used to determine the amount of leeway the sprites have to be considered close enough to link up. The intersects function is a good way to determine if two rectangles overlap. However, the result is the actual rectangle that represents the intersection. So, to make a test out of intersects, we need to see if it's not equal to "rect(0,0,0,0)", which is the value returned if there is no intersection at all.
-- see if a sprite is close enough to this one to link
on closeEnough me, s
-- close enough horizontally
if abs(sprite(me.spriteNum).locH - sprite(s).locH) < pCloseness then
-- close enough vertically
if abs(sprite(me.spriteNum).locV - sprite(s).locV) < pCloseness then
-- see if the sprite rectangles are close enough
if intersect(sprite(s).rect,sprite(me.spriteNum).rect+[cc]
rect(-pCloseness-1,-pCloseness-1,pCloseness+1,pCloseness+1))[cc]
<> rect(0,0,0,0) then
-- close enough
return TRUE
end if
end if
end if
-- not close enough
return FALSE
end
The "on addLink" handler takes the "pLinks" property from both the current sprite, and the new sprite, and combines them into one new variable. The handler then assigns this value to the "pLinks" property of both sprites, as well as all the sprites involved. This insures that all the sprites linked together by this drag action now have the same value for the "pLinks" property.
-- merge the links of this sprite with the links of the
-- new sprite and set the pLinks property for all
-- of these sprites to be the new list
-- this effectively links this sprite to the others
-- and the others to this one
on addLink me, list
newlist = []
-- add all the links for this sprite
repeat with s in pLinks
add newlist, s
end repeat
-- add all the links for another sprite
repeat with s in list
add newlist, s
end repeat
-- set all sprites involved to have the same list
repeat with s in newlist
sprite(s).pLinks = newlist
end repeat
end
Checking to see if the game is over is a simple task. The "pLinks" property holds the list of all the sprites that are linked to the current one. When the puzzle is complete, all the sprites are linked, so all the "pLinks" properties will be a list of all the sprites in the puzzle. So all that is needed to determine the end of the game is to check the current sprite and see if the number of items in "pLinks" is equal to the total number of sprites.
-- if this is linked to all other pieces
-- then game must be over
on checkDoneGame me
-- how many pieces are there?
totalLinks = gLastSprite - gFirstSprite + 1
-- are all linked?
if pLinks.count = totalLinks then
go to frame pGameOverFrame
end if
end
The final two handlers take care of moving the sprites to the front of the screen for dragging, and then back to their normal layer when the drag is over. This is done by altering the locZ property of these sprites. The locZ property overrides the natural sprite layering determined by the sprite's channel in the Score. Because the Score in Director 7 can only go up to channel 1,000, we will use a locZ value of 1,001 for each sprite to make sure they are above all the sprites on the Stage.
-- move all sprites in pLinks to be on top
on moveToFrontZ me
repeat with s in pLinks
sprite(s).locZ = 1001
end repeat
end
To reset the locZ properties of the sprites, just set the locZ to the sprite's channel number.
-- move all sprites in pLinks to normal locZ
on moveToNormalZ me
repeat with s in pLinks
sprite(s).locZ = s
end repeat
end
| |