Tongue lashing monster behaviors
June 13, 1999
by Pat McClellan
Dear Multimedia Handyman,
I'm trying to write a lingo script to have a stationary "monster" eat moving objects that fly randomly around his head by rolling out his tongue. The size of the tongue is initially set at 2x2pixels (i.e., barely visible -- in his mouth) and should extent to the mouseLoc on the mouseDown handler (mouseDown script is attached to the moving object). Since the tongue "rolls-out" as it were, it should extend one (perhaps two or three) pixels at a time until it reaches the mouseLoc and then retract the same amount until it is back to its initial position. If the user has anticipated the flying object correctly the monster's tongue should collide with the flying object and advance the users score. The scoring options are already programmed but the tongue rollout and return is giving me problems.
DCS
Dear D,
First, I wouldn't attach the script to a mouseDown on the target. Since the tongue is going to lash out quickly, there's almost no way for it to miss hitting the target. It seems like the player should be able to lash the tongue at empty space as well (to allow for a miss.)
Since you didn't say which version of Director you're using, I'm not going to assume you have D7. Director 7 has the ability to rotate sprites, but we can do a pretty good job of it the "old fashioned way". To simulate a monster's "tongue-lash", we'll use a quickdraw shape (line) which will originate at the center of the monster's mouth, extending out to the point at which the mouse clicked, and then retracting at the same rate. It might be nice to have a variable speed built into the behavior -- perhaps speeding up as the player gains more points. I'll leave those details to you for experimentation, but for now, I'll build in the speed control.
It's important to understand how Director deals with lines and rects.
Every sprite has a rect, which is the list of points which define the bounding rectangle around the sprite. The syntax for it is:
rect(left, top, right, bottom)
If you draw a circular bitmap member and drag it onto a stage with a dark background, you'll see a white rectangle background behind whatever you've drawn. Usually, you'll set the ink effect to background transparent to make the white background disappear, but you can still control the height and width of the sprite using Lingo, by setting the "rect of sprite X".
In geometry, a line is defined by two points in space (or on a grid). But in Director, a line (as drawn by the tool palette) is defined as the diagonal line between opposing corners of a rect. Another important consideration is that the registration point of a quickdraw member is always the top left corner of its rect. That means that when you drag that member onto the stage, its sprite's loc corresonds to the top left corner.
For reasons known only to Director engineers, a line is either right sloping or left sloping. I'll explain that a little differently. Picture a rectangle. Now draw a line from the bottom left to the top right corner. This is a right sloping line. You can squeeze the width of the rect down to the width of the line -- in which case the line will appear to be perfectly vertical. Or you can stretch the rectangle out wide and flat to create the appearance of a horizontal line. But as far as Director is concerned, that line is still sloping right because is it defined as connecting the bottom left and top right corners of that rect. There is no way you can distort that rect in such a way as to make the line appear to slope up and to the left.
Inversely, a left sloping line connects the top left and bottom right corners of its rect. Remember that I said that the loc of a quickdraw member is always the top left corner -- the point corresponding to the first two values in the rect definition. That means that one end of a left sloping line will always be its loc. However, the loc of a right sloping line is not either of its ends. This will be important shortly.
To make the monster's tongue grow and retract, we'll need to systematically adjust the rect of the "tongue" sprite so that the line stretches from the mouth to the point where the mouse clicked. Since we know that the tongue lash will always emanate from the same origin, we can assume that the origin point will always be one of the corners of our rect. At full extension, the opposing corner of the rect will be the point of the mouse click. When the tongue is fully retracted into the mouth, we can set both corners of the rect at the same point, which will effectively make the tongue disappear.
A sample movie is available for download in Mac or PC format.
Click around the stage in this demo. Notice that when you click above and to the right of the monster, the tongue line is right sloping. Same thing if you click below and left of the monster. But, if you click below and right, or above and left, the tongue line is left sloping. So, that's the first bit of intelligence we'll need to build into our behavior. It must evaluate whether the mouse click was above or below and left or right of the origin point, and it must swap the member of the sprite to either left sloping or right sloping.
Here's the logic for the decision (not correct Lingo syntax):
if the mouseH > OriginH then it's to the right, and if the mouseV > OriginV then it's below (and still to the right) so set the member to the left sloping castmember else the mouseV must be above the originV (and still to the right) so set the member to the right sloping castmember But... if the mouseH < the originH then it's to the left, and if the mouseV > OriginV then it is below (and still to the left) so set the member to the right sloping castmember else the mouseV must be above the originV (and still to the left) so set the member to the left sloping castmember
That can be confusing, but just make sure you understand it before trying to write the code. Otherwise, you'll get it so messed up you'll never find the mistake. I'm using the terms originH and originV to describe the point where the tongue comes out of the mouth.
Another way to picture it is to imagine the origin point at the center of a round clock. If you click the mouse anywhere from 12 to 3 on the clock face, the line would be right sloping. From 3 to 6, left sloping; 6 to 9, right sloping; and from 9 to 12, left sloping.
Besides the slope of the line, we'll need to figure out a way to make it extend and retract. The number of frames it takes to extend or retract corresponds to "speed", so we can think of speed in terms of frames: the fewer the frames, the faster the tongue lash. For our behavior, I'll assume that the fastest will be 1 frame, and the slowest will be 10 frames.
Let's refer to the point that the mouse clicks as our target point. That point has a horizontal coordinate (let's call it targetH) and a vertical coordinate (targetV). So when the mouse clicks, we need to calculate the difference between our originH and the targetH, and then divide that number by the speed. This value (which I'll call deltaH) is how much we need to widen the tongue sprite's rect on each subsequent frame. Similarly, divide the vertical distance by speed to get the incremental change in the rect's height (deltaV).
So, let's imagine what's happening during a tongue lash. Let's assume a speed of 5 frames, and a mouse click that is 30 pixels to the right (deltaH) and 40 pixels below (deltaV) the origin point. That gives us a deltaH of 6 and a deltaV of 8. As the tongue extends, on the first frame, the tip of the tongue will move 6 pixels to the right and 8 pixels down. On the second frame, it will again move 6 pixels right and 8 pixels down. If we count out 5 frames (our speed), then the tip of the tongue will have reached the target point. Now, we'll need to start counting the frames again, this time subtracting the deltaH and deltaV on each frame until all four points of the tongue sprite's rect are at the origin point.
Remember that the loc of the tongue sprite is always the top left corner, so as we adjust the rect, we'll also have to adjust its loc. For example, if the target is up and to the left, as the rect grows, the locH and locV of the rect will move toward the target, rather than remaining at the origin. in fact, the loc only remains at the origin when the target is down and right.
I've set up the getPropertiesDescriptionList handler to allow the author to specify the speed, the origin point, as well as the members for the left and right sloping lines. Here's the behavior, which you should now be able to confidently leapmonster your way through.
-- tongueLash behavior -- copyright © 1999, ZZP Online, LLC -- free use for readers of http://www.director-online.com property pOriginH, pOriginV, pSlopeRightMem, pSlopeLeftMem, ¬ pSprite, pFlag, pSpeed, pDeltaH, pDeltaV, pFrameCount, ¬ pTipH, pTipV, pDirection on getPropertyDescriptionList me set pdlist to [:] addprop pdlist, #pSpeed, [#comment:"Speed in frames:", ¬ #format:#integer, #default:5, #range: [#min:1, #max:10]] addprop pdlist, #pOriginH, [#comment:"OriginH", ¬ #format:#integer, #default:0] addprop pdlist, #pOriginV, [#comment:"OriginV", ¬ #format:#integer, #default:0] addprop pdlist, #pSlopeRightMem, [#comment:"Right ¬ sloping member", #format:#shape, #default:0] addprop pdlist, #pSlopeLeftMem, [#comment:"Left ¬ sloping member", #format:#shape, #default:0] return pdlist end getPropertyDescriptionList on beginSprite me set pFlag = #stop set pSprite = the spriteNum of me set the rect of sprite pSprite = rect(pOriginH, ¬ pOriginV, pOriginH, pOriginV) end on tongueLash me if the mouseH > pOriginH then if the mouseV > pOriginV then set pDirection = #rightDown else set pDirection = #rightUp end if else if the mouseV > pOriginV then set pDirection = #leftDown else set pDirection = #leftUp end if end if case pDirection of #rightUp : set the member of sprite ¬ pSprite to member pSlopeRightMem #rightDown: set the member of sprite ¬ pSprite to member pSlopeLeftMem #leftDown : set the member of sprite ¬ pSprite to member pSlopeRightMem #leftUp: set the member of sprite ¬ pSprite to member pSlopeLeftMem end case set pTipH = pOriginH set pTipV = pOriginV set the rect of sprite pSprite = rect(pOriginH, ¬ pOriginV, pOriginH, pOriginV) set targetH = the mouseH set targetV = the mouseV set pDeltaH = (pOriginH - targetH) / pSpeed set pDeltaV = (pOriginV - targetV) / pSpeed set pFrameCount = 0 set pFlag = #lash puppetSound 1, "slurp" end on exitFrame me if pFlag = #stop then exit else if pFlag = #recoil then set pDeltaH = pDeltaH * -1 set pDeltaV = pDeltaV * -1 set pFlag = #pullBack end if case pDirection of #rightUp: set left = pOriginH set pTipV = pTipV - pDeltaV set top = pTipV set pTipH = pTipH - pDeltaH set right = pTipH set bottom = pOriginV #rightDown: set left = pOriginH set top = pOriginV set pTipH = pTipH - pDeltaH set right = pTipH set pTipV = pTipV - pDeltaV set bottom = pTipV #leftUp: set pTipH = pTipH - pDeltaH set left = pTipH set pTipV = pTipV - pDeltaV set top = pTipV set right = pOriginH set bottom = pOriginV #leftDown: set pTipH = pTipH - pDeltaH set left = pTipH set top = pOriginV set right = pOriginH set pTipV = pTipV - pDeltaV set bottom = pTipV end case set newRect = rect(left,top,right,bottom) set the rect of sprite pSprite = newRect updateStage set pFrameCount = pFrameCount + 1 if pFrameCount = pSpeed then if pFlag = #lash then set pFlag = #recoil set pFrameCount = 0 else set pFlag = #stop end if end if end
You'll notice that there is no mouseDown or mouseUp handler in the behavior, because you never click on the tongue. Instead, all of the action happens on a handler called -- amazingly enough -- on tongueLash. That handler gets called by a mouseDown handler in a movie script. Since it's a movie script, it doesn't get attached to any sprite. Rather, it is executed on every mouseDown throughout the movie. Here's the script, which uses the sendSprite command to call the tongueLash handler. Note that in my demo, the tongue sprite is in sprite channel 6, so that number must be in the sendSprite command. on mouseDown sendSprite(6, #tongueLash) end
Finally, just for kicks, I created a behavior to make the monster's eyes follow the mouse. In D7, there's a behavior that comes with the Library which will rotate a sprite to follow the mouse. However, if you're still using a previous version, you'll need to create several cast members to correspond to the rotations of the eyes. I used only 4 cast members. If you understood the tongueLash behavior, you'll have no problem figuring this one out. You'll notice that since the eyes move based on the relative position of the mouse and each eye, they move independently (you can make the monster cross-eyed.) Here's the behavior.
-- watchMouse behavior -- copyright © 1999, ZZP Online, LLC -- free use for readers of http://www.director-online.com property pSprite, pLeftMem, pRightMem, pDownMem, ¬ pUpMem, pMyLocH, pMyLocV on getPropertyDescriptionList me set pdlist to [:] addprop pdlist, #pLeftMem, [#comment:"Left member:", ¬ #format:#bitmap, #default:0] addprop pdlist, #pRightMem, [#comment:"Right member:", ¬ #format:#bitmap, #default:0] addprop pdlist, #pDownMem, [#comment:"Down member", ¬ #format:#bitmap, #default:0] addprop pdlist, #pUpMem , [#comment:"Up member", ¬ #format:#bitmap, #default:0] return pdlist end getPropertyDescriptionList on beginSprite me set pSprite = the spriteNum of me set pMyLocH = the locH of sprite pSprite set pMyLocV = the locV of sprite pSprite end on exitFrame if the mouseH < pMyLocH then if the mouseV < pMyLocV then set the member of sprite pSprite to ¬ member pLeftMem else set the member of sprite pSprite to ¬ member pDownMem end if else if the mouseV < pMyLocV then set the member of sprite psprite to ¬ member pUpMem else set the member of sprite pSprite to ¬ member pRightMem end if end if end
Good luck with your game.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.