|
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
The game consists mostly of one large behavior. This is the script that controls the items on the right side of the screen. When the player clicks on one of these sprites, the behavior allows him or her to drag it anywhere on the screen. When the player releases the mouse button, the behavior tests to see if a match has been made. This section of this chapter looks at this behavior, piece by piece. You can see this same behavior in the example movie for this chapter on the CD-ROM. The behavior is called "Drag, Drop, and Match Game Behavior." Now, let's look at each handler individually to examine how the behavior works. The goal is to have a behavior that can be used by the programmer, or possibly a non-programmer multimedia author. This author could take this behavior, without knowing any Lingo at all, and make a game from it. The first part of this behavior, as in any, is the declaration of properties. In this case, we have several properties that the author will set when placing each sprite on the Stage. The rest are of no concern to the author. They are used by the behavior to keep track of the sprite's progress in the game. -- Settable properties property pMatchWith, pMatchSprite, pNoMatchAction property pMatchSound, pNoMatchSound, pAllMatchedFrame property pDrag -- if sprite is being dragged property pDragOffset -- offset of click from registration point of sprite property pOrigLoc -- remember the original location to "snap back" to property pMatch -- if the sprite has been locked to another Because the first six properties need to be defined in the on getPropertyDescriptionList anyway, we leave the comments for those properties for that handler. Meanwhile, the comments after the other properties show their purpose. The first handler in any behavior should be the on getBehaviorDescription handler. The only purpose for this is to provide a description for the behavior in the behavior inspector. Many programmers like to make this description very long, and include details about the settable properties as well. You can do that if you think it will be useful in your production process. For the purposes of this book, we will keep these short, or not use them at all. on getBehaviorDescription me text = "Allows the sprite to be dragged." put "Will lock the sprite to another specific sprite if it is dragged to it." after text put "Goes to a new frame when all sprites are locked." after text return text end The on getPropertyDescriptionList provides all the options that the author can change to make the behavior fit his or her needs. Here is the handler. Look at each item that is added to the list and see if you can determine what the behavior's settings dialog box, shown in Figure 3.4, will look like. The explanation of this handler follows the code. The [cc] character is Lingo's continuation character. Instead of pressing Return or Enter to advance to the next line of code, type an Option+L on Mac or an Alt+L in Windows. It is used to wrap long lines of code without really breaking them apart. When programming in Lingo, you don't need to use them at all as your lines can be as long as they want. However, because a book's pages have a finite width, I use them in the book.
on getPropertyDescriptionList me
list = [:]
-- Allow the author to have the sprite match with the previous,
-- next, or a specific sprite
addProp list, #pMatchWith,[cc]{1}
[#comment: "Match With",[cc]
#format: #string,[cc]
#range: ["Previous Sprite","Next Sprite","Specific Sprite"],[cc]
#default: "Previous Sprite"]
-- if a specific sprite is to be used, what is its number?
addProp list, #pMatchSprite,[cc]
[#comment: " Specific Sprite",[cc]
#format: #string,[cc]
#default: 0]
-- when a sprite is dropped to the wrong spot, what is done?
addProp list, #pNoMatchAction,[cc]{2}
[#comment: "When No Match",[cc]
#format: #string,[cc]
#range: ["Nothing", "Snap Back"],[cc]
#default: "Snap Back"]
-- when a sprite is matched, what sound is played?
addProp list, #pMatchSound,[cc]
[#comment: "Match Sound",[cc]
#format: #string,[cc]
#default: ""]
-- when a sprite is not matched, what sound is played?
addProp list, #pNoMatchSound,[cc]
[#comment: "No Match Sound",[cc]
#format: #string,[cc]
#default: ""]
-- when all sprites are matched, which frame should the movie go to?
addProp list, #pAllMatchedFrame,[cc]
[#comment: "All Matched Frame",[cc]
#format: #marker,[cc]
#default: #next]
return list
end
Figure 3.4
The Parameters dialog box for our game behavior.
The first property, "pMatchWith", gives the author three options.{1} The first two, "Previous Sprite" and "Next Sprite" use the sprite's current location in the Score to determine the location of the sprite that it is supposed to match. The third option, "Specific Sprite" allows the author to specify the exact destination sprite, regardless of where the moveable sprite is located in the Score. It uses the "pMatchSprite" property, which would otherwise go unused. The next property, "pNoMatchAction", determines what happens when the user places an item in the wrong spot{2}. The default is to have the sprite snap back to its original location. Also offered is the option of just leaving the sprite where the user dropped it. The next two properties are the sounds that will be played when the sprite is dropped onto a location. One sound is for a correctly placed drop, and the other is for an incorrectly placed drop. If these properties are left blank, no sound is played at all. The last property is the name of the frame that the movie jumps to when all the items have been matched. Now, lets look at the other handlers in the behavior. The on beginSprite handler initializes the "pMatch", "pDrag", and "pOrigLoc" properties. It also sets the "pMatchSprite" property.
on beginSprite me
-- initialize values
pMatch = FALSE
pDrag = FALSE
pOrigLoc = sprite(me.spriteNum).loc
-- set pMatchSprite if a specific sprite is not chosen
case pMatchWith of
"Previous Sprite":
pMatchSprite = me.spriteNum - 1
"Next Sprite" :
pMatchSprite = me.spriteNum + 1
end case
end
When the user clicks down on the mouse, the dragging begins. Thereafter, until the mouse is released, the sprite should follow the cursor. However, this is more complex than it seems. Setting a sprite to a specific location is easy, just set the loc property of the sprite. Because the sprite is to follow the cursor, it makes sense to set the location of the sprite to the location of the cursor. However, this means that the registration point of the sprite will be positioned under the cursor. But what if the user did not click the registration point? In that case, the first thing that would happen when the user clicks is that the sprite would seem to jump so that its registration point is aligned with the cursor. The Lingo property the clickLoc is used to get the Stage location of a click. It is only valid in handlers like on mouseUp, on mouseDown, or the handlers that they call. It returns a point structure. A better way to do this is to record the offset between the location of the sprite and the click. Then, use this offset while dragging the sprite. This method keeps the cursor and the spot clicked on aligned, creating a much more natural feel to the drag. The on mouseDown handler records this offset in the "pDragOffset" variable. It also sets the "pDrag" variable to TRUE. Before it does anything, however, the on mouseDown handler checks the "pMatch" variable to see if this sprite has already been matched to its destination sprite. In that case, no dragging should be done. -- the user begins the drag on mouseDown me if pMatch then exit -- already locked in place pDrag = TRUE pDragOffset = the clickLoc - sprite(me.spriteNum).loc end The constants TRUE and FALSE in Lingo are actually just substitutes for the numbers 1 and 0. When used in comparisons, a value of 0 is taken as false and any other numerical value is taken as true. However, the constants TRUE and FALSE make your code much easier to read. When the user lifts up the mouse button, the drag is over. The on mouseUp handler sets the "pDrag" variable to FALSE and then calls our own custom "on checkForMatch" handler. -- the user ends the drag on mouseUp me if pMatch then exit -- already locked in place pDrag = FALSE checkForMatch(me) end If the player is dragging the sprite fast enough, it is possible that they could release the mouse button at a time when the sprite has not yet been redrawn to be under the cursor. In that case, an on mouseUpOutside handler is called instead of on mouseUp. Because, in this case, we don't care that the sprite was not actually under the cursor, we just redirect this call to the behavior back to the on mouseUp handler. -- user moved the mouse quickly, record as mouseUp anyway on mouseUpOutside me mouseUp(me) end The on exitFrame handler is called on a regular basis as the movie loops on the frame. It is here that we need to reposition the sprite if a drag is in progress. We set the loc of the sprite to the cursor's position, remembering to subtract the drag offset we recorded in the on mouseDown handler.
-- if a drag is in progress, reposition the sprite
on exitFrame me
if pDrag then
sprite(me.spriteNum).loc = the mouseLoc - pDragOffset
end if
end
The on mouseUp handler calls "on checkForMatch" when a drag is complete. This handler needs to determine if the sprite is not over its mate. To do this, it uses the inside function to compare the location of the sprite to the rectangle of the sprite it should match. If a match is found, the "pMatch" variable is set to TRUE. Then, a sound is played if the game's creator supplied a "pMatchSound" property. The "on lockInPlace" handler is called to give the sprite its permanent, correct, position{3}. Finally, the "on checkForAllMatch" handler is called to see if the game is over. In the case where the sprite is not over its mate, the "pNoMatchSound" property is examined and a sound is played if needed. Also, if the "pNoMatchAction" is set to the default "Snap Back", then the sprite's location is reset to its original value{4}.
-- check to see if the sprite is over its match
on checkForMatch me
-- see if the location of the sprite is inside the rect of the matching sprite
if inside(sprite(me.spriteNum).loc, sprite(pMatchSprite).rect) then
-- record the match
pMatch = TRUE
-- play a sound if one is needed
if pMatchSound <> "" then puppetSound pMatchSound
-- lock the sprite into position
lockInPlace(me,pMatchSprite){3}
-- see if all the sprites are matched
checkForAllMatch(me)
else
-- play a sound if one is needed
if pNoMatchSound <> "" then puppetSound pNoMatchSound
case pNoMatchAction of
"Snap Back":
-- put the sprite back in its original location
sprite(me.spriteNum).loc = pOrigLoc{4}
end case
end if
end
The "on lockInPlace" handler sets the location of the dragged sprite to the location of the destination sprite. However, in the case that both sprites are text members, it sets the dragged sprite to match the upper-right corner of the destination sprite, thus placing the two sprites side-by-side.
on lockInPlace me , otherSprite
if (sprite(otherSprite).member.type = #text) and (sprite(me.spriteNum).member.type = #text) then
-- if both are text, then loc the sprite to the upper right corner
loc = point(sprite(otherSprite).rect.right, sprite(otherSprite).rect.top)
else
-- if not text, then lock both sprites exactly together
-- and leave spacing up to the registration points
sprite(me.spriteNum).loc = sprite(otherSprite).loc
end if
end
Every time a match is successfully made, the "on checkForAllMatch" handler is called. This handler loops through all the sprites in the current frame. It calls a handler in these sprites named "on getMatch". This handler returns the value of "pMatch" for the sprite. The Lingo sendSprite command sends a message to a sprite, in effect calling a handler there. This is one way for behaviors to contact each other and send or request information. The beauty of the sendSprite command here is that it returns a value of VOID if the sprite does not have a behavior attached that responds to the "getMatch" message. We can test for this with the voidP function, and then disregard these sprites; we only want the results from sprites with this behavior attached. After the handler finds a sprite that has a "pMatch" of FALSE, the handler knows that the game is not over and uses the exit command to terminate the handler{5}. However, if it makes it through the loop without finding an unmatched sprite, the handler knows that the game is over and goes to the frame in the "pAllMatchedFrame" property.
-- check all sprites to see if any are not matched
on checkForAllMatch me
-- loop through all sprite channels
repeat with i = 1 to the lastChannel
-- get the value of pMatch for the sprite
match = sendSprite(sprite(i),#getMatch)
-- if it is a VOID value, then it is not the right type of sprite anyway
if voidP(match) then next repeat
-- if the sprite is not yet matched, then the game is not yet done
if match = FALSE then exit{5}
end repeat
-- if here, then all sprites found must be matched
go to frame pAllMatchedFrame
end
The last handler we need is the one that responds to the "getMatch" message. This is a simple handler that just returns the value of "pMatch". -- when requested, return the value of pMatch -- for use by the checkForAllMatch handler on getMatch me return pMatch end To better understand this behavior, it's best to look at it all together in the script member in the example on the CD-ROM. If you don't understand something in it, play the game and examine how it works. | |