Developing Asteroids: a series.

Or here is a slightly different take. Again no constant rate of rotation and you can’t boost and fire at the same time, but I feel it has a pleasing fluidity (YMMV). Again very rough and was written several years ago - I’ve updated the sprite assets to remove the deprecation warning.


-- touchcontrol

-- Use this function to perform your initial setup
function setup()
    displayMode(FULLSCREEN)
    touches={}
    tsup={} --supplemental touch info
    activetouches=0
    spacedust={}
    ship=physics.body(CIRCLE,10)
    ship.x=WIDTH/2
    ship.y=HEIGHT/2
    ship.gravityScale=0
    ship.linearDamping=0.3
    shipangle=0
    shipoffset=-90
    shipsize=30
    --bullets
    bullet={}
    bulletspeed=5
    scale=1
    globalx=0
    globaly=0
    bgx=WIDTH/2
    bgy=HEIGHT/2
end

function draw()
    globalx=ship.x
    globaly=ship.y
    background(40, 40, 50)
    activetouches=0
    oldesttouchid=0
    longesttouch=0
    for k,touch in pairs(touches) do
        activetouches = activetouches + 1
        tsup[touch.id].time = tsup[touch.id].time + 1
        if tsup[touch.id].time>longesttouch then
            longesttouch=tsup[touch.id].time
            oldesttouchid=touch.id
            shipangle=math.deg(math.atan2(touch.y-ship.y,touch.x-ship.x))
        end
    end
    
    if activetouches==2 then
        local thrust=2
        ship:applyForce(vec2(thrust*math.cos(math.rad(shipangle)),thrust*math.sin(math.rad(shipangle))))
        table.insert(spacedust,
        {x=ship.x,y=ship.y,dir=shipangle+shipoffset+math.random(20)+170,
        fade=175+math.random(50),size=2+math.random(5),speed=5+math.random(5)})
    end
    if ship.x<0 then ship.x=WIDTH end
    if ship.x>WIDTH then ship.x=0 end
    if ship.y<0 then ship.y=HEIGHT end
    if ship.y>HEIGHT then ship.y=0 end
    for i,b in pairs(bullet) do
        sprite(asset.builtin.Tyrian_Remastered.Bullet_Fire_A,b.x,b.y)
        b.x = b.x + scale*bulletspeed*math.sin(math.rad(-b.a))
        b.y = b.y + scale*bulletspeed*math.cos(math.rad(-b.a))
        if b.x>WIDTH or b.x<0 or b.y>HEIGHT or b.y<0 then
            table.remove(bullet,i)
        end
    end
    for s,d in pairs(spacedust) do
        --add transparency to the space dust so it fades away
        tint(240,120,150,d.fade)
        pushMatrix()
        translate(d.x,d.y)
        sprite(asset.builtin.Cargo_Bot.Star_Filled,0,0,d.size,d.size)
        popMatrix()
        d.x = d.x + d.speed*math.sin(math.rad(-d.dir))
        d.y = d.y + d.speed*math.cos(math.rad(-d.dir))
        d.fade = d.fade -5
        if d.fade<0 then
            table.remove(spacedust,s)
        end
    end
    tint(255)
    pushMatrix()
    translate(ship.x,ship.y) --executed 3rd -move sprite by x along x axis and y along y axis
    rotate(shipangle) --executed 2nd - rotate sprite by 20 degrees anticlockwise
    rotate(shipoffset)
    sprite(asset.builtin.Space_Art.Part_Yellow_Hull_4,0,0,shipsize) --executed 1st - draw sprite at 0,0
    popMatrix()
end

function touched(touch)
    if touch.state == ENDED then
        if tsup[touch.id].time<15 and activetouches==1 then
            table.insert(bullet,{x=ship.x,y=ship.y,a=shipangle+shipoffset})
        end
        touches[touch.id] = nil
        tsup[touch.id]=nil
    else
        touches[touch.id] = touch
        if tsup[touch.id]==nil then
            tsup[touch.id]={time=0}
        end
    end
end

@RonJeffries Here’s something you can try just for kicks. I added Gravity.x to control the rotation of the ship by tilting the iPad left or right. Just don’t press the Left or Right buttons or comment them out.

PS. Using tilt, the Fire button could be on one side and the Go button on the other.

function Ship:move()
    if U.button.left then self.radians = self.radians + U:adjustedRotationStep() end
    if U.button.right then self.radians = self.radians - U:adjustedRotationStep() end
    if U.button.fire then if not self.holdFire then self:fireMissile() end end
    if not U.button.fire then self.holdFire = false end
    self.radians=self.radians-Gravity.x*.2
    self:actualShipMove()
end

@west very interesting. use of physics is creative. does have a nice feel. stardust is neat.

… what do the comments in draw mean about executed 3rd, 2nd, 1st?

@dave1707 interesting. i nearly like it. precise control is difficult, but definitely interesting.

@RonJeffries the execution comments were a reminder for me about the order. Starting from the sprite function you read upwards and apply each of the translate/rotate commands. So translate followed by rotate followed by sprite will rotate then translate the sprite (relative to the 0,0 origin). Not a translate followed by a rotate as one might expect by reading it sequentially line by line. Hope this makes sense?

ah. i guess i’m used to thinking in the forward direction, years of math and such. i get your point., interesting way of thinking of it.

@Ronjeffries - just loaded v26, nice touches control much better and sound effects really raise the temperature. Sound effects accelerate as asteroids destroyed but when the asteroids have disappeared the sound effects continue. Need a check for level complete. Also, can’t remember original, diary - book trip to YouTube, but are there a number of lives displayed before you are kicked out of space? Something like three ships at the top of the screen disappearing after each blammo!!! ?

@RonJeffries - woaah, just visited youtube. Atari vid, forgot about the alien spaceship. Mods for another day.

Yes new waves are needed, as are the large and small ships. I assume I’ll do them in order. The lives are to be shown as a line of ships up by the score. And you win more ships every 10,000 points or something. I don’t think I’ll ever get there. And other details: missiles fire from the middle of the ship; ship kill radius is too small, and probably other things I’ve forgotten.

Yes, I do think the sound helps. The version I’ll push in a couple of minutes has stereo. All the moving sounds have a stereo effect, which is kind of fun and about the level of complexity I could deal with on days like these.

I may not announce #27 on Twitter, in honor of people who have more important things on their mind, but it should be up within the next few minutes.

@RonJeffries I was playing around with ship controls and I came up with this. Slide your finger up or down (lower 3/4 of the screen) to rotate the ship. Tap Fire to fire missiles. Tap or hold Go for ship acceleration. Tap between Fire and Go to both fire a missile and accelerate. After playing awhile, I was able to zoom around the screen fairly easily and fast, shooting missiles. The buttons could be setup on both sides so it doesn’t matter which hand you want to control rotation and Fire/Go.

displayMode(FULLSCREEN)

function setup()
    rectMode(CENTER)
    p=vec2(WIDTH/2,HEIGHT/2)
    dx=0
    shipAng=0
    s=vec2(0,0)
    v=vec2(0,0) is 
    mTab={}
    fill(255)
end

function draw()
    background(141, 131, 131)
    pushMatrix()
    translate(p.x,p.y)
    rotate(shipAng-90)

    sprite(asset.builtin.Tyrian_Remastered.Ship_D,0,0,30)
    if go then
        rotate(180)
        sprite(asset.builtin.Tyrian_Remastered.Flame_2,0,30,10)
        shipVel=vec2(.1,0):rotate(math.rad(shipAng))
        v=v+shipVel
    else
        v=v*.99
    end
    p.x=(p.x+v.x)%WIDTH
    p.y=(p.y+v.y)%HEIGHT
    shipAng=shipAng+dx
    popMatrix()
    
    for a,b in pairs(mTab) do
        sprite(asset.builtin.Tyrian_Remastered.Mine_Spiked,b.x,b.y,8)
        b.x=b.x+b.z
        b.y=b.y+b.w 
        if b.x<0 or b.x>WIDTH or b.y<0 or b.y>HEIGHT then
            table.remove(mTab,a)
        end
    end

    fill(255)
    rect(WIDTH-100,HEIGHT-100,50,50)
    rect(WIDTH-100,HEIGHT-200,50,50)
    fill(0)
    text("Fire",WIDTH-100,HEIGHT-100)
    text("Go",WIDTH-100,HEIGHT-200)
end

function touched(t)
    if t.state==BEGAN then
        -- fire
        if t.x>WIDTH-125 and t.x<WIDTH-75 and t.y>HEIGHT-175 and t.y<HEIGHT-75 then
            misVel=vec2(6,0):rotate(math.rad(shipAng))
            table.insert(mTab,vec4(p.x+misVel.x*3,p.y+misVel.y*3,
                        misVel.x+v.x,misVel.y+v.y))
        end
        -- go
        if t.x>WIDTH-125 and t.x<WIDTH-75 and t.y>HEIGHT-225 and t.y<HEIGHT-125 then
            goId=t.id
            go=true
        end
    end   
    -- rotate
    if t.state==CHANGED and t.y<HEIGHT*.75 then
        rId=t.id
        dx=t.deltaY
    end
    
    if t.state==ENDED then
        if t.id==goId then
            go=false
        end
        if t.id==rId then
            dx=0
        end
    end   
end

that’s pretty nice, i’ll play a bit and see how it feels!

in other news, i didn’t realize mod worked that way. nice.

@RonJeffries - haven’t had much time recently to play, illness in the family, so this has been slow in production. It’s just an example of the controls I posted about earlier, just an idea which combines the power and direction controls for the ship - with a large area for the firing button. Still have some ideas to check but will be a while forthcoming.

One thing, with not having used it for years I struggled to get the sound operating effectively, code below fires up errors so any ideas around that would be appreciated.

I think the controls idea is fairly obvious

displayMode(FULLSCREEN)
function setup()
    --
    assert("Please ensure your sound is enabled")
    cW,cH = WIDTH/2, HEIGHT/2
    angle = -180
    padLoc = vec2(100,140)
    padSize = 180
    goSize = 80
    go = false
    padCheck = padSize/2
    goCheck = goSize/2
    bpX,bpY,bpS = 172,141,32
    butPos = vec3(bpX,bpY,bpS)
    fire = vec4(WIDTH-180,180,240,240)
    ship = readImage(asset.builtin.Space_Art.Red_Ship)
    trail = readImage(asset.builtin.Tyrian_Remastered.Flame_1)
    bang = asset.documents.Sounds.fire
end

function draw()
    -- 
    background(40, 40, 50)
    pad()
    pushMatrix()
        translate(cW,cH)
        rotate(angle+90)
        if go == true then
            sprite(trail,-0,-60,-20)
        end
        sprite(ship,0,0)
        rotate(90)
    popMatrix()
    rectMode(CENTER)
    noFill()
    stroke(254, 63)
    rect(fire.x,fire.y,fire.z,fire.a)
    collectgarbage()
end

function touched(touch)
    --
    checkX = math.abs(touch.pos.x - padLoc.x)
    checkY = math.abs(touch.pos.y - padLoc.y)
    checkGX = math.abs(touch.pos.x - goCheck)
    checkGY = math.abs(touch.pos.y - goCheck)
    if touch.state == CHANGED then
        if math.abs(touch.pos.x - padLoc.x) < goCheck and 
           math.abs(touch.pos.y - padLoc.y) < goCheck then
            print(touch.pos.x - padLoc.x)
            go = true
        else
            go = false
        end
        if checkX < padCheck and checkGX > 0 then
            if  checkY < padCheck and checkGY > 0 then
                angle=math.deg(math.atan2(padLoc.y-touch.pos.y,padLoc.x-touch.pos.x))
            end
        end
    end
    if touch.state == ENDED then
        if isInside(touch.pos) == true then
            sound(bang,0.5)
        end
    end
end

function pad()
    --
    strokeWidth(2)
    stroke(255, 109)
    noFill()
    ellipseMode(CENTER)
    ellipse(padLoc.x,padLoc.y,padSize)
    ellipse(padLoc.x,padLoc.y,goSize)
    pushMatrix()
        translate(padLoc.x,padLoc.y)
        rotate(angle)
        ellipseMode(CENTER)
      --  sprite(asset.builtin.Space_Art.UFO ,padLoc.x-170,0,32)
        ellipse(padLoc.x-170,0,butPos.z)
    popMatrix()
    font("AmericanTypewriter-Bold")
    fontSize(48)
    fill(253, 66)
    text("Go",padLoc.x,padLoc.y)
end

function isInside(t)
--
  if t.x > fire.x and t.y > fire.y then
       if t.x < fire.x+240 and t.y < fire.y+240 then
            return true
        else
            return false
       end
  end
end

i think you’ll find that calling collectgarbage is undesirable.

it’s an interesting idea, i’m finding it hard to work, but could just be me.larger pad might help me. i agree playing with just thumbs would be good. i might try plugging it in. also maybe make controls easier to modify for experimenters. thanks!

@RonJeffries - oops, left the garbage collection in whilst I was having issues with Codea. I operate by cycling my left thumb on the small inner circle for rotation, and just drag back/forward/sideways to fire up the engines. Changing dimensions may help, smaller inner ‘Go’ circle.

The large catchment area on the right, for fire, is easier to use.

Yes. it’s an interesting idea. About the only thing I haven’t tried is firing by shouting “FIRE!”.

Here’s something I have but I don’t remember exactly who wrote it. Maybe they’ll post and take credit. But you rotate the ship with a thumb and finger on the screen like you’re turning a knob.

displayMode(FULLSCREEN)

function setup()
    touches = {}
    touchpt = {}
    angle = 0
    sangle = 0
    iangle = 0
end

function draw()
    background(40,40,50)
    if touches[2] then
        local v = touchpt[touches[2]] - touchpt[touches[1]]
        if pangle then
            pangle = bratan2(v,pangle)
            angle = iangle + pangle - sangle
        else
            pangle = math.atan2(v.y,v.x)
            sangle = pangle
            iangle = angle
        end
    end
    translate(WIDTH/2,HEIGHT/2)
    rotate(math.deg(angle))
    sprite(asset.builtin.Tyrian_Remastered.Plane_Boss,0,0)
end

function touched(touch)
    if touch.state == ENDED then
        touches = {}
        touchpt = {}
        pangle = false
    else
        if touch.state == BEGAN then
            table.insert(touches,touch.id)
        end
        touchpt[touch.id] = vec2(touch.x,touch.y)
    end

end

function bratan2(v,a)
    local b = math.atan2(v.y,v.x)
    b = b + math.floor((a - b)/(2*math.pi) + .5)*2*math.pi
    return b
end

@dave1707 - neat but I was hoping to reduce the number of digits needed, you’d need three to operate in that way. Could be a good way of twisting gears in a grid system - like one of those solve the puzzle path games.

interesting. still think keyboard would work best. going to try bri’s variation next, i think i can plug it in quickly on top of west’s, which i have running.

Just for fun, here is a small racetrack to provide a benchmark for evaluating the controls. Time taken to light all the stars and number of collisions with walls might provide metrics to compare and contrast. Very rough and ready and no test of shooting (bullets go through everything).

Marker in the sand: I’m getting around 45s with about 9 bumps to complete the course