Unfortunately, we need to take things to whole new levels. This’ll be fun. (Later: not as interesting as some, but it has its moments.)

As usual, it doesn’t take long to get answers on the Codea forum, and this morning there was a note from Simeon, one of Codea’s creators, about the issue of corners on sprites.

@RonJeffries this is an issue with draw order. If you have transparent objects, then they need to be drawn last (sorted by Z depth from furthest to closest – actually if you just sort all objects by Z depth you should be good)

The reason for this is that in OpenGL each thing you render writes to the depth buffer. But it writes all of its geometry and does not mask transparent pixel. So that when you go to render something later which should be “behind” the object that has been rendered, OpenGL tests which fragments to render by comparing the depth of the incoming fragment to the value in the depth buffer at that point. If a piece of transparent geometry is there it will “block” the background from getting rendered.

Now, I thought the zLevel drawing option was supposed to deal with this, and as we’ve seen with our results, it kind of does. I asked Simeon whether he views the feature as broken and if so does he plan to fix it.

Meanwhile, we need to figure out what to do. It’s true that we’re getting good graphics now, but it has been pretty fiddly, requiring us to do things in different orders and to move things around. That means that our system will likely be fragile with respect to matters of drawing order and we should do something about it.

Our drawing order right now is to draw the attract screen in Main, and then have Universe draw, which it does by drawing Indestructibles, Destructibles, and Buttons, in that order.

This evolved from the desire to have indestructible objects vs destructible ones, and had nothing to do with drawing. We could have drawn them in the other order, and until yesterday, we did. We have two concepts conflated, the way things interact with other things, and the order in which they are drawn. We didn’t do that on purpose, it just grew that way.

Now we realize we need to have an order of drawing concept which is independent of whether an object interacts with others.

This problem is a bit like the need to move our game to new display hardware, or to the need to make our app work on a new operating system. There will be concepts popping out as we try to push our square peg into the new round hole.

Fortunately, we’ve kept things fairly clean in the drawing area, with most of the details pushed down a level or two. Unfortunately, we’ve not done it perfectly. We don’t feel badly about this, because we gave up on expecting ourselves to be perfect decades ago. I hope you’re the same.

Anyway, that’s today’s problem.

What to do?

Looking at it simply, we have some number of types of things, that need to be drawn in order, basically back to front on the screen. These include:

  • the background
  • the ship
  • the saucers
  • the asteroids
  • the fragments
  • the score
  • the buttons

I think that’s roughly the right order. Buttons are drawn separately now, so that’s off the table. In principle, I think that the main “rule” is that things with transparency need to be drawn “last”, and basically in Fancy mode, everything is transparent. But it seems like asteroids should run over ships and fragments should be on top because they’re fun. And we don’t care which asteroid is on top of which.

Our drawing order, other than indestructibles first, is essentially random, because access to a table by key is random.

What are some ways we could “fix” this?

We now register all objects that Universe draws, either with addObject or addIndestructible. There are objects of various “levels” in each collection.

One possibility is to have one separate collection for drawing, so that both those add methods separate objects based on destructibility, but also add to the drawing collection. Then we could process the drawing collection multiple times, checking the desired drawing level for each object. That seems inefficient, though we have so few objects that it would surely be efficient enough.

What if addObject and addIndestructible accepted an optional level number and then did whatever we found necessary inside. Maybe at first it processes one collection multiple times and later makes separate collections. Maybe it does it by magic. We don’t have to care.

This seems like an interesting start. And I’m going to give the objects levels that aren’t 1,2,3 but 10, 20, 30, to leave room for adjustment later. We used to punch serial numbers on cards that way, too.

Since Codea ignores parameters that are unused, we can just plug this in. Here goes:

Level Parameter

Let’s number that table we had before:

  • [10] background
  • [20] ship
  • [30] saucers
  • [40] asteroids
  • [50] fragments
  • [60] score
  • [70] buttons

That’s certainly more than we need but no harm done starting this way. I’m pretty sure that background will be done outside the main draw loop when we’re done, and buttons already are done outside, so they aren’t registered.

So we’ll edit the relevant addObject and addIndestructible calls. And we should probably start right off making these not be magic numbers, shouldn’t we?

So, for example in Ship, I’d like to write:

function Ship:makeRegisteredInstance(pos)
    if Instance then U:deleteObject(Instance) end
    local ship = Ship(pos)
    Instance = ship
    U:addObject(ship, U.drawLevels.ship)
    return ship
end

Bit of a pain, but better do it:

function Universe:defineLevels()
    self.levels.backgound = 10
    self.levels.ship = 20
    self.levels.saucer = 30
    self.levels.asteroid = 40
    self.levels.fragment = 50
    self.levels.score = 60
    self.levels.buttons = 70
end

And of course a call to this in init. Now I can go through and edit them all in.

Ha! Forgot missiles, didn’t I! The saucer ones look good above asteroids, and mine destroy asteroids, so I’ll put them after asteroid. This once, I’ll renumber. Truth is, having built this table, I can use any numbers at all, can’t I?

And that gives me a great idea. Let’s finish these edits though.

Using addObject we hit ship, missile, saucer, and asteroid. Now for the indestructible objects …

Oops … splat. Forgot splats. Above asteroids, below fragments? OK.

I chose not to update the tests. However they’ll all break until I add the drawLevels to FakeUniverse, so I’d best do that.

The code should all work now as before. And it does.

Commit: all objects register with drawLevel.

So that’s good. Now for my great idea.

Great Idea

We need our objects draw in groups by their level. All objects are now passing in their level, which they have fetched from the Universe.

So why not have the Universe pass them the table it wants them in, which they will then pass back to the add function?

Poof, instant sorting. Then we change Universe:draw to draw the tables in the proper order.

Right now we have a bunch of levels, some unused:

function Universe:defineLevels()
    self.drawLevels = {}
    self.drawLevels.backgound = 10
    self.drawLevels.ship = 20
    self.drawLevels.saucer = 30
    self.drawLevels.asteroid = 40
    self.drawLevels.missile = 50
    self.drawLevels.splat = 60
    self.drawLevels.fragment = 70
    self.drawLevels.score = 80
    self.drawLevels.buttons = 90
end

If we remove the impossible ones (background and buttons) … but no. We just don’t care. If they don’t get used, no harm done.

We need tables for all nine of these things.

I want to digress here just for a moment. It would be possible, even easy, to group these tables, decide which ones to drop, whatever whatever. No. We do the simplest thing that can work, and deal with smoothing it after we have it in place. The idea is to have as few undefined balls in the air as possible.

I’m going to start longhand, because it’s simpler:

function Universe:defineLevels()
    self.t10 = {}
    self.t20 = {}
    self.t30 = {}
    self.t40 = {}
    self.t50 = {}
    self.t60 = {}
    self.t70 = {}
    self.t80 = {}
    self.t90 = {}
    self.drawLevels = {}
    self.drawLevels.backgound = t10
    self.drawLevels.ship = t20
    self.drawLevels.saucer = t30
    self.drawLevels.asteroid = t40
    self.drawLevels.missile = t50
    self.drawLevels.splat = t60
    self.drawLevels.fragment = t70
    self.drawLevels.score = t80
    self.drawLevels.buttons = t90
end

Now there are 9 new tables in Universe, t10 through t90. And when you ask for a level, say that of ship, you get the appropriate table, namely t20. Which you pass back to Universe in the add functions.

Then the add function just puts you in the right table.

But no. Delete won’t work, because we won’t know which table to delete from and it’s irritating to do them all.

Slight change of plans. We won’t pass this info at the time we add the object, we’ll have them store it in a member variable self.drawLevel. Then Universe can get at it when it needs to.

Slightly irritating but easy enough, just redo the searches for add and edit appropriately. (Note “just”, but I think it’ll be OK.)

So now they look like this:

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)
    self.drawLevel = U.drawLevels.asteroid
    U:addObject(self)
end

Again I expect all to work, but we’ll run the tests and the program. It’s all good. Commit: drawLevel tables are member variables.

Note, by the way, that we could have shipped at any point here if we had a reason to. We’re putting in infrastructure as we go, but in a way that doesn’t break things. That’s generally possible, certainly. I think it’s always possible.

The main trick is the one we have here: it’s in, but it’s not yet used.

Let’s Use It

There are two parts to “using it”. First, we have to change our add functions to populate the new tables. Second, we have to draw them all, while not drawing the existing objects and indestructibles tables. Maybe that’s three things. Maybe it’s four. I think it’s two.

Here’s the adding and deleting code (five things?)

function Universe:addObject(object)
    self.addedObjects[object] = object
end

function Universe:deleteObject(object)
    self.objects[object] = nil
    object.drawLevel[object] = nil
end

function Universe:addIndestructible(object)
    self.indestructibles[object] = object
    object.drawLevel[object] = object
end

function Universe:deleteIndestructible(object)
    self.indestructibles[object] = nil
    object.drawLevel[object] = nil
end

function Universe:applyAdditions()
    for k,v in pairs(self.addedObjects) do
        self.objects[k] = v
        v.drawLevel[k] = v
    end
    self.addedObjects = {}
end

Do you find that a bit cryptic? I’m used to it. If you were working here, you’d be used to it too. We could talk about what would be more clear, but one does need to understand how the system and language work.

I think this runs and if it does I’m going to commit. If it doesn’t, I’ll have found someone who didn’t set his drawLevel.

So nineteen tests fail saying attempt to index a nil value drawLevel. I suspect ship based on what failed.

Well, I’d be lying if I didn’t say that took longer than I’d like. The error was blatant, yet subtle. We had this table init:

function Universe:defineLevels()
    self.t10 = {}
    self.t20 = {}
    self.t30 = {}
    self.t40 = {}
    self.t50 = {}
    self.t60 = {}
    self.t70 = {}
    self.t80 = {}
    self.t90 = {}
    self.drawLevels = {}
    self.drawLevels.backgound = t10
    self.drawLevels.ship = t20
    self.drawLevels.saucer = t30
    self.drawLevels.asteroid = t40
    self.drawLevels.missile = t50
    self.drawLevels.splat = t60
    self.drawLevels.fragment = t70
    self.drawLevels.score = t80
    self.drawLevels.buttons = t90
end

Simple table init, no problem Except that t10 isn’t defined, and is therefore nil and therefore the main table, drawLevels, has no entries because table.entry = nil removes the entry. Tables can’t contain nils.

The correct version is this:

function Universe:defineLevels()
    self.t10 = {}
    self.t20 = {}
    self.t30 = {}
    self.t40 = {}
    self.t50 = {}
    self.t60 = {}
    self.t70 = {}
    self.t80 = {}
    self.t90 = {}
    self.drawLevels = {}
    self.drawLevels.backgound = self.t10
    self.drawLevels.ship = self.t20
    self.drawLevels.saucer = self.t30
    self.drawLevels.asteroid = self.t40
    self.drawLevels.missile = self.t50
    self.drawLevels.splat = self.t60
    self.drawLevels.fragment = self.t70
    self.drawLevels.score = self.t80
    self.drawLevels.buttons = self.t90
end

Everything works. Commit: level tables tN populated.

Now our tables are populated, with what we think are the right objects at each level. Some of the tables are empty, not that we care much if at all.

Let’s draw using those tables. Here’s drawEverything:

function Universe:drawEverything()
    for k,o in pairs(self.indestructibles) do
        self:drawProperly(o)
    end
    for k,o in pairs(self.objects) do
        self:drawProperly(o)
    end
end

We need to stop drawing those guys, and draw tables self.t10 through self.t90 instead.

I went off my head and coded this:

function Universe:drawEverything()
    local tables = {self.t10, self.t20, self.t30, self.t40, self.t50, self.t60, self.t70, self.t80, self.t90}
    for i, tab in ipairs(tables) do
        for k,o in pairs(tab) do
            self:drawProperly(o)
        end
    end
end

Make an array of the tables, process it as an array, ensuring order, and draw them babies right on out. I wonder what will happen.

Well. Not what I expected, I can tell you that. In attract mode the asteroids show up and everything looks fine. In game mode, nothing shows up except that the ship explodes all on its own and the fragments show up.

I kind of suspect drawing order above drawEverything but I am of course concerned that my clever loop may not be working. But the fact that some things draw make me think it’s drawing order during game play.

Letting it play with invisible things tells me everything is moving and colliding properly. I wonder whether there are objects being drawn, or whether they’re not drawn at all.

I’ll take down the background, that should ensure that nothing is covered.

Along the way I did get this:

smeared pic

That happened when I had no background at all.

Doing that more nearly right assures me that everything is being drawn, so it’s something about the levels, as if Score is being drawn too late, so the background covers everyone.

Smacks forehead! D’oh!

Oh, right, that’s intentional: score’s supposed to be on top and we’re supposed to draw the background explicitly now, not in Score:drawFancy, which need not exist at all.

That whole idea of drawing the background in score, which is a foreground object, was too fancy. Quite likely we wouldn’t be here at all had we put the background in the back to begin with. Anyway this is probably better.

I notice one amusing thing: the asteroids from attract mode are stuck on the screen, because they never get deleted.

I can fix that by clearing the asteroid draw table on a new wave. Right now, there’s nothing else in that table but asteroids, so I’ll at least try it.

function Universe:newWave()
    local pos
    self.beatDelay = 1 -- second
    self.timeOfNextWave = 0
    self.drawLevels.asteroid = {}
    for i = 1, self:newWaveSize() do
        if math.random(0,1) then
            pos = vec2(0,math.random(HEIGHT))
        else
            pos = vec2(math.random(WIDTH), 0)
        end
        Asteroid(pos)
    end
end

Hm. That’s too extreme, now they don’t appear at all. Oh, that’ll be because we’re creating a new table, not emptying the old one, and they’re not being picked up dynamically … and I’m not sure if they should be. I’ll empty it formally.

function Universe:newWave()
    local pos
    self.beatDelay = 1 -- second
    self.timeOfNextWave = 0
    local t = self.drawLevels.asteroid
    for k, o in pairs(t) do
        t[o] = nil
    end
    for i = 1, self:newWaveSize() do
        if math.random(0,1) then
            pos = vec2(0,math.random(HEIGHT))
        else
            pos = vec2(math.random(WIDTH), 0)
        end
        Asteroid(pos)
    end
end

And that works. Interesting. I’m not loving that solution but it’s clearly working, if not “right”.

It’s past time to break, so let’s commit: New level tables working, and then sum up.

Summing Up

The not so good news includes:

It’s possible that we could have found an acceptable way to do this that didn’t require fully assigning separate levels to everything. But had we not done so, what ran over what would be undefined, and now it’s clearly defined. So I’m OK with that.

In addition, this is the kind of structural change that sometimes crops up when we evolve away from our current design, and it was good to see that it was possible and could be done in small discrete steps without a long delay between potential releases.

It’s also not entirely good news that the way I chose to set up the tables left us with references to nil, and that Lua didn’t object. We’ve used that feature to our advantage all thought this design, so I suppose I can’t complain.

The good news, of course, is that now we have a system whose behavior we understand and have under more control. What displayed on top used to be random, and yesterday I spent a lot of time trying to figure out what was what. Now we know what was what, and despite our nagging believe that zLevel should work in Codea and should have handled this, we have a solution that is solid.

Finally, I’m a bit troubled by the fact that I can’t just clear the asteroids table by setting it to an empty table. I think a bit of refactoring will make that work and probably make the code more clear as well. We’ll try that soon.

All in all, a good day, with some excitement and a good outcome.

See you next time!

Asteroids.zip