Back to Lesson 13
Forward to Lesson 15

Invadir 14 - Did I Shoot Six Bullets or Only Five?

Now that we have done the preparatory work we can think about multiple bullets. There are several strategies that could work here. For what I have in mind there will have to be specific sprite channels set aside for bullets. Right now we're using channel 2 Let's assume that 21-25 are also used for bullets. Copy the bullet sprite and paste it into those channels.

It might seem a bit messy to have a discontiguous set of bullets. It is messy, but I am going to use this to illustrate another aspect of complexity management. Decent encapsulation does not necessarily concern itself with having everything in a neat row. It's the boundaries that are important, not the data inside. For conceptual tidiness you might want to drop sprite 2 from the list and delete that sprite altogether, although I'm going to proceed as if we don't do this.

One way to implement multiple bullets is to store the list [2, 21, 22, 23, 24, 25,] in both the invaders and the cannon, called something like bullets. This would be a property of each script and would be intitally set ('initialized') in their beginsprite handlers. When it comes to shooting, the most easy way would be something like this:

--CANNON SCRIPT--

property mySprite -- Sprite channel
property bullets -- List of Sprite channels
property stagewidth

on beginsprite me

  set mySprite to the spritenum of me
  set bullets to [2, 21, 22, 23, 24, 25]
  set stagewidth to the width of the rect of the stage

end

on exitFrame me

  -- test that mouse cursor is on stage

  if the mouseH > 0 and the mouseH < stagewidth then

    set the locH of sprite mySprite to the mouseH -- move sprite to mouseH

  end if

  -- shift key sends shoot message to bullet

  if the shiftdown then

    repeat with bullet in bullets

      if not the shooting of sprite bullet then -- n.b. Testing the property of another object.

        shoot sprite bullet, mysprite -- sends own sprite channel as extra parameter
        return -- success, leave, doing nothing else

      end if

    end repeat

    -- if we get this far, no bullets are available right now so...

    return -- leave handler

  end if

end

...although some OO purists might object (no pun intended) to checking the property of an object without using a predefined 'accessor' function. I would simply say that the 'shooting' property of the bullet class has become 'public' because it is intended to be visible to the outside. (Excuses excuses). I will clean this up later.

If you expect a property to be 'public' in this way, it is quite important to be aware of the fact, even specifying it with a comment. Of course all properties and handlers are public in Lingo, but if you want to have a clearer picture of who is doing what, you should be especially careful that data inside one object is never directly modified by another object. Visibility is one thing, having foreign code mess around with a carefully organised internal subsystem is often a recipe for disorder and lengthy debugging cycles. You have been warned.

We can now make equivalent additions to the invader script, and clean up the hardcoded stuff I added at the end of the last lesson:

--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 scorer -- Sprite channel
property points -- how many points the invader is worth
property cannon -- Sprite channel
property bullets -- List of Sprite channels

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 scorer to 20
  set points to 10
  set cannon to 1
  set bullets to [2, 21, 22, 23, 24, 25]
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

    set hdirection to 1

  end if

  if myH > stagewidth then -- hit right edge

    set hdirection to -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

    repeat with bullet in bullets

      if not the shooting of sprite bullet then -- n.b. Testing the property of another object.

        shoot sprite bullet, mysprite -- sends own sprite channel as extra parameter
        return -- success, leave, doing nothing else

      end if

    end repeat

    -- if we get this far, no bullets are available right now so...

    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

Now when you run your movie, you should have bullets galore, plenty for everyone, although the invaders will probably monopolise somewhat. Of course you can even add extra ones later by copying the bullet sprite and pasting it into even more sprite channels. Remember to attend to the bullet lists in each case though.

 

Forward to Lesson 15