Invadir 18 - New Improved Invaders
Things are going well, but the rain of bullets is not very encouraging, especially when the invaders get to steal them all.
We can fix this by getting the invaders to shoot bullets sometimes, but not all the time. Director has a built in function for generating random numbers:
random(N)
This function will return a random integer from 1 to N.
Take a look at the end of the exitframe handler of the invader script:
if myH > cannonLeft and myH < cannonRight then -- directly above cannon
if bullets = [] then
return -- no bullets are available right now so leave handler
end if
set bullet to getAt(bullets,1) -- first available bullet in list
sendsprite bullet, #shoot, mysprite -- sends own sprite channel as extra parameter
return -- leave handler
end if
If we just insert a little test in here, we can get the invaders to shoot only when a random number is of a certain value:
if random(11) < 10 then
return
end if
We would probably want the game to get harder as it progresses so later we will be using the level to dictate how often the invaders shoot. We might as well prepare for this now:
set level to 1 --temporary (hardcoded)--
set N to 10 --temporary (hardcoded)--
if random(N+level) < N then
return
end if
This means that as the the variable level is increased (which will happen every time the player kills all the invaders), the invaders will shoot more often because it will be increasingly likely that the random value is larger than the value N. We'll deal with the level stuff later.
Some purists will have noticed that the invaders are all moving in independent directions. The original space invaders game had the invaders all change directions at the same time. One way to do this is to have it such that whenever any of the invaders hits the sides, it will send a message to itself and all the others to change direction:
on exitFrame me
if not alive then
return -- leave here, do nothing
end if
set myH to the locH of sprite mySprite -- current horizontal pos
if myH < 0 then -- hit left edge
bounceInvadersH me, 1
end if
if myH > stagewidth then -- hit right edge
bounceInvadersH me, -1
end if
This means that we will need the bounceInvadersH handler in the invader script:
on bounceInvadersH me, newHdir
repeat with invader in invaders
sendsprite invader, #bounceH, newHdir
end repeat
end
...which in turn calls a new bounceH handler in all the invaders:
on bounceH me, h set hdirection to h end
Notice the way the parameter is passed from the test in the exitframe handler to the bounceInvadersH handler (newHdir) which broadcasts it to the bounceH handler in all the invader sprites.
It would also be nice to get the invaders to be a bit more menacing, i.e. not just shooting but actually invading. To do this, we'll just increase the vertical position of the invader every time it hits the side:
on bounceH me, h set myV to the locV of sprite mySprite -- current horizontal pos set the locV of sprite mySprite to myV + speed -- move sprite down a bit set hdirection to h end
While we're at it, we'll add a little extra animation so that the invaders change a bit when they hit the sides:
on bounceH me, h
set myV to the locV of sprite mySprite -- current horizontal pos
set the locV of sprite mySprite to myV + speed -- move sprite down a bit
set hdirection to h
if h = 1 then
set the member of sprite mySprite to member "inv1"
else
set the member of sprite mySprite to member "inv2"
end if
end
This illustrates another essential technique of sprite animation with lingo, setting the cast member used by a sprite. Here's an even more elegant way to do it; Rename the cast member "inv2" so that it is called "inv-1" instead and change the above handler to this:
on bounceH me, h
set myV to the locV of sprite mySprite -- current horizontal pos
set the locV of sprite mySprite to myV + speed -- move sprite down a bit
set hdirection to h
set newMemName to ("inv"&h)
set the member of sprite mySprite to member newMemName
end
Sneaky trick huh? When the direction is 1, the cast member is "inv1". When the direction is -1, the cast member is "inv-1". By naming the cast members after the direction they represent, we have eliminated a test from the script.
You can build entire animations on the principle of cast member names. The more adventurous reader might wish to try modifying the earlier versions of the temporary loop script to this effect.
Now you should have invaders which all change direction at the same time AND they should all change their appearance too! Here is the entire invader script:
--INVADER SCRIPT--
property mySprite -- Sprite channel
property hdirection -- either 1 or -1
property speed -- pixels per frame
property stagewidth
property alive -- Boolean (either True or False)
property offstage -- large negative value
property explodeSound -- sound cast member name
property soundChan -- sound channel
property points -- how many points the invader is worth
property invaders -- list of invader sprite channels
property bullets -- list of bullet sprite channels
property cannon -- sprite channel
property scorer -- score sprite channel
on beginsprite me
set mySprite to the spritenum of me
set hdirection to 1
set speed to 8
set stagewidth to the width of the rect of the stage
set alive to true
set explodeSound to "explosion"
set offstage to -999
set soundChan to 1
set points to 10
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 bounceInvadersH me, newHdir
repeat with invader in invaders
sendsprite invader, #bounceH, newHdir
end repeat
end
on bounceH me, h
set myV to the locV of sprite mySprite -- current horizontal pos
set the locV of sprite mySprite to myV + speed -- move sprite down a bit
set hdirection to h
set newMemName to ("inv"&h)
set the member of sprite mySprite to member newMemName
end
on exitFrame me
if not alive then
return -- leave here, do nothing
end if
set myH to the locH of sprite mySprite -- current horizontal pos
if myH < 0 then -- hit left edge
bounceInvadersH me, 1
end if
if myH > stagewidth then -- hit right edge
bounceInvadersH me, -1
end if
set the locH of sprite mySprite to myH + (hdirection * speed) -- move sprite
set cannonLeft to the left of sprite cannon
set cannonRight to the right of sprite cannon
if myH > cannonLeft and myH < cannonRight then -- directly above cannon
set level to 1 --temporary (hardcoded)--
set N to 30 --temporary (hardcoded)--
if random(N+level) < N then
return
end if
if bullets = [] then
return -- no bullets are available right now so leave handler
end if
set bullet to getAt(bullets,1) -- first available bullet in list
sendsprite bullet, #shoot, mysprite -- sends own sprite channel as extra parameter
return -- leave handler
end if
end
on hit me
set alive to false
set the locH of sprite mySprite to offstage
puppetsound soundChan, explodeSound
inc sprite scorer, points
end