Asteroids 7: Who's got a button?
I’ve decided how buttons should work. It only remains to make it so.
I’ve managed to get buttons displayed on the screen, where I actually intend them to be. I spoke last time about various options, and about my feeling that I’d have moved to objects by now if not sooner. But maybe there’s no reason to do it yet, and I’ve come up with an idea for how I’d like them to work, and an idea for how to do that.
It seems to me that one way or another, we want to know things like *if the ‘left’ button is pressed”. Just what we’ll do isn’t entirely clear, but it seems like if the ‘left’ button is pressed, we’ll add a bit to the Ship’s current angle. And so on.
So I want to be able to code roughly like this:
if Button.left then
Ship.angle = Ship.angle + angleIncrement
end
So the job of our button code will be to maintain an object called Button
, with fields like fire
and left
, setting those fields to true
if the button is pressed. We already have a table called Buttons that knows the button locations. We’ll extend that to be a table that knows the button’s name as well, and use that name to set the fields of the Button object.
Here’s the button setup now:
function createButtons()
local dx=50
local dy=200
table.insert(Buttons, vec2(dx,dy))
table.insert(Buttons, vec2(dy,dx))
table.insert(Buttons, vec2(WIDTH-dx,dy))
table.insert(Buttons, vec2(WIDTH-dy,dx))
end
We ask each button for its x and y when we draw it:
function drawButtons()
pushStyle()
ellipseMode(RADIUS)
stroke(255)
strokeWidth(1)
for i,b in ipairs(Buttons) do
pushMatrix()
translate(b.x,b.y)
ellipse(0,0, 50)
popMatrix()
end
popStyle()
end
So let’s preserve that and make each of the buttons a table with x, y, and name. (It’s worth noting that vec2
is just a name for a kind of table that yas x and y, no more than that. Well, a bit more, because the secret tables for vec2
include pointers to handy vector functions. We’ll ignore that fact for now, because we’re not using anything really vectorish. Here’s the new create:
function createButtons()
local dx=50
local dy=200
table.insert(Buttons, {x=dx, y=dy, name="left"})
table.insert(Buttons, {x=dy, y=dx, name="right"})
table.insert(Buttons, {x=WIDTH-dx, y=dy, name="fire"})
table.insert(Buttons, {x=WIDTH-dy, y=dx, name = "go"})
end
And I improved the draw a bit:
function drawButtons()
pushStyle()
ellipseMode(RADIUS)
textMode(CENTER)
stroke(255)
strokeWidth(1)
for i,b in ipairs(Buttons) do
pushMatrix()
translate(b.x,b.y)
ellipse(0,0, 50)
pushStyle()
fill(255)
fontSize(30)
text(b.name,0,0)
popStyle()
popMatrix()
end
popStyle()
end
That’s getting pretty long, by the way. We should extract its insides. And I am getting the feeling that the styles are out of control, but as long as we push and pop at the right times, we’ll be OK. The effect is pretty good:
Commit: “labeled buttons”.
Let’s make the buttons work
Recall what we have for touches:
function touched(touch)
if touch.state == ENDED or touch.state == CANCELLED then
Touches[touch.id] = nil
else
Touches[touch.id] = touch
end
end
This maintains a table of as many touches as are present, indexed by their id
, which Codea maintains for continuity. (I imagine that in fact it’s iPadOS that takes care of that.) Each touch knows many things, the most important to us being the field pos
, its position.
Our buttons are conveniently round, with a radius of 50. So if a touch’s position is within 50 of a button’s position, you’re pressing the button. I think the best thing will be to sort out the buttons at the beginning of each draw cycle. We’ll just loop over touches and buttons and see what’s what. Let me try to write that:
That went easily:
function draw()
checkButtons()
pushStyle()
background(40, 40, 50)
drawButtons()
drawShip()
moveShip()
drawAsteroids()
popStyle()
end
function checkButtons()
Button.left = false
Button.right = false
Button.go = false
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
Button[button.name]=true
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 Button[b.name] then
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
We call checkButtons
at the beginning of draw. In checkButtons
we clear all the button flags, then we check each touch against each button, and if the touch is in range, we set that button flag to true.
I tweaked the drawButton
to color the button red when it’s pressed, by way of a test. And it goes like this:
Commit: “colored active buttons”.
Rotation
Just to complete the morning, let’s see if we can make the left and right buttons make the ship rotate. That’s about the right size for the time I have left.
Turning logic goes in moveShip
, which is presently empty. I’ll try just adjusting the angle by 1 degree when the button is down:
function moveShip()
if Button.left then Ship.ang = Ship.ang + 1 end
if Button.right then Ship.ang = Ship.ang - 1 end
end
And it works just about as one might like:
Commit: “ship turns”.
Summing Up
That’ll do for today. We have buttons implemented fairly cleanly, and we’ve pretty seriously munged up their drawing. So that could use a bit of cleaning.
Other than that, which is localized in any case, we might want to rename one or both of Button
and Buttons
, lest there be confusion.
I think we’re almost ready to fly the ship and then to shoot at the asteroids.
By the way …
I’ve found some nice information about the old version, including the original 6502 source code on computerarcheology.com. I’d not discovered that site before. It’s really quite interesting. Among the things that will be useful will be that it includes the original patterns for the shapes of the asteroids, plus the graphics chip’s code for them, for example:
; Rock Pattern 1
09E6: 08 F9 SVEC scale=03(/64) bri=0 x=0 y=1 (0.0000, 0.0156)
09E8: 79 F9 SVEC scale=03(/64) bri=7 x=1 y=1 (0.0156, 0.0156)
09EA: 79 FD SVEC scale=03(/64) bri=7 x=1 y=-1 (0.0156, -0.0469)
09EC: 7D F6 SVEC scale=02(/128) bri=7 x=-1 y=-2 (-0.0078, -0.0234)
09EE: 79 F6 SVEC scale=02(/128) bri=7 x=1 y=-2 (0.0078, -0.0234)
09F0: 8F F6 SVEC scale=02(/128) bri=8 x=-3 y=-2 (-0.0234, -0.0234)
09F2: 8F F0 SVEC scale=02(/128) bri=8 x=-3 y=0 (-0.0234, 0.0000)
09F4: 7D F9 SVEC scale=03(/64) bri=7 x=-1 y=1 (-0.0156, 0.0156)
09F6: 78 FA SVEC scale=03(/64) bri=7 x=0 y=2 (0.0000, 0.0313)
09F8: 79 F9 SVEC scale=03(/64) bri=7 x=1 y=1 (0.0156, 0.0156)
09FA: 79 FD SVEC scale=03(/64) bri=7 x=1 y=-1 (0.0156, -0.0469)
09FC: 00 D0 RTS
;
So that’s nice, isn’t it? We’ll just code that right up.
But that’s for another day. For today, we’re good to go. See you next time, I hope!
-- Asteroids
-- RJ 20200511
local Asteroids = {}
local Vel = 1.5
local Ship = {}
local Touches = {}
local Buttons = {}
local Button = {}
function setup()
print("Hello Asteroids!")
displayMode(FULLSCREEN_NO_BUTTONS)
createButtons()
createAsteroids()
createShip()
end
function createAsteroids()
for i = 1,10 do
table.insert(Asteroids, createAsteroid())
end
end
function createAsteroid()
local a = {}
a.pos = vec2(math.random(WIDTH), math.random(HEIGHT))
a.angle = math.random()*2*math.pi
return a
end
function createButtons()
local dx=50
local dy=200
table.insert(Buttons, {x=dx, y=dy, name="left"})
table.insert(Buttons, {x=dy, y=dx, name="right"})
table.insert(Buttons, {x=WIDTH-dx, y=dy, name="fire"})
table.insert(Buttons, {x=WIDTH-dy, y=dx, name = "go"})
end
function createShip()
Ship.pos = vec2(WIDTH, HEIGHT)/2
Ship.ang = 0
end
function draw()
checkButtons()
pushStyle()
background(40, 40, 50)
drawButtons()
drawShip()
moveShip()
drawAsteroids()
popStyle()
end
function checkButtons()
Button.left = false
Button.right = false
Button.go = false
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
Button[button.name]=true
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 Button[b.name] then
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 drawShip()
local sx = 10
local sy = 6
pushStyle()
pushMatrix()
translate(Ship.pos.x, Ship.pos.y)
rotate(Ship.ang)
strokeWidth(2)
stroke(255)
line(sx,0, -sx,sy)
line(-sx,sy, -sx,-sy)
line(-sx,-sy, sx,0)
popMatrix()
popStyle()
end
function moveShip()
if Button.left then Ship.ang = Ship.ang + 1 end
if Button.right then Ship.ang = Ship.ang - 1 end
end
function drawAsteroids()
pushStyle()
stroke(255)
fill(0,0,0, 0)
strokeWidth(2)
rectMode(CENTER)
for i,asteroid in ipairs(Asteroids) do
drawAsteroid(asteroid)
moveAsteroid(asteroid)
end
popStyle()
end
function drawAsteroid(asteroid)
rect(asteroid.pos.x, asteroid.pos.y, 120)
end
function moveAsteroid(asteroid)
local step = vec2(Vel,0):rotate(asteroid.angle)
local pos = asteroid.pos + step
asteroid.pos = vec2(keepInBounds(pos.x, WIDTH), keepInBounds(pos.y, HEIGHT))
end
function keepInBounds(value, bound)
return (value+bound)%bound
end
function touched(touch)
if touch.state == ENDED or touch.state == CANCELLED then
Touches[touch.id] = nil
else
Touches[touch.id] = touch
end
end