Back to Lesson 18
Forward to Lesson 20

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


 

Forward to Lesson 20