Note About This Book: Advanced Lingo For Games was written by Gary Rosenzweig in 2000 for users of Macromedia Director 7. It is presented here for free on an as-is basis, with no updating. Most of the information and code here can be used in the most recent version of Director. The book has been reproduced from the final editing files archived in 2000, and not the final proof galleys. So some minor differences between this version and the printed version my exist. The entire contents of this book are Copyright 2000, Gary Rosenzweig. No part may be reproduced or copied without written permission. The text here is provided for individual use only.
Want to thank me for making this book available for free? Just buy Special Edition Using Macromedia Director MX and we'll call it even!

Advanced Lingo For Games
by Gary Rosenzweig


Chapter 12 Section 2

Making the Game

Behaviors are necessary for the frame, paddle, ball, and bricks.

Frame Behavior

In this game, the frame behavior doesn't do as much as in the previous two chapters. It delegates collision detection to the ball sprite, because it is the only object that collides with anything.

The frame behavior is mostly concerned with working with the score and the number of lives, or balls, remaining.


property pNextLevelFrame
property pEndGameFrame
property pStartLives

global gScore, gLives

The on getPropertyDescriptionList handler allows you to set the number of lives that the player starts with. This should only be set for the first level. A value of 0 for the other levels should be used as a signal not to reset the lives and score. Figure 12.2 shows the Parameters dialog box that results.

Figure 12.2
The frame behavior Parameters dialog box.

on getPropertyDescriptionList me
  list = [:]

  -- number of lives to start with, or 0 if not first level
  addProp list, #pStartLives,[cc]
    [#comment: "Starting Lives",[cc]
     #format: #integer,[cc]
     #default: 0]

  -- next level
  addProp list, #pNextLevelFrame,[cc]
    [#comment: "Next Level Frame",[cc]
     #format: #marker,[cc]
     #default: #next]

  -- game over
  addProp list, #pEndGameFrame,[cc]
    [#comment: "End Game Frame",[cc]
     #format: #marker,[cc]
     #default: #next]

  return list
end

When the frame starts, it determines if the number of lives and the score should be reset.


on beginSprite me
  -- if pStartLives is not 0 then set gLives, reset score
  if pStartLives <> 0 then
    gLives = pStartLives
    gScore = 0
  end if
  showScore(me)
end

The frame behavior is in charge of incrementing the score and also displaying it.


on addPoints me, n
  gScore = gScore + n
  showScore(me)
end

on showScore me
  text = ""
  put "Score:"&&gScore&RETURN after text
  put "Lives:"&&gLives after text
  member("score").text = text
end

When a ball goes past the bottom of the Stage, the "on endLife" handler in the frame behavior is used to subract the life, send the reset message, and also determine if the game is over.


-- missed ball, lose life
on endLife me
  gLives = gLives - 1
  if gLives < 0 then
    go to frame pEndGameFrame
  else
    showScore(me)
    sendAllSprites(#reset) -- send reset to ball
  end if
end

When all the bricks are gone, the "endLevel" message is sent to the frame behavior. The purpose for this is to keep the "pNextLevelFrame" property as a frame behavior property rather than separating it from "pEndGameFrame".


on endLevel me
  go to frame pNextLevelFrame
end

And, of course, the on exitFrame handler keeps the frame looping.


on exitFrame
  go to the frame
end

Ball Behavior

The ball sprite behavior is the main behavior in this game. It is in charge of the movement of the ball, and determining if the ball hits anything. The ball sprite behavior checks to see if the ball hits a brick, a wall, or the paddle, and if it falls off the bottom of the screen. Each occurrence has its consequence.


property pMoveX,  pMoveY -- momentum
property pOrigMoveX, pOrigMoveY -- original momentum
property pOrigLoc -- original location
property pLoc -- current location
property pSpeed -- vertical speed
property pRadius -- radius of ball
property pPaddleSprite -- paddle sprite
property pMaxHitSlope -- slope to use when ball hits edge
property pPaddleSound, pWallSound

The ball behavior has a long list of parameters that can be set when the game is being created. Each of these can vary on different levels because the ball sprite exists individually on these frames.

The starting momentum is recorded in the "pOrigMoveX" and "pOrigMoveY" parameters. This is used to start the ball off at the beginning of the level, and is used to start a new ball after the player misses one. The channel number for the paddle sprite is also asked for.

The "pMaxHitSlope" determines how steep the angle at which the ball will fly off will be when it hits the paddle. This value is equivalent to the horizontal speed of the ball. The parameter "pSpeed" determines the vertical speed of the ball.

Two sounds are also asked for. You can use the same sound for the ball hitting the wall or the paddle, or you can come up with two different sounds. Figure 12.3 shows the resulting Parameters dialog box.

Figure 12.3
The ball behavior Parameters dialog box.

on getPropertyDescriptionList me
  list = [:]

  -- direction of initial movement
  addProp list, #pOrigMoveX,[cc]
    [#comment: "Move Horizontal",[cc]
     #format: #integer,[cc]
     #default: 1]
  addProp list, #pOrigMoveY,[cc]
    [#comment: "Move Vertical",[cc]
     #format: #integer,[cc]
     #default: -1]

  -- paddle sprite
  addProp list, #pPaddleSprite,[cc]
    [#comment: "Paddle Sprite",[cc]
     #format: #integer,[cc]
     #default: 5]

  -- how much of an angle will ball fly off at if the
  -- ball hits the edge of the paddle
  addProp list, #pMaxHitSlope,[cc]
    [#comment: "Maximum Hit Slope",[cc]
     #format: #float,[cc]
     #range: [#min: 1.0, #max: 5.0],[cc]
     #default: 3.0]

  -- vertical speed of ball
  addProp list, #pSpeed,[cc]
    [#comment: "Ball Speed",[cc]
     #format: #float,[cc]
     #range: [#min: 1.0, #max: 8.0],[cc]
     #default: 2.0]

  -- hit paddle sound
  addProp list, #pPaddleSound,[cc]
    [#comment: "Hit Paddle Sound",[cc]
     #format: #string,[cc]
     #default: ""]

  -- hit wall sound
  addProp list, #pWallSound,[cc]
    [#comment: "Hit Wall Sound",[cc]
     #format: #string,[cc]
     #default: ""]

  return list
end

The ball sprite starts off by recording the original position of the ball and getting its radius for future reference in other handlers. It then calls "on reset" to set the location and momentum of the ball.


-- record original location, get radius
on beginSprite me
  pOrigLoc = sprite(me.spriteNum).loc
  pRadius = sprite(me.spriteNum).width / 2
  reset(me)
end

-- start momentum and current position
on reset me
  pMoveX = pOrigMoveX
  pMoveY = pOrigMoveY
  pLoc = pOrigLoc
end

The on exitFrame handler changes the position of the ball and then checks to see if any collisions occur. Four handlers are called to determine collisions. Those handlers directly follow the on exitFrame handler in the script.


-- move ball and check for impact
on exitFrame me
  pLoc = pLoc + pSpeed*point(pMoveX,pMoveY)
  checkHitSides(me) -- hit sides of screen
  checkHitPaddle(me) -- hit paddle
  checkMissedBall(me) -- fell through bottom
  sprite(me.spriteNum).loc = pLoc -- set new location
  checkHitBricks(me) -- hit a brick
end

Notice that you can multiply structures like points and rects by regular numbers. The result is the same type of structure, but with each value inside it individually multiplied by the number. So multiplying point(5,9) by 2 results in point(10,18).

The "on checkHitSides" handler determines if the ball has hit the top, left, or right sides of the screen. It does a lot more that just detect it, actually. It also computes how much past the edge the ball went{1} and resets its position to match where the ball should be in real life{2}. It then reverses the momentum in that direction.


-- see if the ball hit the left, right or top of screen
on checkHitSides me
  amountPastTop = 0 - (pLoc.locV - pRadius){1]
  if amountPastTop >= 0 then
    if pWallSound <> "" then puppetSound pWallSound
    pLoc.locV = pLoc.locV + amountPastTop{2}
    pMoveY = -pMoveY
  end if

  amountPastSide = 0 - (pLoc.locH - pRadius){1}
  if amountPastSide >= 0 then
    if pWallSound <> "" then puppetSound pWallSound
    pLoc.locH = pLoc.locH + amountPastSide{2}
    pMoveX = -pMoveX
  end if

  amountPastSide = (pLoc.locH + pRadius) - (the stage).rect.width{1}
  if amountPastSide >= 0 then
    if pWallSound <> "" then puppetSound pWallSound
    pLoc.locH = pLoc.locH - amountPastSide{2}
    pMoveX = -pMoveX
  end if
end

The next handler checks to see if the ball has hit the paddle. It uses the Lingo inside function to determine if contact has been made.

If there is a hit, the vertical momentum of the ball is reversed. The horizontal momentum, however, is thrown away and totally replaced by a new value depending on where on the paddle the ball hit{3}.


-- see if the ball hit the paddle
on checkHitPaddle me
  if inside(pLoc,sprite(pPaddleSprite).rect) then
    amountPastPaddle = (pLoc.locV + pRadius) - sprite(pPaddleSprite).rect.top

    if pPaddleSound <> "" then puppetSound pWallSound

    pLoc.locV = pLoc.locV - amountPastPaddle
    pMoveY = -pMoveY

    paddleWidth = sprite(pPaddleSprite).rect.width
    ballHitSpot = pLoc.locH - sprite(pPaddleSprite).locH

    -- send off in a slope relative to how close to the center
    -- of the paddle the ball hit
    pMoveX = 3.0*ballHitSpot/paddleWidth{3}
  end if
end

If the ball falls below the bottom of the screen, it means that the player missed it with the paddle. The ball is lost and the "on endLife" handler in the frame behavior is called.


-- see if ball is below the bottom of the screen
on checkMissedBall me
  if pLoc.locV > (the stage).rect.height then
    sendSprite(0,#endLife)
  end if
end

The goal of the game is to hit bricks. This next handler checks for that. It sends a "checkHit" message to each brick sprite. If any of these messages return a TRUE, then it means that the brick was hit. The brick takes care of removing itself, but the ball behavior must handle reversing the momentum of the ball.

This handler also keeps track of the case where no sprites at all return a value. If a brick is hit by the ball, it returns a TRUE. If it has not been hit, but is still on the screen, it returns a FALSE. However, if the sprite is no longer on the screen, or the sprite is not even a brick, it returns VOID. If all inquiries return VOID, then the "someBricksRemain" variable is still FALSE when the loop is done, and the level is over.


-- see if the ball hit a brick
on checkHitBricks me
  someBricksRemain = FALSE -- assume all bricks are gone

  repeat with s = 1 to the lastChannel
    check = sendSprite(s,#checkHit,me.spriteNum)
    if check then
      pMoveY = -pMoveY -- bounce
    else if not voidP(check) then
      someBricksRemain = TRUE -- note that at least one brick remains
    end if
  end repeat

  -- if no bricks remain, then level is over
  if not someBricksRemain then
    sendSprite(0,#endLevel)
  end if
end

Brick Behavior

Bricks have their own behavior even though they do not move during the game. Using the parameters of this behavior we can make each brick play a different sound, and have each brick worth a different amount of points.

The behavior is also in charge of checking to see whether the brick is currently in contact with the ball. If so, the brick should disappear.

The on getPropertyDescriptionList handler just needs to find out how many points the brick is worth, and what sound to play when it is hit. Figure 12.4 shows the Parameters dialog box.

Figure 12.4
The Parameters dialog box for the brick behavior.

property pHit -- has brick been hit?
property pSound -- sound to play when hit
property pPoints

on getPropertyDescriptionList me
  list = [:]

  -- how many points is brick worth
  addProp list, #pPoints,[cc]
    [#comment: "Points",[cc]
     #format: #integer,[cc]
     #default: 10]

  -- how many points is brick worth
  addProp list, #pSound,[cc]
    [#comment: "Sound",[cc]
     #format: #string,[cc]
     #default: ""]

  return list
end

-- start off not hit
on beginSprite me
  pHit = FALSE
end

The "on checkHit" handler is called by the ball sprite behavior. If this brick determines that it has been hit, then it returns a TRUE after making itself disappear. If the brick is already gone, it returns a VOID instead of a TRUE or FALSE to notify the ball sprite behavior that it is no longer in play{4}.


-- see if the ball is hitting this brick right now
on checkHit me, ballSprite
  if pHit then return VOID -- already hit, don't check{4}

  if sprite me.spriteNum intersects ballSprite then -- a hit
    sprite(me.spriteNum).loc = point(-100,-100) -- remove brick
    pHit = TRUE -- remember I'm hit
    if pSound <> "" then puppetSound pSound
    sendSprite(0,#addPoints,pPoints) -- increase score
    return TRUE -- I was hit
  else
    return FALSE -- I was not hit
  end if
end

The brick behavior is also in charge of sending the "addPoints" message to the frame behavior so that the score can be increased.

Paddle Behavior

The frame, ball, and brick behaviors take care of everything in the game except for one aspect: the movement of the paddle. This is simply accomplished by setting the horizontal location of the paddle to the horizontal location of the cursor.


-- paddle follows cursor
on exitFrame me
  sprite(me.spriteNum).locH = the mouseH
end