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.

  1. Bill Tozier, mentioned above, who is enjoying pairing on this thing with me and who’ll be present in most of the upcoming articles.