UPDATE: I’ve learned how to add sounds into the project. I’ll update the zip file and maybe it will include them! See the end for details.

New ship, new sounds, learn new tricks, amaze your friends, confuse your enemies. Apply now!

Last night while sitting in our “living room”, I read the code for drawing the ship, and drew it on a field of coordinates in Paper™, my sketching tool of choice. It looked like this:

paper ship

I had to take a couple of wild guesses at some of the values in the description I had, because of the oddities of the scaling operations on the DVG that drew things on the old Asteroids game. But since I knew the ship was symmetrical, it was short work to get it right. So then I coded it up in an old version of this program that I have on the old iPad, and wound up with this new draw method:

local accel = 0

function Ship:draw()
   local sx = 10
   local sy = 6
   pushStyle()
   pushMatrix()
   translate(self.pos.x, self.pos.y)
   rotate(math.deg(self.radians))
   strokeWidth(1)
   stroke(255)
   scale(2)
   line(-3,-2, -3,2)
   line(-3,2, -5,4)
   line(-5,4, 7,0)
   line(7,0, -5,-4)
   line(-5,-4,-3,-2)
   accel = (accel+1)%3
   if U.button.go and accel == 0 then
       strokeWidth(1.5)
       line(-3,-2, -7,0)
       line(-7,0, -3,2)
   end
   popMatrix()
   popStyle()
end

As you can see above, I also put in the acceleration “flame”, which draws a little triangle at the back of the ship, one out of every three drawing cycles. The original did one out of four, but three looks better on my screen.

I’ll do a blown-up version here for you to look at.

new ship

The ship is pretty small at scale 2, and based on user feedback, me and Dave and Bri, maybe we’ll change that. But the ship looks good and the game runs, so let’s commit: “new ship”.

Sounds

With just a tiny bit of Google-fu, I found recordings of the Asteroid sounds, on classicgaming.cc.

I managed to make those work on my old iPad, so this morning I’ll go through the process of getting them into the game.

I downloaded the files on the iPad, which puts them in Files / Downloads. From there, I can long-press the zip files and select uncompress, which puts a new file of the same name in Files / Downloads. fire.zip becomes fire, and so on.

Touching those files brings up a quick look at that file:

quick

If you touch the “…”, you get a menu that includes “Move” and you can copy (it copies, doesn’t really move) the file into Codea / Dropbox.assets. At this writing, I don’t know how to get the file inside a specific project, because Files doesn’t seem to recognize Codea projects as folders.

Now the sounds are in a place where we can access them. Let’s do the “fire” sound when you fire a missile.

We have this for firing missiles:

function Ship:fireMissile()
    self.holdFire = true
    Missile(self)
end

This seems like a reasonable place to send the sound. Another possibility would be to do it in Missile:

function Missile:init(ship)
    function die()
        self:die()
    end
    self.pos = ship.pos
    self.step = U.missileVelocity:rotate(ship.radians) + ship.step
    U.missiles[self] = self
    tween(3, self, {}, tween.easing.linear, die)
end

I don’t have a strong feeling about this. For now, I’ll put it in fireMissile and see if something happens to change my mind:

function Ship:fireMissile()
    sound(asset.documents.Dropbox.fire)
    self.holdFire = true
    Missile(self)
end

That’s all there is to it, and now the ship makes a nice “pew pew” noise when I press fire. I wish you could hear it. Unfortunately, the iPad’s built-in screen recorder doesn’t record the sound. Just think “pew pew” to yourself.

Now I’ll commit: “pew pew”.

Now one very good question is whether we want all these calls to sound spread all over the program. The calls themselves are OK, but since the asset path is so baroque, and because it’s likely to change once I find out how to bind sounds into my project, it might be nice to centralize them.

I wonder if I can store one of those path things in a variable:

function Ship:fireMissile()
    local fire = asset.documents.Dropbox.fire
    sound(fire)
    self.holdFire = true
    Missile(self)
end

Ha! That works a treat. So let’s put the sounds into the Universe, in a sub-table called sounds. I’ll do that for the first one:

function Ship:fireMissile()
    sound(U.sounds.fire)
    self.holdFire = true
    Missile(self)
end


function Universe:init()
    self.processorRatio = 1.0
    self.score = 0
    self.rotationStep = math.rad(1.5) -- degrees
    self.missileVelocity = vec2(MissileSpeed,0)
    self:defineSounds()
    self.button = {}
    self.asteroids = {}
    self.missiles = {}
    self.explosions = {}
    self.attractMode = true
    self:newWave()
end

function Universe:defineSounds()
    self.sounds = {}
    self.sounds.fire = asset.documents.Dropbox.fire
end

Works just right. Commit: “fire sound in Universe”.

Now I’ll just import all the ones I have:

function Universe:defineSounds()
    self.sounds = {}
    self.sounds.fire = asset.documents.Dropbox.fire
    self.sounds.bangLarge = asset.documents.Dropbox.bangLarge
    self.sounds.bangMedium = asset.documents.Dropbox.bangMedium
    self.sounds.bangSmall = asset.documents.Dropbox.bangSmall
    self.sounds.beat1 = asset.documents.Dropbox.beat1
    self.sounds.beat2 = asset.documents.Dropbox.beat2
    self.sounds.extraShip = asset.documents.Dropbox.extraShip
    self.sounds.fire = asset.documents.Dropbox.fire
    self.sounds.saucerBig = asset.documents.Dropbox.saucerBig
    self.sounds.saucerSmall = asset.documents.Dropbox.saucerSmall
    self.sounds.thrust = asset.documents.Dropbox.thrust
end

Commit: “all known sound assets defined”.

Well, now, let’s make some noise. We’ve got firing, let’s do thrust. That should turn on at the same time as we do the acceleration thing, which currently looks like this:

function Ship:actualShipMove()
    if U.button.go then
        local accel = vec2(0.015,0):rotate(self.radians)
        self.step = self.step + accel
        self.step = maximize(self.step, 3)
    end
    self:finallyMove()
end

That seems reasonable: I’ll put it there. I don’t care quite so much where I put them now, because the part that most likely changes is centralized.

function Ship:actualShipMove()
    if U.button.go then
        sound(U.sounds.thrust)
        local accel = vec2(0.015,0):rotate(self.radians)
        self.step = self.step + accel
        self.step = maximize(self.step, 3)
    end
    self:finallyMove()
end

That sound is a bit loud, but I’ll leave it for now. What else? The asteroids make sounds when they break. Easy enough to do that with an if, although it’s not my favorite idea.

function splitAsteroid(asteroid, asteroids)
    if asteroid.scale == 16 then
        sound(U.sounds.bangLarge)
    elseif asteroid.scale == 8 then
        sound(U.sounds.bangMedium)
    elseif asteroid.scale == 4 then
        sound(U.sounds.bangSmall)
        Splat(asteroid.pos)
        DeadAsteroids[asteroid] = asteroid
        return
    end
    asteroid.scale = asteroid.scale//2
    asteroid.angle = math.random()*2*math.pi
    local new = Asteroid()
    new.pos = asteroid.pos
    new.scale = asteroid.scale
    asteroids[new] = new
    Splat(asteroid.pos)
end

Oh my, the game is much more fun with these sounds. I’m very pleased. Commit: “thrust and bang”. What else?

We don’t have saucers or extra ships yet. The remaining sounds are “beat1” and “beat2”. These are two deep beat sounds, very similar. The original game played them slowly at the beginning of a wave and faster and faster as the wave went on.

What would be a good way to play a sound every so often? We get a draw call every so often … 1/120th or 1/60th or 1/30th of a second. We could accumulate time that way.

Codea has a variable ElapsedTime that continually increases, so we could difference that at draw time. That would be similar.

Or maybe we could do something clever with tween. It has a callback after an interval.

As a sound check, I wrote this little program:

-- soundcheck

function setup()
    b1 = asset.documents.Dropbox.beat1
    b2 = asset.documents.Dropbox.beat2
    soundLastTime = ElapsedTime
    delay = 1
end

-- This function gets called once every frame
function draw()
    playSound()
end

function touched()
    if (CurrentTouch.state ~= ENDED) then return end
    delay = 0.9 * delay
    print(delay)
end

local lastSound
function playSound()
    if ElapsedTime - soundLastTime > delay then
        if lastSound == b2 then
            lastSound = b1
        else
            lastSound = b2
        end
        sound(lastSound)
        soundLastTime = ElapsedTime
    end
end

This plays the two beat sounds, alternating, every “delay” seconds. Touching the screen reduces delay to 0.9*delay. Sliding that down to about 0.2 seconds seems to be about right. The original game starts the delay at 128 frametimes, reduces the delay every 64 frames, and reduces it by 1 frametime, down to a minimum of 8. Now if we only knew the frame time.

Ha, found this line in the original source code:

INC FrameCounter   ;Start a new frame. 62.5 frames per second.

So that’s nice. Starting delay is 128/62.5 or about 2 seconds, ending is 8/62.5, or 0.128 seconds. Pretty close to what my ears told me. And it ticks down the delay every frame, by 1. That’s 0.016, down per cycle.

I’ll adjust my little sample program to work that way. This is absolute hackery but I’m just here to listen to the sound and see if I like it. And I do:

-- soundcheck

function setup()
    b1 = asset.documents.Dropbox.beat1
    b2 = asset.documents.Dropbox.beat2
    soundLastTime = ElapsedTime
    delay = 1
    frame = 0
end

-- This function gets called once every frame
function draw()
    frame = (frame + 1)%64
    if frame == 63 and delay > 0.128 then
        delay = delay - 0.016
    end
    playSound()
end

function touched()
    if (CurrentTouch.state ~= ENDED) then return end
    delay = 0.9 * delay
    print(delay)
end

local lastSound
function playSound()
    if ElapsedTime - soundLastTime > delay then
        if lastSound == b2 then
            lastSound = b1
        else
            lastSound = b2
        end
        sound(lastSound)
        soundLastTime = ElapsedTime
    end
end

OK, what’s a semi decent way to do this?

We can keep a frame counter in Universe. Right now the only one we need is a 64-frame rollover, so we’ll just do that. Henceforth, U.frame64 will be a 0-63 frame counter:

function Universe:init()
    self.processorRatio = 1.0
    self.score = 0
    self.rotationStep = math.rad(1.5) -- degrees
    self.missileVelocity = vec2(MissileSpeed,0)
    self.frame64 = 0
    self:defineSounds()
... and so on

function Universe:draw()
    self.frame64 = (self.frame64+1)%64
    displayMode(FULLSCREEN_NO_BUTTONS)
    pushStyle()
... and so on

And it might as well be the Universe that plays the general beat, so:

function Universe:draw()
    self.frame64 = (self.frame64+1)%64
    self:checkBeat()
    displayMode(FULLSCREEN_NO_BUTTONS)
...

function Universe:checkBeat()
    if self.attractMode then return end
    self:updateBeatDelay()
    if ElapsedTime - self.lastBeatTime > self.beatDelay then
        self.lastBeatTime = ElapsedTime
        self:playBeat()
    end
end

function Universe:updateBeatDelay()
    if self.frame64 == 63 and self.beatDelay > 0.128 then
        self.beatDelay = self.beatDelay - 0.016
    end
end

function Universe:playBeat()
    if self.lastBeat == self.sounds.beat2 then
        self.lastBeat = self.sounds.beat1
    else
        self.lastBeat = self.sounds.beat2
    end
    sound(self.lastBeat)
end

And now an irritating beat goes on. And on .. and onandonandon.

Note that I’ve broken out three phases of handling the beat: checkBeat checks to see if we’re not in attract mode, updates the beat delay, and if time has elapsed calls for the beat to be played.

We might factor that a bit differently, and you might prefer to write it out all in line, but my preferred style is to have lots of little methods.

As you can see, I’ve stored all the beat-relevant information as member variables in Universe. I could imagine building a Beat class that did all of this. It’s certainly a separate sort of thing from Universe, and a case could be made for breaking it out as we have done with buttons and ships and asteroids.

I think I’ll defer that for right now, it’s time for lunch.

Commit: “the beat goes on”.

UPDATE

The Codea group, in the person of @piinthesky, came through with instructions on moving assets into the project. It goes like this:

If you have a sound() call, you can click on it and get a popup asset picker:

picker-1

You navigate into the folder you want to move things from (Dropbox.assets for me) and then touch “Edit”.

picker-2

You’ll see “Add to” down at the bottom.

picker-3

Select the assets you want to copy to the new project, touch “Add to” and navigate to the new folder. Touch “Done” and it’s done.

I believe the new zip file will contain the sound assets now. The new asset code looks like this:

function Universe:defineSounds()
    self.sounds = {}
    self.sounds.bangLarge = asset.bangLarge
    self.sounds.bangMedium = asset.bangMedium
    self.sounds.bangSmall = asset.bangSmall
    self.sounds.beat1 = asset.beat1
    self.sounds.beat2 = asset.beat2
    self.sounds.extraShip = asset.extraShip
    self.sounds.fire = asset.fire
    self.sounds.saucerBig = asset.saucerBig
    self.sounds.saucerSmall = asset.saucerSmall
    self.sounds.thrust = asset.thrust
end

And the sounds still work!

Summing Up

So this went surprisingly well. The ship draws itself more nicely, and we have all the game sounds we could find stored as close to the product as we can manage. I’m waiting for feedback from the forum on how to get the sounds right into the project.

Having taken a break long enough to make a sandwich, I’m thinking that at least the beat, and possibly all the sounds, should be broken out into a separate class, but for now they’re well-enough organized. Universe is starting to take on some of the attributes of a “god class”, with too much going on. We need to keep an eye on that but for now it’s not bad.

I’ll include a link to a zipped file of the source code, which is much easier for actual Codea users to use. If you would like me to go back to including the whole file pasted into the articles, or some other scheme, let me know.

See you next time!

zipped project