Asteroids 20
I’m feeling like coding a bit this evening. So stand back.
Before I figure out something easy to do, I wanted to pass on a comment from “West” on the Codea forum. He created an Asteroids tutorial a while back, and he commented without criticism that he noticed that I went for getting the right asteroid shapes rather early on.
I did. The reason was that I had been reading the 6502 code and wondered whether I was understanding the vector code of the DVG processor and I just kind of got into it. And it was kind of interesting building that little conversion program that converted the DVG code to something I could use in Codea.
However, it’s worth noting that in the spirit of getting a program working ASAP and keeping progress going from start to finish, spending time on the asteroid shapes might not be an ideal strategy.
The point is, I’m not here to say what the ideal strategy is, nor do I claim that what I’m doing is particularly ideal, particularly “Agile”, or particularly anything. I’m here to show you what I do, what happens when I do it, and to try to point to what I believe about what I do and what happens.
What I believe might not even be true for me, much less for you. What I hope for is to give you things to think about and to use to adjust your own practice, even if it’s to say Well, I’ll never do that!
I’m trying to do reasonable things, certainly, and trying to make clear what I’m up to and why. It remains up to you to find the value.
Anyway, what shall we do tonight?
Universe
What I think I’ll do is work a bit on the Universe
. In this morning’s exercise I moved some creation from Main / setup
into Universe. I think I’d like to move everything in there, first from setup
and then later from Main’s other components, leaving just the basic operational hooks in Main that have to be there.
Tonight I’m just thinking to deal with setup
but we’ll see where we wind up. The code may lead us elsewhere. Here’s Main setup
and its globals.
-- Asteroids
-- RJ 20200511
Touches = {}
Ratio = 1.0 -- draw time scaling ratio
Score = 0
function setup()
U = Universe()
U:createAsteroids()
Score = 0
--displayMode(FULLSCREEN_NO_BUTTONS)
createButtons()
end
I believe that Ratio is used about once now, so it might be a good start. I’ll try Codea’s search function to see what we find:
OK, a few times. It’s set in draw
, and applied in two places, once in Universe
and once in Asteroid
. One of uses is a mistake:
function Universe:moveObject(anObject)
local pos = anObject.pos + Ratio*anObject.step
anObject.pos = vec2(self:keepInBounds(pos.x, WIDTH), self:keepInBounds(pos.y, HEIGHT))
end
function Asteroid:init()
self.pos = vec2(math.random(WIDTH), math.random(HEIGHT))
self.shape = Rocks[math.random(1,4)]
self.scale = 16
local angle = math.random()*2*math.pi
self.step = Ratio*vec2(Vel,0):rotate(angle)
end
That second usage is a mistake! On my hot iPad, Ratio is always 1, so we don’t notice the effect. On my slower iPad, I noticed last night that broken asteroids ran twice as fast as the big ones. And I found that bug. And I made a mental note to fix it this morning. You see how well that worked. Anyway we’ve found it again. I’ll remove that one and fix the other references to be inside Universe:
function Universe:init()
self.ship = createShip()
self.processorRatio = 1.0
self.asteroids = {}
self.missiles = {}
self.explosions = {}
self.missileVelocity = vec2(MissileSpeed,0)
end
function Universe:draw()
self.processorRatio = DeltaTime/0.0083333
self:drawAsteroids()
self:drawExplosions()
end
function Universe:moveObject(anObject)
local pos = anObject.pos + self.processorRatio*anObject.step
anObject.pos = vec2(self:keepInBounds(pos.x, WIDTH), self:keepInBounds(pos.y, HEIGHT))
end
I renamed it processorRatio
, hoping that was a better name.
Now what else is in setup
? Not much.
function setup()
U = Universe()
U:createAsteroids()
Score = 0
--displayMode(FULLSCREEN_NO_BUTTONS)
createButtons()
end
Now createButtons
is in the Button tab right now, and it won’t care where it’s called from so we can just move that create over to Universe:
function Universe:init()
createButtons()
self.ship = createShip()
self.processorRatio = 1.0
self.asteroids = {}
self.missiles = {}
self.explosions = {}
self.missileVelocity = vec2(MissileSpeed,0)
end
Everything still works. Commit: “move ratio and createButton to universe”.
I’m curious how the buttons work, that is, how the ship interacts with them. No, I don’t remember: I have a computer for that. Ah, yes, there is a global table, Button
, that includes values true / false for left, right, go, and fire. Let’s move that into Universe and access it from there. That will involve a few changes:
function Universe:init()
self.button = {}
createButtons()
self.ship = createShip()
self.processorRatio = 1.0
self.asteroids = {}
self.missiles = {}
self.explosions = {}
self.missileVelocity = vec2(MissileSpeed,0)
end
function moveShip()
if U.button.left then Ship.radians = Ship.radians + rotationStep end
if U.button.right then Ship.radians = Ship.radians - rotationStep end
if U.button.fire then if not Ship.holdFire then fireMissile() end end
if not U.button.fire then Ship.holdFire = false end
actualShipMove()
end
function checkButtons()
U.button.left = false -- CHANGED on down
U.button.right = false
U.button.go = false
U.button.fire = false
for id,touch in pairs(Touches) do
for i,button in ipairs(Buttons) do
if touch.pos:dist(vec2(button.x,button.y)) < 50 then
U.button[button.name]=true -- CHANGED
end
end
end
end
function drawButtons()
pushStyle()
ellipseMode(RADIUS)
textMode(CENTER)
stroke(255)
strokeWidth(1)
for i,b in ipairs(Buttons) do
pushMatrix()
pushStyle()
translate(b.x,b.y)
if U.button[b.name] then -- CHANGED
fill(128,0,0)
else
fill(128,128,128,128)
end
ellipse(0,0, 50)
fill(255)
fontSize(30)
text(b.name,0,0)
popStyle()
popMatrix()
end
popStyle()
end
function actualShipMove()
if U.button.go then -- CHANGED
local accel = vec2(0.015,0):rotate(Ship.radians)
Ship.step = Ship.step + accel
Ship.step = maximize(Ship.step, 3)
end
finallyMove(Ship)
end
The Codea search helped me find those, even though it’s not the best search ever. It finds too much but that’s better than finding too little.
The game still plays, commit: “buttons in Universe”.
I think there’s something weird about the buttons, but I’m not clear how to improve it just now. They’re somewhat weird by their nature. They could be generalized but I see no reason to do that just now. Similarly, they might be made more object-oriented but why?
We’ll stick with working on moving things from Main to Universe. Here’s Main, except for touched
:
function setup()
U = Universe()
U:createAsteroids()
Score = 0
--displayMode(FULLSCREEN_NO_BUTTONS)
end
function draw()
--displayMode(FULLSCREEN_NO_BUTTONS)
checkButtons()
pushStyle()
background(40, 40, 50)
U:draw()
drawButtons()
drawShip()
moveShip()
U:drawMissiles()
drawSplats()
drawScore()
popStyle()
U:findCollisions()
end
function drawScore()
local s= "000000"..tostring(Score)
s = string.sub(s,-5)
fontSize(100)
text(s, 200, HEIGHT-60)
end
We can move checkButtons over, and obviously U:findCollisions
belongs over there. Ultimately all of the drawing should move over bit by bit. It’s tempting to move them all in a batch. It’s also a bit risky. But hey, let’s go for it, I’m going to move everything from background
on down. No, the heck with it. I’m moving the whole thing, leaving only U:draw
. Hold my Diet Coke.
function Universe:draw()
--displayMode(FULLSCREEN_NO_BUTTONS)
pushStyle()
background(40, 40, 50)
self.processorRatio = DeltaTime/0.0083333
self:drawAsteroids()
self:drawExplosions()
checkButtons()
drawButtons()
drawShip()
moveShip()
self:drawMissiles()
drawSplats()
drawScore()
popStyle()
self:findCollisions()
end
I reordered things just enough to move the display setting stuff to the top. The game still runs. Commit: “move all drawing to Universe”.
Now Main looks almost clean:
-- Asteroids
-- RJ 20200511
Touches = {}
Score = 0
function setup()
U = Universe()
U:createAsteroids()
Score = 0
end
function draw()
U:draw()
end
function drawScore()
local s= "000000"..tostring(Score)
s = string.sub(s,-5)
fontSize(100)
text(s, 200, HEIGHT-60)
end
function touched(touch)
if touch.state == ENDED or touch.state == CANCELLED then
Touches[touch.id] = nil
else
Touches[touch.id] = touch
end
end
We’ll move score:
function Universe:init()
self.score = 0
self.missileVelocity = vec2(MissileSpeed,0)
self.button = {}
createButtons()
self.ship = createShip()
self.processorRatio = 1.0
self.asteroids = {}
self.missiles = {}
self.explosions = {}
end
function drawScore()
local s= "000000"..tostring(U.score)
s = string.sub(s,-5)
fontSize(100)
text(s, 200, HEIGHT-60)
end
We’ll make drawScore
an instance method. (And should do so with some other functions here:
function Universe:drawScore()
local s= "000000"..tostring(self.score)
s = string.sub(s,-5)
fontSize(100)
text(s, 200, HEIGHT-60)
end
Commit: “move score to universe”.
When we complete this move to object-orientation, probably there will be no plain function calls in Universe:draw()
but for now it looks like this:
function Universe:draw()
--displayMode(FULLSCREEN_NO_BUTTONS)
pushStyle()
background(40, 40, 50)
self.processorRatio = DeltaTime/0.0083333
self:drawAsteroids()
self:drawExplosions()
checkButtons()
drawButtons()
drawShip()
moveShip()
self:drawMissiles()
drawSplats()
U:drawScore()
popStyle()
U:findCollisions()
end
It’s 9 PM. Let’s sum up.
Summing Up
We’ve moved all the game specific stuff out of Main, leaving just a generic Touches table. I think I might move that out sometime as well.
Universe is taking on more of its responsibilities to know all the constants and objects. We aren’t all the way to what I’d call good design – and we may never get all the way, since there are features to write and no one gets to perfection anyway.
But in the course of about an hour, the code is better than it was, by my standards. And we can almost always find an hour to keep things clean.
Oh. I forgot to paste the code at the bottom. Too late now, I’ll do it in tomorrow’s article.
See you next time!