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.