Let’s get some more modernization up in this baby. I’m sure it’s the only reason we aren’t selling millions.

We’ve made fancy ships, fancy saucers, and fancy missiles. We have asteroids, explosions, and maybe splats to do. Let’s get to it.

It seems to me that asteroids should be easy, so I’ll start there.

function Asteroid:draw()
    if NoAsteroids then return end
    pushMatrix()
    pushStyle()
    stroke(255)
    fill(0,0,0, 0)
    strokeWidth(2)
    rectMode(CENTER)
    translate(self.pos.x, self.pos.y)
    scale(self.scale)
    strokeWidth(1/self.scale)
    for i,l in ipairs(self.shape) do
        line(l.x, l.y, l.z, l.w)
    end
    popStyle()
    popMatrix()
end

We can remove those push/pop things. Perhaps should leave the NoAsteroids check. It would take some time to be sure we could remove it, and it is harmless enough

function Asteroid:draw()
    if NoAsteroids then return end
    stroke(255)
    fill(0,0,0, 0)
    strokeWidth(2)
    rectMode(CENTER)
    translate(self.pos.x, self.pos.y)
    scale(self.scale)
    strokeWidth(1/self.scale)
    for i,l in ipairs(self.shape) do
        line(l.x, l.y, l.z, l.w)
    end
end

It’s worth a glance at asteroid creation as well:

function Asteroid:init(pos, size)
    self.pos = pos or vec2(math.random(WIDTH), math.random(HEIGHT))
    self.scale = size or 16
    self.shape = Rocks[math.random(1,4)]
    local angle = math.random()*2*math.pi
    self.step = vec2(Vel,0):rotate(angle)
    U:addObject(self)
end

Note that the asteroid gets created with one of the four official asteroid shapes. We could do something like that for Fancy mode as well, if we had more than one asteroid shape. Right now, we have zero. As things are built now, as soon as you flick the Fancy switch, everything cuts over to the appropriate style. So we might not want to make a final decision here anyway.

But it’s good to know.

Now for drawFancy …

function Asteroid:drawFancy()
    if NoAsteroids then return end
    translate(self.pos.x, self.pos.y)
    sprite(asset.builtin.Space_Art.Asteroid_Large)
end

As before, while we wait for the graphic designers to come back with marvelous asteroids, we’re plugging in these “clip art’ ones, so that the code will be ready for the real art. Which brings up an organizational issue that we should talk about. I’ve made a note to mention it in the summary. Let’s run this and see how our asteroid looks:

brown asteroids

Well, those are ugly and evocative aren’t they? That should light a fire under those graphics designers.

The way the code is now, they won’t change size when they split:

more brown

They could at least have the decency to get smaller. That’s what the scale value is in the draw code. It starts at 16 and goes 16, 8, 2. I’ll patch in a guess and we’ll scale it by eye if need be.

function Asteroid:drawFancy()
    if NoAsteroids then return end
    translate(self.pos.x, self.pos.y)
    scale(self.scale/16)
    sprite(asset.builtin.Space_Art.Asteroid_Large)
end

split

They do resize nicely. I wonder, though, how their scale corresponds to their kill distance. We’d like it to be pretty close. Let’s draw a circle around each one temporarily, to see what kill distance looks like:

kill dist

That’s not bad, we’ll go with it. I think we’re done here Commit: Fancy asteroids.

Explosion

I think we’d better work on the explosion of the ship. That line drawing is cute but we need something more up to the times. Let’s see how that works, it’s a bit interesting.

Explosion is just a factory that creates Fragments that fly about:

Explosion = class()

function Explosion:init(ship)
    local pos = ship.pos
   for i = 1,5 do
       local f = Fragment(pos, i==1)
   end
end

-- Fragment
-- RJ 20200612 from Spacewar

Fragment = class()

function Fragment:init(pos, guy)
    self.pos = pos
    self.color = 255
    self.frag = not guy
    self.base = vec2(0,0)
    self.ang= math.random(360)
    self.step = vec2(2.0*math.random(),0):rotate(self.ang)
    self.spin = math.random(9)
    self.ds = 8*math.random()
    self.life = 4
    U:addIndestructible(self)
end

There’s only one specialized fragment now, the one that looks like a little spaceman, the “guy”. (No offense intended to any space women, but I couldn’t bear to cast one into space.) I think we’d like all our new fragments to be different, so first let’s change the logic a bit. Instead of passing in a boolean to fragment, let’s pass frag number and use that in draw to decide what to draw:

function Explosion:init(ship)
    local pos = ship.pos
   for i = 1,5 do
       local f = Fragment(pos, i)
   end
end

function Fragment:init(pos, frag)
    self.pos = pos
    self.color = 255
    self.frag = frag
    self.base = vec2(0,0)
    self.ang= math.random(360)
    self.step = vec2(2.0*math.random(),0):rotate(self.ang)
    self.spin = math.random(9)
    self.ds = 8*math.random()
    self.life = 4
    U:addIndestructible(self)
end

function Fragment:draw()
    self.life = self.life - DeltaTime
    if self.life < 0 then
        U:deleteIndestructible(self)
    end
    pushStyle()
    pushMatrix()
    stroke(self.color)
    strokeWidth(2)
    noFill()
    translate(self.pos.x, self.pos.y)
    rotate(self.spin)
    scale(1)
    if self.frag ~= 1 then
        line(-10,0,10,0)
        line(10,0,5,13)
    else
        scale(3)
        strokeWidth(1)
        ellipse(0,5,8)
        line(0,3,0,-2)
        line(-4,2,4,2)
        line(0,-2,-3,-5)
        line(0,-2,3,-5)
    end
    popMatrix()
    popStyle()
end

That works just fine. Now for drawFancy:

function Fragment:drawFancy()
    translate(self.pos.x, self.pos.y)
    rotate(self.spin)
    sprite(FragArt[self.frag])
end

My cunning plan is to have a table called FragArt with space junk in it, drawn from these bits:

assets-red

local FragArt = {asset.builtin.Space_Art.Part_Red_Hull_2,
asset.builtin.Space_Art.Part_Red_Wing_1,
asset.builtin.Space_Art.Part_Red_Wing_2,
asset.builtin.Space_Art.Part_Red_Wing_3,
asset.builtin.Space_Art.Part_Red_Wing_4,}

function Fragment:drawFancy()
    translate(self.pos.x, self.pos.y)
    rotate(self.spin)
    sprite(FragArt[self.frag])
end

This shows up with two problems:

big parts

The pieces are quite a bit too large, and they don’t go away. Note in draw that the timing is done there, manually, rather than with tweens. We’ll have to duplicate that, and scale down to about 1/3. And I think I’ll replace one of the wing parts with something else, they look too much alike.

local FragArt = {asset.builtin.Space_Art.Part_Red_Hull_2,
asset.builtin.Space_Art.Part_Red_Wing_1,
asset.builtin.Space_Art.Part_Red_Wing_2,
asset.builtin.Space_Art.Part_Red_Hull_3,
asset.builtin.Space_Art.Part_Red_Wing_4,}

function Fragment:drawFancy()
    self.life = self.life - DeltaTime
    if self.life < 0 then
        U:deleteIndestructible(self)
    end
    translate(self.pos.x, self.pos.y)
    scale(0.33)
    rotate(self.spin)
    sprite(FragArt[self.frag])
end

good frags

That looks really good. I think we’ll leave the splats as they are. But that background, so 1970s. What can we do about that?

Background

We have one immobile indestructible object now, the Score, Perfect place to display a background. Right now, II think it doesn’t have a drawFancy, but we can fix that right up:

function Score:drawFancy()
    pushMatrix()
    pushStyle()
    self:draw()
    popStyle()
    popMatrix()
    translate(WIDTH/2,HEIGHT/2)
    scale(WIDTH/137,HEIGHT/91)
    sprite(asset.milkyway)
end

The scale bit is there because my graphic, milkyway, is 137x91, so that scales it to full screen. It nearly works as intended:

milky way1

milky way 2

There are two issues. First, the background doesn’t show up in attract mode, no surprise, because score doesn’t show up there, so we should move the background to the draw in Main.

Second, the asteroids and buttons show up on a black rectangular background, which isn’t very pretty. I’m not sure what’s causing that.

I’ve reverted the code, reinstalled the Score;drawFancy, and played with the order of drawing. I am fairly sure there is a problem in Codea with the z-order (back to front) of drawing sprites, and/or with the setting up of assets. I’ve been unable to duplicate the issue in a small program but will continue that i hopes of generating a bug report.

With the current drawing order, things look good:

looks good

I’ve lost the new fragment code in the reversion and so far the fancy background doesn’t show up in attract mode. I’ll commit this “Fancy background in game play”, and then work on fragments and attract mode.

I also have two failing unit tests, which I’d best go after first.

12: Ship added to objects -- Actual: 0, Expected: 1
27: Small Saucer shoots at 20/20 accuracy -- Actual: 0, Expected: 999

Curious.

        _:test("Ship added to objects", function()
            _:expect(countObjects()).is(0)
            Ship()
            U:applyAdditions()
            _:expect(countObjects()).is(1)
        end)

Here we need to use our new code to make a registered instance. I must not have noticed this yesterday.

This test has the same problem:

        _:test("Small Saucer shoots at 20/20 accuracy", function()
            U = FakeUniverse()
            local score = Score(3)
            score:addScore(3000)
            local ship = Ship(vec2(400,800))
            local saucer = Saucer(vec2(500,800))
            _:expect(saucer.size).is(0.5, 0.0001)
            local count = 0
            for i = 1,1000 do
                local m = saucer:fireMissile()
                if m.step.y < 0.001 and m.step.y > -0.001 then
                    count = count + 1
                end
            end
            saucer:dieQuietly()
            _:expect(count).is(999)
        end)

Changed tests now run. Commit: use Ship:makeRegisteredInstance in tests.

Explosion Again

I “should” be able to just paste the explosion code back in. Should have committed it when it worked.

With the initial code in place:

function Explosion:init(ship)
    local pos = ship.pos
   for i = 1,5 do
       local f = Fragment(pos, i)
   end
end

function Fragment:init(pos, frag)
    self.pos = pos
    self.color = 255
    self.frag = frag
    self.base = vec2(0,0)
    self.ang= math.random(360)
    self.step = vec2(2.0*math.random(),0):rotate(self.ang)
    self.spin = math.random(9)
    self.ds = 8*math.random()
    self.life = 4
    U:addIndestructible(self)
end


function Fragment:draw()
    self.life = self.life - DeltaTime
    if self.life < 0 then
        U:deleteIndestructible(self)
    end
    pushStyle()
    pushMatrix()
    stroke(self.color)
    strokeWidth(2)
    noFill()
    translate(self.pos.x, self.pos.y)
    rotate(self.spin)
    scale(1)
    if self.frag ~= 1 then
        line(-10,0,10,0)
        line(10,0,5,13)
    else
        scale(3)
        strokeWidth(1)
        ellipse(0,5,8)
        line(0,3,0,-2)
        line(-4,2,4,2)
        line(0,-2,-3,-5)
        line(0,-2,3,-5)
    end
    popMatrix()
    popStyle()
end

I’m seeing an odd thing: not all the fragments show up all the time. Sometimes there are three, sometimes four or five. Sometimes none. That’s mysterious. At a guess, they’re behind the background sprite.

This is grotesque hackery, but it works:

function Fragment:draw()
    self.life = self.life - DeltaTime
    if self.life < 0 then
        U:deleteIndestructible(self)
    end
    stroke(self.color)
    strokeWidth(2)
    noFill()
    translate(self.pos.x, self.pos.y)
    rotate(self.spin)
    scale(1)
    zLevel(1) -- < --- in front of everything
    if self.frag ~= 1 then
        line(-10,0,10,0)
        line(10,0,5,13)
    else
        scale(3)
        strokeWidth(1)
        ellipse(0,5,8)
        line(0,3,0,-2)
        line(-4,2,4,2)
        line(0,-2,-3,-5)
        line(0,-2,3,-5)
    end
end

I’ve not messed with zLevel yet but the default is zero, so one is in front, and now the line fragments are visible. Let’s put in the fancy draw again.

With that in, I had the same problem, the fragments not showing up, and I applied the same hammer, setting zLevel to one. I do not like that, Sam I Am. But it works.

Commit: fancy fragments.

Background Some More

The attract mode doesn’t show the fancy background. That’s because right now only Score shows it. Let’s try showing it early on in the overall process, in attract mode.’

I’m treading lightly now, because I really don’t understand what was making the sprites not be transparent.

My first attempt made the asteroids not appear at all. That was because my background sprite needs to be drawn first, before anything else. So now I have this:

function draw()
    if U.attractMode then
        pushMatrix()
        pushStyle()
        if Fancy then
            translate(WIDTH/2,HEIGHT/2)
            scale(WIDTH/137,HEIGHT/91)
            sprite(asset.milkyway)
            popStyle()
            popMatrix()
            pushMatrix()
            pushStyle()
        end
        fontSize(50)
        fill(255,255,255, 128)
        text("TOUCH SCREEN TO START", WIDTH/2, HEIGHT/4)
        text(Console, WIDTH/2, HEIGHT - 200)
        popStyle()
        popMatrix()
    end
    U:draw(ElapsedTime)
end

That properly displays the background in attract mode and during game play. Commit: fancy background in attract mode.

Summing Up

Overall Good Result

The game now has a thoroughly modern (1980s) look as opposed to its earlier 1970s look:

game movie

That’s what we set out to accomplish on Monday, and here on Tuesday it’s all in place.

That’s a good thing.

Some Confusion

There is definitely something going on with drawing order and the transparency of assets. I think it may relate to the state of the background when the asset is accessed, and Im sure that using the zLevel capability is fraught. I think the only place I’m using it now is in the fragments, and my guess is that sometimes the background from Score is drawn before the fragments, due to the randomness of loops over the object collections.

I suspect that if I can set up the sprite background first thing in the system draw, I can remove it from Score and probably remove the zLevel stuff. We’ll try that anon.

Organizing the art

I noticed an issue with organizing the art files. When the graphics designers come back with their lovely new sprites, we’ll have to install them by searching out the sprite() calls, and we’ll have to do something special about the Fragments, since they look up their sprite at run time.

Probably we should bring all the asset files together in some object or class and access them by an abstract name like “ShipArt” or “Fragment1”.

That’s for another day. Right now, after a somewhat confusing chasing of my tail on sprite backgrounds, we have a lovely new version in less than a full time day’s worth of work.

Not bad at all.

Summing up the summing up

If readers were to learn a single lesson from these articles so far, it would be that by keeping the code well-organized by our then-current standards, and by updating that organization as we gain new insights, we can help to ensure that new features will go in fairly smoothly.

If readers were to learn two lessons, the second would probably be that with the best of care, I make mistakes, and sometimes get pretty confused, and that the same will probably happen to the reader.

A third lesson, probably way too much to ask, is not to panic, write tests and experiments to learn, and to keep on keeping on.

See you next time!

Asteroids.zip