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

Making the Game

Only two behaviors are needed for this game. The main one--the frame behavior--controls the game flow, the questions and answers, the player's score, and the timer. The second behavior is the button behavior, which sends messages to the frame behavior and handles key presses.

Frame Behavior

The frame behavior only needs to know which member holds the question and answer data, and which frame to go to when the last question is over. In addition, you can supply two sounds to be used when the user attempts an answer. Figure 13.2 shows the resulting Parameters dialog box.

Figure 13.2
The frame behavior Parameters dialog box.

property pDataMember -- question text
property pQuestionNum -- current question
property pPossiblePoints -- points to add to score
property pScore -- current score
property pCorrectAnswer -- which is current correct answer
property pCorrectSound, pWrongSound -- sounds
property pEndGameFrame -- frame to go to when game is over

on getPropertyDescriptionList me
  list = [:]

  addProp list, #pDataMember,[cc]
    [#comment: "Data Member",[cc]
     #format: #text,[cc]
     #default: VOID]

  addProp list, #pCorrectSound,[cc]
    [#comment: "Correct Sound",[cc]
     #format: #string,[cc]
     #default: ""]
  addProp list, #pWrongSound,[cc]
    [#comment: "Wrong Sound",[cc]
     #format: #string,[cc]
     #default: ""]

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

Notice how you can specify #text as the format for the "pDataMember" parameter. This results in a pop-up menu of text members. You can use other member types as well, such as #bitmap or #sound. If you use #member, a pop-up menu appears that contains all member types.

When the frame starts, the question number is set to 1, the score set to 0, and the first question is asked.


on beginSprite me
  pQuestionNum = 1
  pScore = 0
  showScore(me)
  askQuestion(me)
end

The "on askQuestion" handler takes the text from the member specified and extracts the question, answers, and number of the correct answer from it. It populates the question text member{1} as well as the four answer text members{2}.


on askQuestion me
  -- get the data
  text = pDataMember.text.line[pQuestionNum]

  -- reset all the button sprites
  sendAllSprites(#makeVisible)

  -- get the question, answers and correct answer number
  the itemDelimiter = ";"
  question = text.item[1]
  answers = text.item[2]
  pCorrectAnswer = value(text.item[3])

  -- display the question
  member("Question").text = question{1}

  -- display the answers
  the itemDelimiter = ","
  repeat with i = 1 to 4
    member("Answer"&&i).text = answers.item[i]{2}
  end repeat

  -- start the potential points at 1000
  pPossiblePoints = 1000
  showPossiblePoints(me)
end

The Lingo property the itemDelimiter specifies the character to be used to separate items in a text string. You can set it to any character, such as a semicolon or comma, and then use the item descriptor to extract segments of a string.

The next two handlers are in charge of updating the potential points text member and the score text member.


-- update the possible points display
on showPossiblePoints me
  if pPossiblePoints < 0 then pPossiblePoints = 0
  member("Possible Points").text = "Points:"&&pPossiblePoints
end

-- update the score display
on showScore me
  member("Score").text = "Score:"&&pScore
end

When the user clicks a button, the message is sent back to the frame behavior. The frame behavior determines if the answer is correct.


-- see if an answer is correct
on clickAnswer me, n
  if n = pCorrectAnswer then
    if pCorrectSound <> "" then puppetSound pCorrectSound
    -- add to score
    pScore = pScore + pPossiblePoints
    showScore(me)
    -- move on to next question
    nextQuestion(me)
    return TRUE
  else
    if pWrongSound <> "" then puppetSound pWrongSound
    -- subtract from potential score
    pPossiblePoints = pPossiblePoints - 100
    showPossiblePoints(me)
    return FALSE
  end if
end

When the player gets the right answer, this next handler moves the game on to the next question. If there are no more questions, it moves the game on to the "pEndGameFrame".

To determine the number of elements in a string, use the count property. For instance, "myText.word.count" returns the number of words in "myText". You can also use "myText.line.count," "myText.item.count," and "myText.char.count." The latter example is the same as "myText.length."


-- move on to next question
on nextQuestion me
  pQuestionNum = pQuestionNum + 1
  if pQuestionNum > pDataMember.text.line.count then
    -- no more questions
    go to frame pEndGameFrame
  else
    askQuestion(me)
  end if
end

The on exitFrame handler needs to decrease points from "pPossiblePoints" in addition to looping on the frame.


on exitFrame me
  -- subtract from potential score
  pPossiblePoints = pPossiblePoints - 1
  showPossiblePoints(me)
  go to the frame
end

Because key press messages come to the frame behavior, we need to handle them here. However, the button behaviors will know which button responds to which key. So, we will send the "keyHit" message to all the sprites and let the button behaviors take it from there.


-- take any key presses and send to the button sprites
on keyDown me
  sendAllSprites(#keyHit,the key)
end

Button Behavior

The button behavior needs only to react to the player's input. This can come from either a mouse click or a key press.

This behavior resembles a typical button behavior more than anything else. It records the original member number as the normal state of the button. It assumes that the very next member is the down state of the button. When the user clicks down on the button, the down state is shown. When the user releases the mouse button, the action is taken. If the user moves off the button before releasing the mouse button, then the action is not taken, but instead the button is returned to the normal state.

The behavior starts off by gathering two important pieces of information in the on getPropertyDescriptionList handler. The first bit of information is which answer number the button corresponds to. The second is which key on the keyboard the button maps to. Figure 13.3 shows the Parameters dialog box.

Figure 13.3
The button behavior Parameters dialog box.

property pOrigMemNum -- normal member number
property pPressed -- whether the button is being pressed
property pAnswerNum -- which answer the button corresponds to
property pKey -- which key the button is mapped to

on getPropertyDescriptionList me
  list = [:]

  -- which answer number the button represents
  addProp list, #pAnswerNum,[cc]
    [#comment: "Answer Number",[cc]
     #format: #integer,[cc]
     #default: 1]

  -- which key the button is mapped to
  addProp list, #pKey,[cc]
    [#comment: "Key",[cc]
     #format: #string,[cc]
     #default: ""]

  return list
end

The behavior starts by getting the current member number of the sprite to be used as the normal state of the button. The "pPressed" property is used throughout the behavior to tell whether the button is currently being held down by the player.


-- remember original, normal, member
on beginSprite me
  pOrigMemNum = sprite(me.spriteNum).memberNum
  pPressed = FALSE
end

At the start of a question, the frame behavior asks all the sprites to reset by sending the "makeVisible" message. This resets the buttons to their normal states. This is needed because some buttons may have been removed due to wrong answers by the player.


-- respond to frame behavior's message to reset
on makeVisible me
  sprite(me.spriteNum).memberNum = pOrigMemNum
end

The clicking process starts when the player presses down on the button. The "pPressed" property is set to TRUE and the sprite's member is changed to the down state.


-- user clicks down on button
on mouseDown me
  pPressed = TRUE
  -- change to down state
  sprite(me.spriteNum).memberNum = pOrigMemNum + 1
end

The click process ends if the player lifts up the mouse button while the cursor is over the sprite. If so, the button is returned to the up state{3}, and the answer is sent to the frame behavior to be checked{4}. If the answer is wrong, we remove the button by setting its memberNum to 0.


-- user finished click
on mouseUp me
  if pPressed <> TRUE then exit
  pPressed = FALSE
  -- reset to up state
  sprite(me.spriteNum).memberNum = pOrigMemNum{3}
  -- check to see if the answer was right
  if not sendSprite(0,#clickAnswer,pAnswerNum) then{4}
    -- remove the button
    sprite(me.spriteNum).memberNum = 0
  end if
end

Another way to end a click is to move the mouse off the button before releasing. In this case, we want to give the player the benefit of the doubt and assume that they did not want to click the button in the first place.


-- user did not complete click
on mouseUpOutside me
  pPressed = FALSE
  -- reset button
  sprite(me.spriteNum).memberNum = pOrigMemNum
end

When a key on the keyboard is hit, the message is sent to the frame behavior. In turn, our frame behavior sends a "keyHit" message to all the sprites. When one of these button behaviors gets this message, it compares the key that was pressed to the key that the button is supposed to be mapped to{5}. If they match, the button acts just like it has been clicked.


-- respond to the frame behavior's "on keyHit" message
-- if this button is mapped to that key
on keyHit me, key
  if key = pKey then{5}
    -- check to see if the answer was right
    if not sendSprite(0,#clickAnswer,pAnswerNum) then
      -- remove the button
      sprite(me.spriteNum).memberNum = 0
    end if
  end if
end