Asteroids 24
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:
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.
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:
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:
You navigate into the folder you want to move things from (Dropbox.assets for me) and then touch “Edit”.
You’ll see “Add to” down at the bottom.
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!