Articles Archive
Articles Search
Director Wiki
 

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.

Patrick McClellan is Director Online's co-founder. Pat is Vice President, Managing Director for Jack Morton Worldwide, a global experiential marketing company. He is responsible for the San Francisco office, which helps major technology clients to develop marketing communications programs to reach enterprise and consumer audiences.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.