Back to Lesson 19
Forward to Lesson 21

Invadir 20 - Lives and Levels

Right, now we're in a position to add lives and levels to the game. You'll need to make a seperate field cast member for each of these, put them on an appropriate place on the screen. arrange the lives field sprite from frame 2 to 4 in channel 18, the levels sprite from frame 2 to 5 in channel 19. Make sure they have the same number display script as the score field and then proceed:

Adjust the number display script like this:

-- NUMBER DISPLAY SCRIPT --
property displayField -- field cast member reference
property val -- the displayed value
property invaders -- list of invader sprite channels
property bullets -- list of bullet sprite channels
property cannon -- sprite channel
property scorer -- score sprite channel
property startVal

on beginsprite me
  
  set displayField to the member of sprite the spritenum of me
  reset me
  
end

on storeShared me, gameSprites
  
  set invaders to the invaders of gameSprites
  set bullets to the bullets of gameSprites
  set cannon to the cannon of gameSprites
  set scorer to the scorer of gameSprites
  
end

on reset me
  
  set val to startVal
  update me
  
end

on update me
  
  put val into field displayField
  
end

on inc me, amount
  
  set val to val + amount
  update me
  
end

on setVal me, v
  
  set val to v
  update me
  
end

on getVal me, v
  
  return val
  
end

on getPropertyDescriptionList me
  
  return [#startVal:[#comment:"Start value", #format:#integer, #default:0]]
  
end

Here again we use the getPropertyDescriptionList handler so that each number display field can have its own starting value. You will have to open the property dialog box for each one. The score should start at 0, the lives should be set at at least 3, the level should start at 1.

It is quite common to provide handlers which get and set values of an otherwise private property. They allow external objects to get or set an internal value in a polite way, i.e. by asking for it. We could have referred to the val of sprite S, but there is less room for error if the object gets a chance to see who is accessing its internal data. If we wanted to be really safe, we could make additional checks in the setVal handler, for example to ensure that the parameter is a number and not some less suitable datatype. This is a very common practice. Try typing

beep "now"

...in the message window (and press return) and you will see a manifestation of exactly this kind of internal check. (Director complains with a "integer expected" message because the parameter was of the wrong type.)

 

Now, back to the job in hand. Change the connector script as follows

-- connector sprite script --
on beginsprite me
  
  set mysprite to the spritenum of me
  set invs to [3, 4, 5, 6, 7, 8, 9, 10]
  set buls to [2, 21, 22,23, 24, 25]
  set can to 1
  set sc to 20
  set liv to 18
  set lev to 19
  sendallsprites #storeShared, [#invaders:invs,#bullets:buls, #cannon:can, #scorer:sc, #livesSprite:liv, #levelSprite:lev]  
end

This will send the additional information about the lives and the levels to all the sprites at the beginning of the game. (Only the bullet sprite really needs to use this extra information). It also sends messages to the number display sprites for lives and levels to set their initial values to 3 and 1 respectively.

In the bullet script, add the following properties

property livesSprite 
property levelSprite

...and modify the storeShared handler as follows:

on storeShared me, gameSprites
  
  set invaders to the invaders of gameSprites
  set bullets to the bullets of gameSprites
  set cannon to the cannon of gameSprites
  set scorer to the scorer of gameSprites
  set livesSprite to the livesSprite of gameSprites
  set levelSprite to the levelSprite of gameSprites
end

These new lines take the information about the lives and level sprites and store their channels in internal properties.

Then inside the exitframe handler, make the following adjustments:

      if target = cannon then
        
        -- End of life
        sendsprite livesSprite, #inc, -1
        go to frame "Dead"
        
      else
        
        deleteOne invaders, target -- removes invader from shared list
        
        if invaders = [] then
          
          -- End of level
          sendsprite levelSprite, #inc, 1
          go to frame "Next Level"
          
        end if
        
      end if

Now when you run the movie, you will find that your lives will decrease every time you get hit, and the level will increase every time you kill all the invaders.

If the player gets killed several times, she should run out of lives. Currently we have no way to jump to the Game over screen and the lives just keep on being reduced by one leaving to negative values for the lives.

To deal with this, I would suggest that we make a test after losing a life to find out whether there are any lives left. This is why we need the getVal function in the number display script If there are none, we will want to change the destination of the temporary looper so that instead of jumping back into the game, it jumps to the Game over frame. Here again, if we want to keep things tidy, we will need to add an accessor handler for the destination property in the temporary loop script:

on changeDestination me, newDestination
  set destination to newDestination
end

Now back to the exitframe handler of the bullet script.

      if target = cannon then
        
        -- End of life
        go to frame "Dead"
        if sendsprite(livesSprite, #getVal) = 0 then
          sendsprite -5, #changeDestination, "Game Over"
        else
          sendsprite livesSprite, #inc, -1
        end if
        
      else

This last addition illustrate three ways in which sendsprite can be used. In reverse order;

sending a message to a sprite in the normal way with sendsprite livesSprite, #inc, -1

sending a message to the framescript by addressing it to sprite channel -5 sendsprite -5, changeDestination, "Game Over"
Although the framescript does not contain a sprite, it does have a spritenum property, the value of which is -5. This is very convenient.

Using a sendsprite call as a function call. sendsprite (livesSprite, #getVal)
This is just like any other function call. The expression stands in for the value returned by the function.

Now when the player loses all her lives, the playback head will jump to the Game Over frame. Now finally we have the game working properly. Everybody can get killed, lives, levels and score are incremented and decremented properly, all that remains now is to add some polish.

 

Forward to Lesson 21