If nothing else, you have to admire me for getting over 100 articles out of ancient video games. Let’s do another today.

OK, I just compiled the “Reading” article and it will push up to the site shortly. Along with a new favicon how nice. What shall we do with the invaders today?

Having just spent 90 minutes or so writing the previous article, I want something easy today. Let’s see about making the Saucer explode or make noise, and see where that leads.

We do have a single bitmap for the saucer exploding. Let’s see how invaders do it:

function Invader:draw()
    if self.alive then
        sprite(self.sprites[self.picture+1], self.pos.x, self.pos.y)
    elseif self.exploding > 0 then
        sprite(asset.alien_exploding,self.pos.x,self.pos.y)
        self.exploding = self.exploding - 1
    end
end

This warrants copying over to the Saucer, causing duplication that we should be aware of. We’ll worry about that later, or never. So we get:

function Saucer:draw()
    pushStyle()
    tint(255,0,0)
    if self.alive then
        sprite(asset.saucer, self.pos:unpack())
    elseif self.exploding > 0 then
        sprite(asset.saucer_exploding,self.pos:unpack())
        self.exploding = self.exploding - 1
    end
    popStyle()
end

Now we need to set exploding suitably, initializing to zero, I suppose, and it must get set to something when we explode.

Ha, look at this, it’s already in there:

I copied and pasted that yesterday, I guess. This should work now, I believe. Well, nearly. We need to set exploding sooner than I did:

function Saucer:init(test)
    self.exploding = 0
    if test == nil then
        self.alive = false
    else
        self:go(test)
    end
end

When that init wasn’t there, there was an exception where someone was checking that exploded flag in draw. I’m not sure why, and I don’t care. I think it’s that we draw the saucer unconditionally in Army, thinking that decisions should be delegated down to it.

There’s another issue, which is that the explosion doesn’t always get a chance to run: another saucer starts out as soon as the current one is hit. I do get the kill sound, and I’ve seen the explosion a time or two but generally it doesn’t show. We’ll take a look at how the decision to run the saucer is made:

function Army:dropRandomBomb()
    if not self:runSaucer() then
        local bombs = {self.rollingBomb, self.plungerBomb, self.squiggleBomb}
        local bombType = math.random(3)
        self:dropBomb(bombs[bombType])
    end
end

I think this is meant to be “if we’re not firing the saucer right now, then try these other bombs”. The runSaucer method is this:

function Army:runSaucer()
    if self.saucer.alive then return true end
    if self:yPosition() > 89 then return false end
    if math.random(1,20) < 10 then return false end
    self.saucer:go(self.player:shotsFired())
    return true
end

We return true here if the saucer is alive now, or if we start it up in this cycle. As written this will mean that no one fires while the saucer is alive. I’ll run the game and see if I can verify that.

Yes, that turns out to be the case. I think we want to continue firing when the saucer is running or exploding, but not fire a missile in the same cycle as starting the saucer. Therefore:

function Army:runSaucer()
    if self.saucer:isRunning() then return false end
    if self:yPosition() > 89 then return false end
    if math.random(1,20) < 10 then return false end
    self.saucer:go(self.player:shotsFired())
    return true
end

I’ve delegated the question to saucer:

function Saucer:isRunning()
    return self.alive or self.exploding > 0
end

I think this should allow missiles to fall when the saucer is running, and make the explosions work every time.

And in fact it does. I’ll try to make a movie of it but I hit the saucer so seldom that I’d have to learn how to edit movies to fit it on my web site. Trust me, though, it works.

There are two ufo (saucer) sounds in my assets. One of them is high-pitched and a zillisecond long, the other is rather longer. For now, we’ll trigger that sound when we start the saucer:

function Saucer:go(shotsFired)
    self.startPos = vec2(  8,185)
    self.stopPos  = vec2(208,185)
    self.step     = vec2(  2,  0)
    if shotsFired%2==1 then
        self.step = -self.step
        self.startPos,self.stopPos = self.stopPos,self.startPos
    end
    self.alive = true
    self.pos = self.startPos
    self.exploding = 0
    SoundPlayer:play("ufoLo")
end

That sounds nearly good. I’ve been slaving over a hot computer for well over two hours now, so let’s commit and do a short summing up.

Commit: invader sound and explosion.

Summing Up

Well, good news and not so good news?

We successfully put in a couple of simple changes to the Saucer and in so doing we expressed an idea explicitly that was not explicit before, and that is not explicit in other objects and probably should be.

The idea is isRunning and it is a one-line method:

function Saucer:isRunning()
    return self.alive or self.exploding > 0
end

This is just the sort of small method that Cindy Sridharan was talking about in this morning’s referenced article. I hope you can see why I like it, and why I hope that everyone, including Cindy, would like it.

We have lots of duplication going on now, certainly between invaders and the saucer and perhaps between ordinary bombs and the saucer. I think we’ll look for that next time: I like to deal with things like this with fresh eyes.

I would say that today we made at least one small design improvement with isRunning but that overall, we left the campground a bit messier than we found it. If I had a pair to hand off to for a while, I’d do that. As it is, I’m going to take a break and deal with the campground next time.

See you then!

Invaders.zip