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 10 Section 3

Making the Game

This game uses five behaviors. The game state is controlled by the frame behavior. There is a behavior for the invaders and a behavior for the ship. In addition, there is a behavior for the player's bullets and a behavior for the invader bullets.

Ship Behavior

The ship behavior is responsible for controlling the position of the ship. Because it monitors the keyboard to do this, we will also look for a key press that signifies the firing of a bullet.

The on getPropertyDescriptionList handler allows you to set a speed for the player's ship when attaching the behavior to a sprite. It also sets the fire sound. The "pFireDelay" parameter is the number of ticks that the player must wait before another shot is allowed. The smaller this number, the more quickly the shots will appear if the player holds down the spacebar. Figure 10.2 shows the Parameters dialog box for this behavior.

Figure 10.2
Set the speed for the ship behavior in the Parameters dialog box.

property pSpeed
property pFireSound
property pFireDelay
property pLastFireTime

on getPropertyDescriptionList me
  list = [:]

  -- how many pixels the ship moves at a time
  addProp list, #pSpeed,[cc]
    [#comment: "Speed",[cc]
     #format: #integer,[cc]
     #default: 5]

  -- sound to use when firing a bullet
  addProp list, #pFireSound,[cc]
    [#comment: "Fire Sound",[cc]
     #format: #string,[cc]
     #default: ""]

  -- how many ticks to wait before allowing another bullet
  addProp list, #pFireDelay,[cc]
    [#comment: "Delay Between Shots (ticks)",[cc]
     #format: #integer,[cc]
     #default: 30]

  return list
end

The "pLastFireTime" property is set in the on beginSprite handler. This property is used to determine if it's too soon to fire another shot. When the user fires a shot, the "pLastFireTime" property is set to the current time, but for now, it is set to 0.


on beginSprite me
  pLastFireTime = 0
end

The on exitFrame handler mainly looks for keys that are pressed. If the left- or right arrow keys are down, it moves the ship in the appropriate direction.

If the spacebar is down, the on exitFrame handler checks to make sure that it has been long enough since the last shot{1}, and then sends a message for another shot to be fired.

The keyPressed function is an alternative to using on keyDown to detect key presses. Instead of responding to an event like on keyDown, the keyPressed function looks at the current state of the keyboard and determines if a specific key is being held down. It is ideal for arcade games because you can detect multiple keys and determine if they are being held down rather than just detecting the initial key press.


on exitFrame me
  -- left arrow
  if keyPressed(123) then
    x = sprite(me.spriteNum).locH
    x = x - pSpeed
    if x < 20 then x = 20
    sprite(me.spriteNum).locH = x
  end if

  -- right arrow
  if keyPressed(124) then
    x = sprite(me.spriteNum).locH
    x = x + pSpeed
    if x > (the stage).rect.width-20 then x = (the stage).rect.width-20
    sprite(me.spriteNum).locH = x
  end if

  -- check spacebar, plus check to make sure did not fire last frame
  if keyPressed(SPACE) then
    -- space, fire
    if the ticks < pLastFireTime + pFireDelay then exit{1}
    if pFireSound <> "" then puppetSound 1, pFireSound
    sendSprite(6, #fire, sprite(me.spriteNum).loc)
    pLastFireTime = the ticks
  end if
end

Invader Behavior

The main purpose of the invader behavior is to keep the invaders moving. The movement is from left to right, then right to left, then left to right again, and so on. Each time the invaders change direction, there must be a small vertical drop as well.

The behavior keeps track of the current horizontal direction with the property "pDirection". It starts off as 1, and changes to -1 when the direction is reversed. The change in position for the sprite is "pSpeed" multiplied by "pDirection".

The property "pSpeed" is set through the on getPropertyDescriptionList handler. You can make it faster on harder levels. You should set the speed of every invader on the screen to the same amount, or you will have chaos on the screen. Figure 10.3 shows the Parameters dialog box for the invader behavior.

Figure 10.3
Set the speed for the invader behavior in the Parameters dialog box.

property pSpeed
property pDirection
property pMemNum
property pHit
property pOrigLoc

on getPropertyDescriptionList me
  list = [:]

  addProp list, #pSpeed,[cc]
    [#comment: "Speed",[cc]
     #format: #integer,[cc]
     #default: 2]

  return list
end

When the sprite begins, it remembers the original location in the "pOrigLoc" property. This will be used to reset the sprite should the level need to start over again.

The "pMemNum" property is set to the original member number as well, and it is used to create the two-member animation. The "pHit" property records whether the sprite is still alive.


on beginSprite me
  pOrigLoc = sprite(me.spriteNum).loc
  pMemNum = sprite(me.spriteNum).memberNum
  pDirection = 1 -- start moving to right
  pHit = FALSE
end

When the user dies, the level should start over again. The "on reset" handler is called for all sprites. This handler resets the sprite's location, the "pHit" property, and the "pDirection" property.


on reset me
  pDirection = 1 -- start moving to right
  pHit = FALSE
  sprite(me.spriteNum).loc = pOrigLoc
end

All the action takes place in the on exitFrame handler. It first checks to see if the sprite is already out of action, and does nothing if it is{2}.

Otherwise, it moves the sprite one step horizontally. It checks to see if the sprite is now up against a side of the screen{3}. If so, it sends a message to the frame behavior.

This handler also swaps the member to create the simple animation{4}. The member is either the original member used by the sprite, or it is the very next member in the Cast.

Finally, based on a 1 in 300 chance, the invader fires a bullet{5}. This is passed on to the first invader bullet sprite's behavior. In this case, the sprite is number 55. If you change the location of this sprite, make sure you change this number as well.


on exitFrame me
  if pHit then
    exit{2}
  else
    -- move
    x = sprite(me.spriteNum).locH
    x = x + pDirection*pSpeed
    sprite(me.spriteNum).locH  = x
    -- hit a wall?
    if pDirection > 0 and x > (the stage).rect.width-20 then{3}
      sendSprite(0,#hitWall)
    else if pDirection < 0 and x < 20 then{3}
      sendSprite(0,#hitWall)
    end if
    -- toggle to other member to create animation
    if sprite(me.spriteNum).memberNum = pMemNum then{4}
      sprite(me.spriteNum).memberNum = pMemNum + 1
    else
      sprite(me.spriteNum).memberNum = pMemNum
    end if
    -- fire 1 out of 300 times
    if random(300) = 1 then{5}
      sendSprite(55, #fire, sprite(me.spriteNum).loc)
    end if
  end if
end

If any sprite has signaled the frame behavior that it has hit the side of the screen, then the frame behavior notes it and signals back to all invaders later on in the code to change direction. The following handler takes care of that message, dropping the sprite down a bit as well.


on changeDirection me
  -- move down
  y = sprite(me.spriteNum).locV
  y = y + pSpeed
  sprite(me.spriteNum).locV = y
  -- hit bottom?
  if y > sprite(5).rect.top then sendSprite(0,#hitBottom)
  -- reverse direction
  pDirection = -pDirection
end

If a player's bullet intersects an invader, the invader sprite is notified through a call to the "on hit" handler. This handler displays the "Invader Hit" member for a moment, and then removes the invader member altogether. The "on hit" handler also sets "pHit" to TRUE, so this sprite will do nothing for the rest of the level.


-- called by bullet behavior to notify of a hit
on hit me
  sprite(me.spriteNum).member = member("Invader Hit") -- change to hit graphic
  updateStage  -- show now, since sprite will disappear next frame
  sprite(me.spriteNum).memberNum = 0
  pHit = TRUE
end

The last handler in this behavior is the "on invaderAlive" handler. This is called by the frame behavior every time an invader dies. The frame behavior is searching for an invader that is still alive and kicking. If it finds one, then it knows that the level is not yet over.


-- called by frame behavior to see if any invaders remain
on invaderAlive me
  return not pHit
end

Frame Behavior

The frame behavior keeps track of things that go beyond a single object. For instance, because all the invaders should change direction in unison, this event gets channeled through the frame behavior. The frame behavior also checks for and handles the case where the player dies, or the invaders land. The frame behavior is responsible for keeping track of the score as well. Figure 10.4 shows the Parameters dialog box that results from the on getPropertyDescriptionList handler.

Figure 10.4
The Parameters dialog box for the frame behavior.

property pHitSound
property pDieSound
property pEndLevelFrame
property pEndGameFrame

property pScore
property pLives
property pInvadersHitWall
property pInvadersHitBottom

on getPropertyDescriptionList me
  list = [:]

  addProp list, #pHitSound,[cc]
    [#comment: "Hit Sound",[cc]
     #format: #string,[cc]
     #default: ""]

  addProp list, #pDieSound,[cc]
    [#comment: "Die Sound",[cc]
     #format: #string,[cc]
     #default: ""]

  addProp list, #pEndLevelFrame,[cc]
    [#comment: "End Level Frame",[cc]
     #format: #marker,[cc]
     #default: #next]

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

  return list
end

The on beginSprite handler initializes the number of lives remaining and the score.


on beginSprite me
  pLives = 3
  pScore = 0
  showScore(me)
end

When any invader gets close to the side of the screen, it sends the "hitWall" message to the frame behavior. This is noted by setting the "pInvadersHitWall" property to TRUE. This property is then used in the on enterFrame handler.


on hitWall me
  pInvadersHitWall = TRUE
end

The same idea is used when the invaders hit the bottom of the screen. In this case, the "pInvadersHitBottom" property is set.


on hitBottom me
  pInvadersHitBottom = TRUE
end

The on enterFrame handler is called just after the screen is drawn for the next frame. This handler is used to check for the case in which the invaders need to change direction, or have hit the bottom of the screen. In the first case, all the invaders are sent the message to change direction. In the second case, the game ends.

The on enterFrame handler is called in every behavior just after the frame is drawn. It will thus be called for every behavior before the first on exitFrame handler is called of any behavior. This means that we can use it to execute code that needs to happen before all on exitFrame handlers are executed. In this case, we use it to change direction of all in invaders before any of them have the chance to move.


on enterFrame me
  -- see if an invader hit a wall
  if pInvadersHitWall then
    sendAllSprites(#changeDirection)
  end if

  -- an invader hit the bottom
  if pInvadersHitBottom then
    go to frame pEndGameFrame
  end if

  -- reset wall hit flag for this frame
  pInvadersHitWall = FALSE
end

When an invader is hit, the "on addScore" handler is called to add to the score, update the text on the screen, and check to see if the level is over.


-- message sent when an invader is hit
on addScore me
  if pHitSound <> "" then puppetSound 2, pHitSound
  pScore = pScore + 1
  showScore(me)
  checkEndLevel(me)
end

The level is over if all the invaders are dead. This next handler loops through all the sprites and sends the "invaderAlive" message to each. If it ever gets back a TRUE as an answer, it knows that an invader is alive.

Note that this is a very time-consuming process that only works in this case because we are not doing it very often. The only time the handler is called is when an invader dies; it's not called every single frame.


on checkEndLevel me
  -- loop through all sprites
  repeat with i = 1 to the lastChannel
    -- if one is still alive, look no further
    if sendSprite(i,#invaderAlive) then exit
  end repeat
  -- not invaders left, level is over
  go to frame pEndLevelFrame
end

The "on showScore" handler places the score and the number of lives left in the text member on the screen. the "on showScore" handler is only called when either the score or the number of lives left change.


on showScore me
  text = ""
  put "Score:"&&pScore&RETURN after text
  put "Lives Left:"&&pLives after text
  member("Score").text = text
end

When the player's ship is hit, the "on shipHit" handler performs a series of actions. First, it displays a new graphic in place of the ship. In this example movie, we just use the same graphic that is shown for the exploding invaders.

Next, the number of lives remaining is changed. If the number of lives remaining is now less than 0, then the game ends. Otherwise, all the sprites are sent the "reset" message.

When the player dies, there is also a call to the "on freeze" handler. This handler performs a loop for two seconds, thus freezing the screen and the computer for that time.

This is not a good method for creating a pause. However, it is a simple method, and is used here to try to keep from complicating the game code further.

If I were making this game for my site, I would probably not use "on freeze", but instead have the movie go to another frame that displays a "Press Any Key to Try Again" button.


-- message sent when the player is hit
on shipHit me
  sprite(5).member = member("Invader Hit") -- ship explodes
  updateStage
  sprite(5).member = member("Ship")
  pLives = pLives - 1
  if pDieSound <> "" then puppetSound 2, pDieSound
  if pLives < 0 then
    go to frame pEndGameFrame
  else
    sendAllSprites(#reset)
    showScore(me)
    freeze(me)
  end if
end

-- pause all operation for 2 seconds
on freeze me
  freezeTime = the ticks + 120
  repeat while the ticks < freezeTime
  end repeat
end

Finally, the frame behavior has the responsibility of keeping the frame looping.


on exitFrame
  go to the frame
end

Bullet Behavior

As previously mentioned in this chapter, the bullet sprites exist as a bank of sprites. The first sprite in this bank is always the one that is asked to play the role of the latest bullet being fired. If it's busy already, it passes the message down the line until a sprite that isn't doing anything can act as the new bullet.

The following behavior is placed on all the sprites in this bank. The behavior starts with an on getPropertyDescriptionList handler that lets the author define the speed of the bullet. Figure 10.5 shows the Parameters dialog box that results. There is also an on reset handler that resets the bullet in the case where a player dies.

Figure 10.5
The Parameters dialog box is shown here for the bullet behavior

property pSpeed
property pMoving

on getPropertyDescriptionList me
  list = [:]

  addProp list, #pSpeed,[cc]
    [#comment: "Speed",[cc]
     #format: #integer,[cc]
     #default: 16]

  return list
end

on beginSprite me
  reset(me)
end

on reset me
  sprite(me.spriteNum).locV = -100
  pMoving = FALSE
end

The "on fire" handler is called when the player presses the fire key. It first checks the "pMoving" property to see if the sprite is already firing. If it is, the "on fire" handler passes the message to the next sprite. If not, the sprite positions itself to where it is told, and the "pMoving" property is set to TRUE.


on fire me, loc
  -- got signaled to fire
  if pMoving then -- busy, send to next sprite
    sendSprite(sprite(me.spriteNum+1),#fire,loc)
  else
    sprite(me.spriteNum).loc = loc
    pMoving = TRUE
  end if
end

The on exitFrame handler checks to see if the "pMoving" property indicates that the sprite is in use. If it is, then it moves vertically up, according to "pSpeed"{6}. A call to "on didIHit" checks to see if the bullet hit anything.


on exitFrame me
  if pMoving then
    -- move bullet up
    y = sprite(me.spriteNum).locV
    y = y Ð pSpeed{6}
    sprite(me.spriteNum).locV = y
    if y < 0 then -- reached top of screen
      pMoving = FALSE
    else
      didIHit(me) -- check for hit
    end if
  end if
end

The "on didIHit" handler loops through the invader sprites to see if any intersect the bullet. The first and last invader sprite is hard coded here as 30 and 53{7}, so be sure to adjust these if you change your Score.


on didIHit me
  repeat with i = 30 to 53{7} -- loop through invader sprites
    if sprite i intersects me.spriteNum then -- see if it hit
      sendSprite(sprite i, #hit)
      -- get rid of bullet
      sprite(me.spriteNum).locV = -100
      pMoving = FALSE
      sendSprite(0,#addScore)
    end if
  end repeat
end

Invader Bullet Behavior

The invader bullets behave in an opposite way from the player's bullets. They move down, instead of up. They also look for an intersection with the player's ship, rather than with an invader sprite. Otherwise, you will recognize most of the code from the previous behavior.


property pSpeed
property pMoving

on getPropertyDescriptionList me
  list = [:]

  addProp list, #pSpeed,[cc]
    [#comment: "Speed",[cc]
     #format: #integer,[cc]
     #default: 16]

  return list
end

on beginSprite me
  reset(me)
end

on reset me
  sprite(me.spriteNum).locV = -100
  pMoving = FALSE
end

on fire me, loc
  if pMoving then -- busy, send to next sprite
    sendSprite(sprite(me.spriteNum+1),#fire,loc)
  else
    -- set loc, mode
    sprite(me.spriteNum).loc = loc
    pMoving = TRUE
  end if
end

on exitFrame me
  if pMoving then
    -- move down
    y = sprite(me.spriteNum).locV
    y = y + pSpeed
    sprite(me.spriteNum).locV = y
    if y > (the stage).rect.height then -- hit bottom
      pMoving = FALSE
    else
      didIHit(me) -- hit gun?
    end if
  end if
end

on didIHit me
  if sprite 5 intersects me.spriteNum then -- hit gun?
    sendSprite(0,#shipHit)
  end if
end