Spacewar! 04 - Let's see if we can get this baby off the ground.
Here’s our code now, rotating based on the parameter slider. In this next session, we’ll see about making the ship move.
-- S3 Spacewar
function setup()
pos = vec2(WIDTH/2, HEIGHT/2)
vel = vec2(0,0)
theta = 0
parameter.integer("Turn", -1, 1, 0)
end
function draw()
background(40, 40, 50)
strokeWidth(2)
theta = theta + Turn
vel = vec2(1,1)
pos = clip_to_screen(pos + vel)
translate(pos:unpack())
rotate(theta)
ellipse(0,0,10,15)
line(0,0,0,10)
end
function clip_to_screen(vec)
return vec2(vec.x%WIDTH, vec.y%HEIGHT)
end
Let’s try acceleration. We’ll change our code so that touching the screen in the middle causes the ship to accelerate in the Y direction, because that’s how the ship is drawn, with Y forward. Touching the screen toward the left will rotate the ship to the left, touching toward the right side will make it rotate right.
-- S3 Spacewar
function setup()
pos = vec2(WIDTH/2, HEIGHT/2)
vel = vec2(0,0)
theta = 0
end
function draw()
background(40, 40, 50)
strokeWidth(2)
Turn = 0
local accel = vec2(0,0)
if (CurrentTouch.x < WIDTH/3) then
Turn = 1
end
if ( CurrentTouch.x > WIDTH*0.66) then
Turn = -1
end
if ( CurrentTouch.x >= WIDTH/3 and CurrentTouch.x <= WIDTH*0.66 ) then
accel = vec2(0,1)
end
theta = theta + Turn
vel = vel + accel
pos = clip_to_screen(pos + vel)
translate(pos:unpack())
rotate(theta)
ellipse(0,0,10,15)
line(0,0,0,10)
end
function clip_to_screen(vec)
return vec2(vec.x%WIDTH, vec.y%HEIGHT)
end
This works but the ship keeps accelerating, because we’re not conditioning our looks at CurrentTouch by whether the touch is BEGAN, ENDED, or MOVING. Let’s only accept BEGAN and MOVING. (That should also fix the thing where the ship starts out rotating to port.)
Voila:
-- S3 Spacewar
function setup()
pos = vec2(WIDTH/2, HEIGHT/2)
vel = vec2(0,0)
theta = 0
end
function draw()
background(40, 40, 50)
strokeWidth(2)
Turn = 0
local accel = vec2(0,0)
if ( CurrentTouch.state == BEGAN or CurrentTouch.state == MOVING ) then
if (CurrentTouch.x < WIDTH/3) then
Turn = 1
end
if ( CurrentTouch.x > WIDTH*0.66) then
Turn = -1
end
if ( CurrentTouch.x >= WIDTH/3 and CurrentTouch.x <= WIDTH*0.66 ) then
accel = vec2(0,1)
end
end
theta = theta + Turn
vel = vel + accel
pos = clip_to_screen(pos + vel)
translate(pos:unpack())
rotate(theta)
ellipse(0,0,10,15)
line(0,0,0,10)
end
function clip_to_screen(vec)
return vec2(vec.x%WIDTH, vec.y%HEIGHT)
end
The acceleration is way too fast. No surprise there. And it is straight “up”, not conditioned by the ship’s rotation. The ship flies up rapidly, which will not make for good game play. Let’s fix both those:
-- S3 Spacewar
function setup()
pos = vec2(WIDTH/2, HEIGHT/2)
vel = vec2(0,0)
theta = 0
end
function draw()
background(40, 40, 50)
strokeWidth(2)
Turn = 0
local accel = vec2(0,0)
if ( CurrentTouch.state == BEGAN or CurrentTouch.state == MOVING ) then
if (CurrentTouch.x < WIDTH/3) then
Turn = 1
end
if ( CurrentTouch.x > WIDTH*0.66) then
Turn = -1
end
theta = theta + Turn
if ( CurrentTouch.x >= WIDTH/3 and CurrentTouch.x <= WIDTH*0.66 ) then
accel = vec2(0, 0.1):rotate(math.rad(theta))
end
end
vel = vel + accel
pos = clip_to_screen(pos + vel)
translate(pos:unpack())
rotate(theta)
ellipse(0,0,10,15)
line(0,0,0,10)
end
function clip_to_screen(vec)
return vec2(vec.x%WIDTH, vec.y%HEIGHT)
end
Sweet. We can even rotate the ship and accelerate back. Note that when CurrentTouch is “in the middle”, we rotate the standard acceleration (0, 0.1) according to the current ship theta. In other words, we apply acceleration in the direction we’re headed.
We think we have physics spiked. All this needs to be abstracted into a ship object or something. And we need to invent controls. And make more than one ship fly. And and and …
But first, let’s review the code …
-- S3 Spacewar
function setup()
pos = vec2(WIDTH/2, HEIGHT/2)
vel = vec2(0,0)
heading = 0
end
function draw()
background(40, 40, 50)
-- controls
Turn = 0
local accel = vec2(0,0)
if ( CurrentTouch.state == BEGAN or CurrentTouch.state == MOVING ) then
if (CurrentTouch.x < WIDTH/3) then
Turn = 1
end
if ( CurrentTouch.x > WIDTH*0.66) then
Turn = -1
end
if ( CurrentTouch.x >= WIDTH/3 and CurrentTouch.x <= WIDTH*0.66 ) then
accel = vec2(0, 0.02)
end
end
-- move
adjustHeading()
adjustVelocity(heading, accel)
pos = clip_to_screen(pos + vel)
-- draw
strokeWidth(2)
translate(pos:unpack())
rotate(heading)
ellipse(0,0,10,15)
line(0,0,0,10)
end
function clip_to_screen(vec)
return vec2(vec.x%WIDTH, vec.y%HEIGHT)
end
function adjustHeading()
heading = heading + Turn
end
function adjustVelocity(heading, accel)
vel = vel + accel:rotate(math.rad(heading))
end
We’ve separated out controls, moving, and drawing, and we’ve pulled out adjusting the heading and velocity. We’re refactoring in a direction of better abstractions and moving toward creation of an object.
Even here, where we’re just beginning to spike on how these ships might work, we’re trying to have the code express our intention. Are we thinking about controlling? Let’s break that code away from the code for moving and the code for drawing. We won’t push very far, because we’re not sure just where these ideas will go in the real system, but we set them off a bit, so that the different abstractions have a chance to grow independently.
We review our code always, seriously if not in depth. In so doing, we note that the function adjustHeading
has a “side effect”: it sets heading inside itself. This could be OK, but a pure function seems better. Our first cut looks like this:
-- move
heading = adjustedHeading()
...
function adjustedHeading()
return heading + Turn
end
Note, however, that the function is referring to some (global) variables, which will perhaps become member variables, or perhaps be local to some function. Tozier1 and I think we should change the adjust functions to have parameters for everything they reference:
-- S3 Spacewar
function setup()
pos = vec2(WIDTH/2, HEIGHT/2)
vel = vec2(0,0)
heading = 0
end
function draw()
background(40, 40, 50)
-- controls
Turn = 0
local accel = vec2(0,0)
if ( CurrentTouch.state == BEGAN or CurrentTouch.state == MOVING ) then
if (CurrentTouch.x < WIDTH/3) then
Turn = 1
end
if ( CurrentTouch.x > WIDTH*0.66) then
Turn = -1
end
if ( CurrentTouch.x >= WIDTH/3 and CurrentTouch.x <= WIDTH*0.66 ) then
accel = vec2(0, 0.02)
end
end
-- move
heading = adjustedHeading(heading, Turn)
vel = adjustedVelocity(vel, heading, accel)
pos = clip_to_screen(pos + vel)
-- draw
strokeWidth(2)
translate(pos:unpack())
rotate(heading)
ellipse(0,0,10,15)
line(0,0,0,10)
end
function clip_to_screen(vec)
return vec2(vec.x%WIDTH, vec.y%HEIGHT)
end
function adjustedHeading(heading, turn)
return heading + turn
end
function adjustedVelocity(vel, heading, accel)
return vel + accel:rotate(math.rad(heading))
end
This feels better. It’s a judgment call, since these (I foresee) will turn into methods on an object, and methods are allowed to access member variables, but it feels better this way to us, so we’re going with it.
-
Bill Tozier, mentioned above, who is enjoying pairing on this thing with me and who’ll be present in most of the upcoming articles. ↩