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.