Spacewar! 08 - A review
Here we are on Thursday. It’s frankly snowing here outside the Brighton Agile Roundtable. Tozier will be here shortly, and meanwhile I’ll put down some thoughts. Here are our questions from last time, cleaned up a bit.
Does each control receive all touches or are they somehow filtered “above”? The latter would be more efficient but might cause us to have to create a place to stand. The touched
event occurs naturally only in Main and must be passed on. To whom?
There will be eight buttons, left, right, accelerate, fire, one set for each ship. If each button receives each touch that shows up at the top, we’d wind up sending irrelevant touches to controls that will reject them for being outside the button boundary. That seems inefficient, since a touch can be in one button at most. However, even at the top, we have to at least troll down through an average of four buttons to decide who a touch belongs to. I’m inclined not to worry about this matter, and instead let whatever architecture seems natural happen. Then maybe deal with any inefficiency that may seem to be there.
Maybe we could build a table of button description pointing to button or something. Anyway, I think it’s premature to worry about this kind of efficiency.
There will be control objects, owning a region of the screen. Will the control tell the ship turn left or whatever, or will the ship ask the control, do you think I should turn left? (Is there another possible way?)
It seems to me that we’ll want a Control kind of object, an instance for each button, and that Control will know whether or not it’s active, that is, whether you have your finger on it. As I sit here randomly thinking, it seems to me that the Control will handle the edges somehow, turning itself on and off as your fingers dance blithely on the screen. So since it has the edge, maybe it sends a message like turningLeft (true)
to the Ship. That seems better than the Ship inquiring repeatedly of the Control, whimpering pathetically “have you any messages for me NOW????”. So we’ll head that direction and see what happens.
General Note: It may seem to you as you read this that we’re almost trying to make mistakes. That is correct. One of my big messages here is that we do Mae er make mistakes and the trick is in working in such a way that we can recover from them. 1
Let’s code
We’ll proceed to work on a Control, figuring out things like sliding in and out, touching and lifting, and so on. Here goes.
We begin with this sketch:
function setup()
local offset = 200
ship1 = Ship(vec2(WIDTH/2 + offset, HEIGHT/2), 0)
ship2 = Ship(vec2(WIDTH/2 - offset, HEIGHT/2), 180)
ship1.accel = vec2(0,0.02)
ship1.turn = 0.5
ship2.accel = vec2(0,0.01)
ship2.turn = -0.4
button1accel = Button(WIDTH-100, HEIGHT/5, "accel")
ship1:addButton(button1accel)
end
We think that in the fullness of time this will create a button 1/4 of the way up the screen, over to the right. We’ll see. We’d really like to pass in the Ship’s accelerate function but we’ll just say “accel” for now and figure that out later.
This will not work: we’ll need to build the Button class.
Yes, well, that’s wrong. The Ship doesn’t need a Button: the Button needs a Ship:
button1accel = Button(WIDTH-100, HEIGHT/5, "accel")
button1accel:ship(ship1)
Here we go … no, wrong again. Bind the ship in the creation. (Tozier claims he was going to raise this issue.)
button1accel = Button(ship1, WIDTH-100, HEIGHT/5, "accel")
Now then:
Button = class()
function Button:init(ship, x, y, action)
self.ship = ship
self.pos = vec2(x,y)
self.action = action
end
function Button:draw()
pushMatrix()
translate(self.pos:unpack())
ellipse(0,0, 100)
popMatrix()
end
function Button:touched(touch)
-- Codea does not automatically call this method
end
And in Main:
function draw()
background(40, 40, 50)
ship1:control()
ship2:control()
ship1:move()
ship2:move()
-- draw
strokeWidth(2)
button1accel:draw()
ship1:draw()
ship2:draw()
end
Our next step, T and I agree, is to program our button for touch and have it tell the ship something. We revise Button:
Button = class()
function Button:init(ship, x, y, action)
self.ship = ship
self.pos = vec2(x,y)
self.action = action
self.radius = 100
end
function Button:draw()
pushMatrix()
translate(self.pos:unpack())
ellipse(0,0, self.radius)
popMatrix()
end
function Button:touched(touch)
if not self:insideCircle(touch.x,touch.y) then return end
if touch.state == BEGAN or touch.state == MOVING then
self.ship:buttonDown()
end
end
function Button:insideCircle(touchX,touchY)
return self.pos:dist(vec2(touchX,touchY)) <= self.radius
end
We (I) made several mistakes here, mostly of the form of not saying self.
or self:
where it was needed. We didn’t implement buttonDown in the Ship, so we can touch the screen anywhere but inside the button and nothing happens. When we touch inside the button, we get the error telling us Ship does not understand buttonDown. Now, I think we have to figure out how to tell the Button what method to call on ship. How do we pass a function to another object. Hmm …
We noticed that the calls in Main to ship: control() are unnecessary with our current scheme, so removed them.
A long delay has entered the game …
Wow, sorry, we were away quite a while. We tried a lot of strange syntax to access the acceleration method in Ship. Which we forgot to write, so it was nil, so when we did successfully call it, we still got an error. We got in more and more trouble. I imagine you’ve been there. We must have been stuck for around a half hour. So first we built our Ship:accelerate function, so our button could call it.
function Ship:accelerate()
self.accel = vec2(0,0.2)
end
function Button:touched(touch)
if not self:insideCircle(touch.x,touch.y) then return end
if touch.state == BEGAN or touch.state == MOVING then
self.action(self.ship)
end
end
We couldn’t quite work out how to tell the Button it was to accelerate
, and then to send that message to the Ship.
We tried various syntax in Button to get the call to work. In Ship:accelerate, seen above, self
is expected to be bound to the current instance of Ship. The syntactic sugar around a statement like myShip:accelerate() uses the colon to make Lua pass the object being called, myShip
in this case, as the first argument, which binds to self inside the function. So the syntax above uses the . notation to pass the ship as the first argument. Let me try to make that more clear. In Lua, the following two statements are identical in meaning:
Ship:accelerate()
self.acceleration = vec2(0, 0.02)
end
-- somewhere else ...
myShip:accelerate()
myShip.accelerate(myShip)
Note the difference, colon and dot. Inside a function, self
refers to that first invisible argument, when the definition of the function uses colon, as it does above. If you want to call using dot, you must pass the self parameter in manually. We think there should be a way to avoid passing the self.ship
using colon notation, but we can’t find one that will compile. All these other examples work with this as the button creation:
button1accel = Button(ship1, WIDTH-100, HEIGHT/5, "accelerate")
Note that we’re now passing in the name of the function, so we can look it up via ship[self.action] since function names are just strings inside the object’s table.
function Button:touched(touch)
if not self:insideCircle(touch.x,touch.y) then return end
if touch.state == BEGAN or touch.state == MOVING then
self.ship:accelerate() -- works but not using pluggable method name
self.ship.accelerate(self.ship). -- works but not using pluggable method name
self.ship["accelerate"](self.ship). -- works, not pluggable, and we have to pass ship in
self.ship[self.action](self.ship). -- works, pluggable, but must pass ship in as first parm.
end
end
So we have our message being sent, but we don’t like the syntax. Google has not helped. I’m going to ask the Codea help crowd, who are quite helpful and see if there’s a better way.
Note: at some point in our work, we fetched the actual function from ship1 and passed it into the Button. Looking at it now, I think I’d rather pass the name and look it up as we’re doing here. I’ll talk about that tomorrow.
And our time is up for today. This has been a lot like work. I hate when that happens.
-
I can’t even tell you how many typos I put in that sentence. Murphy has apparently aligned with Cthulhu. But I digress. ↩