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

Making the Game

As mentioned before, all the game code is contained in one long frame behavior. This makes it very easy to implement this game with just the one behavior and a collection of bitmaps.

The property declarations have useful descriptions after them, with the exception of the parameters that are defined in the on getPropertyDescriptionList handler.


-- Parameters
property pFirstSprite, pLastSprite
property pGloveSprite
property pSpeed
property pDropFrequency
property pCatchRequirement, pCatchDistance
property pScoreIncrement
property pEndGameCondition, pEndGameNumber
property pCatchBadPenalty
property pDropSound, pCatchSound
property pMissSound, pBadCatchSound
property pEndGameFrame

-- Other Properties
property  pScore -- Player Score
property pUsingSprites -- Sprites currently in the action
property pNumberMissed -- Number of good objects missed
property pNumberBadCaught -- Number of bad objects caught
property pNextDropTime -- next time an object is scheduled to fall

This behavior contains the longest on getPropertyDescriptionList handler that we have seen so far in this book. Thus, this game is highly customizable. I've scattered some additional comments inside the handler text that follows. Figure 8.2 shows the resulting Parameters dialog box.

Figure 8.2
The Parameters dialog box for the falling objects game has quite a few customizable parameters.

on getPropertyDescriptionList me
  list = [:]

  -- First sprite with object
  addProp list, #pFirstSprite,[cc]
    [#comment: "First Sprite",[cc]
     #format: #integer,[cc]
     #default: 11]

  -- last sprite with object
  addProp list, #pLastSprite,[cc]
    [#comment: "Last Sprite",[cc]
     #format: #integer, #default: 20]

The last two parameters tell the behavior where the sprites in the score are located that will be used as the falling objects. You can put in as few or as many of these as you want. The game will not try to assign more falling objects at one time than there are sprites. So, if you want to limit the number of items that can be falling at once to eight, only have eight sprites available.


  -- sprite that catches objects
  addProp list, #pGloveSprite,[cc]
    [#comment: "Glove Sprite",[cc]
     #format: #integer,[cc]
     #default: 5]

  -- starting speed of fall
  addProp list, #pSpeed,[cc]
    [#comment: "Speed",[cc]
     #format: #integer,[cc]
     #default: 5]

The "pSpeed" property sets the number of pixels that each falling object moves for each frame. Another important factor in the speed of the game is the Score tempo. Make sure you set the Score tempo to something like 15 or 30 frames per second before playing around with the "pSpeed" parameter. Try to keep the tempo constant as you adjust "pSpeed".

In most games, the speed of the game is determined by both the behavior parameters, like "pSpeed" and the Score's tempo. On fast machines, you can use a low parameter value, and a high tempo to make a smooth game. However, slower machines may not be able to keep up with the high tempo of the movie. If you want your game to work fast on slower machines, use a high behavior parameter such as "pSpeed", and a low tempo. Experiment with different values on both a low-speed test machine and your high-speed development computer.


  -- how often to drop objects
  addProp list, #pDropFrequency,[cc]
    [#comment: "Drop Frequency",[cc]
     #format: #integer,[cc]
     #default: 60]

This property can be confusing because a lower number means that objects fall more often. More precisely, it is the number of ticks that must pass before another object falls.


  -- test to be performed to determine catch
  addProp list, #pCatchRequirement,[cc]
    [#comment: "Catch Requirement",[cc]
     #format: #string,[cc]
     #range: ["Rects Touch", "Loc Inside Rect",[cc]
              "Rect Touches Loc", "Square Distance",[cc]
              "Circular Distance"],[cc]
     #default: "Loc Inside Rect"]

The "pCatchRequirement" parameter sometimes needs an accompanying number value to define it. This number is used in case either of the distance methods are chosen. The "pCatchDistance" parameter is used for that. If neither of the distance methods are used, the "pCatchDistance" parameter is ignored.


  -- distance measurement for catch test
  addProp list, #pCatchDistance,[cc]
    [#comment: "Catch Distance (If Needed)",[cc]
     #format: #integer,[cc]
     #default: 20]

The next parameter determines how many points are awarded for each correct catch, as well as how many points should be subtracted for an incorrect catch.


  -- how many points to be awarded for each catch
  addProp list, #pScoreIncrement,[cc]
    [#comment: "Score Increment",[cc]
     #format: #integer,[cc]
     #default: 10]

Like the "pCatchRequirement", the "pEndGameCondition" needs a number value to fully define the end game condition. In the case of the first two choices, this number is used to determine how many objects must be missed, or caught, to end the game. In the case of the "Obtain Certain Score" choice, the number is used as the score that must be obtained.


  -- how does the game end
  addProp list, #pEndGameCondition,[cc]
    [#comment: "End Game Condition",[cc]
     #format: #string,[cc]
     #range: ["Miss Number of Good Objects",[cc]
              "Catch Number of Bad Objects",[cc]
              "Obtain Certain Score"],[cc]
     #default: "Miss Number of Good Objects"]

-- number associated with the end game condition
  addProp list, #pEndGameNumber,[cc]
    [#comment: "End Game Number",[cc]
     #format: #integer,[cc]
     #default: 3]

The next parameter allows you to specify if you want points to be subtracted when the player catches the wrong object.


  -- what happens when a bad item is caught
  addProp list, #pCatchBadPenalty,[cc]
    [#comment: "Catch Bad Item Penalty",[cc]
     #format: #string,[cc]
     #range: ["Nothing",[cc]
              "Lose Points"],[cc]
     #default: "Nothing"]

The next four parameters are the sounds to be used in the game. If you leave them blank, no sound is played at all.


  -- sound to play when item drops
  addProp list, #pDropSound,[cc]
    [#comment: "Drop Sound",[cc]
     #format: #string,[cc]
     #default: ""]
  -- sound to play when item is caught
  addProp list, #pCatchSound,[cc]
    [#comment: "Catch Sound",[cc]
     #format: #string,[cc]
     #default: ""]
  -- sound to play when item is missed
  addProp list, #pMissSound,[cc]
    [#comment: "Miss Sound",[cc]
     #format: #string,[cc]
     #default: ""]
  -- sound to play when bad item is caught
  addProp list, #pBadCatchSound,[cc]
    [#comment: "Bad Catch Sound",[cc]
     #format: #string,[cc]
     #default: ""]

The last parameter is the frame to which the movie advances when the game is over.


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

  return list
end

The on beginSprite handler resets all the score-like properties, clear the pUsingSprites list and set the value of the score text on the screen.


-- reset score and sprite list
on beginSprite me
  pScore = 0
  pNumberMissed = 0
  pNumberBadCaught = 0
  pNextDropTime = the ticks
  showScore(me)
  pUsingSprites = []
end

All action in the game takes place in the on exitFrame handler. First, the glove sprite is aligned to match the current cursor position. Then, each falling object is moved down a bit. Next, all the objects are checked to see if any have been caught. Finally, the time is checked to see if another object should drop.


-- regularly timed events
on exitFrame me
  -- match glove sprite to cursor
  sprite(pGloveSprite).locH = the mouseH
  -- move objects down
  letObjectsFall(me)
  -- see if an object has been caught
  checkCaught(me)
  -- see if another item can be dropped
  if the ticks > pNextDropTime then
    dropObject(me)
    pNextDropTime = the ticks + pDropFrequency
  end if
  -- loop on the frame
  go to the frame
end

The "on letObjectsFall" handler loops through the sprites in use and moves them down according to "pSpeed"{1}. If the sprite has passed out of view on the bottom of the screen, then the sprite is recycled by removing it from the "pUsingSprites" list{2}.

A check is also made to see if the object is a "good" object. If so, then it was missed by the player and gets added to the "pNumberMissed" property{3}. If the game is set to end when the player misses a certain number of good objects, then the "on checkEndGame" handler is called to end the game.


-- move all existing objects down
on letObjectsFall me
  repeat with i = pUsingSprites.count down to 1
    -- get sprite number
    s = pUsingSprites[i]
    -- get vertical location
    y = sprite(s).locV
    -- increase vertical location
    y = y + pSpeed{1}
    sprite(s).locV = y
    if y > (the stage).rect.height + sprite(s).rect.height/2 then
      -- sprite is beyond bottom of screen, remove object
      if sprite(s).member.name = "Good" then
        if pMissSound <> "" then puppetSound pMissSound
        pNumberMissed = pNumberMissed + 1{3}
        showScore(me)
        checkEndGame(me)
      end if
      deleteAt pUsingSprites, i{2}
    end if
  end repeat
end

When it is determined that it's time to drop a new object, the "on dropObject" handler is called. This handler first loops through the sprite range to find a sprite not being used{4}. Then, it assigns it a random member from the cast library of objects. It also picks a random horizontal location and a vertical location that puts the object just above the top of the screen.


-- create a new falling object
on dropObject me
  -- loop through sprites
  repeat with s = pFirstSprite to pLastSprite
    -- see if it is being used
    if not getOne(pUsingSprites,s) then{4}
      -- pick a random member
      r = random(the number of members of castLib "Objects")
      mem = member(r,"Objects")
      -- random horizontal location that still
      -- has entire member on the screen
      screenWidth = (the stage).rect.width
      memberWidth = mem.rect.width
      x = random(screenWidth-memberWidth)+memberWidth/2
      -- set vertical loc to just above screen
      memberHeight = mem.rect.height
      sprite(s).loc = point(x,-memberHeight/2)
      -- set member
      sprite(s).member = mem
      -- add to list
      add pUsingSprites, s
      -- no need to look further
      if pDropSound <> "" then puppetSound pDropSound
      exit repeat
    end if
  end repeat
end

The "on checkCaught" handler checks each sprite that is in use and determines if it has been caught by the glove. It uses one of the five methods to determine this, depending on the "pCatchRequirement" parameter.

If a catch has been made, the "on checkCaught" handler must deal with it being either a good object or a bad one. In the latter case, the "pNumberBadCaught" must be incremented{5}. If it's a good catch, "on addScore" is called{6}. Either way, the "on checkEndGame" handler must be called.


-- check to see if any objects were caught
on checkCaught me
  repeat with i = pUsingSprites.count down to 1
    s = pUsingSprites[i]
    if pCatchRequirement = "Rects Touch" then
      if sprite s intersects pGloveSprite then
        catch = TRUE
      end if
    else if pCatchRequirement = "Loc Inside Rect" then
      if inside(sprite(s).loc, sprite(pGloveSprite).rect) then
        catch = TRUE
      end if
    else if pCatchRequirement = "Rect Touches Loc" then
      if inside(sprite(pGloveSprite).loc, sprite(s).rect) then
        catch = TRUE
      end if
    else if pCatchRequirement = "Square Distance" then
      if (sprite(s).locH - sprite(pGloveSprite).locH) <= pCatchDistance and[cc]
         (sprite(s).locV - sprite(pGloveSprite).locV) <= pCatchDistance then
        catch = TRUE
      end if
    else if pCatchRequirement = "Circular Distance" then
      if sqrt(power(sprite(s).locH - sprite(pGloveSprite).locH,2)+[cc]
              power(sprite(s).locV - sprite(pGloveSprite).locV,2))[cc]
         <= pCatchDistance then
        catch = TRUE
      end if
    end if
    if catch then
      sprite(s).locV = -30
      deleteOne pUsingSprites, s
      if sprite(s).member.name = "Good" then
        if pCatchSound <> "" then puppetSound pCatchSound
        addScore(me){6}
      else
        if pBadCatchSound <> "" then puppetSound pBadCatchSound
        if pCatchBadPenalty = "Lose Points" then
          subtractScore(me)
        end if
        pNumberBadCaught = pNumberBadCaught + 1{5}
        showScore(me)
      end if
      checkEndGame(me)
    end if
  end repeat
end

If a catch has been made, the score increases, and the drop frequency decreases.


-- add to the score and increase drop frequency
on addScore me
  pScore = pScore + pScoreIncrement
  pDropFrequency = pDropFrequency - 1
  showScore(me)
end

If a bad object has been caught, and the "pBadCatchPenalty" is set to "Lose Points", then the "on subtractScore" handler is used to decrease the score. Care is given not to allow the score to fall below zero. If you prefer not to take pity upon bad players, you can remove that line and let the score fall into negative values.


-- subtract points
on subtractScore me
  pScore = pScore - pScoreIncrement
  if pScore < 0 then
    -- donŐt allow score to go under 0
    pScore = 0
  end if
  showScore(me)
end

The "on showScore" handler works as it did for earlier games, but has an addition here. If the "pEndGameCondition" is set to "Miss Number of Good Objects", then the "pNumberMissed" is shown as well as the score. If the "pEndGameCondition" is set to "Catch Number of Bad Objects", then the "pNumberBadCaught" is shown. Either helps the user determine how close he or she is to the end of the game, thus adding a little tension to the gameplay.


-- update the onscreen text member
on showScore me
  text = "Score:"&&pScore&RETURN
  -- add second line depending on the type of game
  if pEndGameCondition = "Miss Number of Good Objects" then
    put "Objects Missed:"&&pNumberMissed after text
  else if pEndGameCondition = "Catch Number of Bad Objects" then
    put "Bad Objects:"&&pNumberBadCaught after text
  end if
  member("score").text = text
end

The "on checkEndGame" looks at each of the three possible ways the game can end, and goes to a new frame if any are true. You might want to make it so the game can end on a combination of the end game conditions, rather than just one. A little modification of the code could have this handler ignore the "pEndGameCondition" property and instead just check for all the conditions.


-- see if the necessary game over condition is met
on checkEndGame me
  if pEndGameCondition = "Miss Number of Good Objects" then
    if pNumberMissed >= pEndGameNumber then
      go to frame pEndGameFrame
    end if
  else if pEndGameCondition = "Catch Number of Bad Objects" then
    if pNumberBadCaught >= pEndGameNumber then
      go to frame pEndGameFrame
    end if
  else if pEndGameCondition = "Obtain Certain Score" then
    if pScore >= pEndGameNumber then
      go to frame pEndGameFrame
    end if
  end if
end