Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Developing Asteroids: a series.

in Examples Posts: 659

I've been developing a little homage to the 1979 arcade game, Asteroids, in Codea. I'm doing a series of articles, up to #17 right now and not done, describing how I build up a program like this. I've intentionally started in a procedural way, as one often does, and only now is the program beginning to move to a more object-oriented style.

Unlike many "how to" articles, my articles show the trials I make, the mistakes, the recoveries, all the things that happen to real programmers when we build real programs. I hope that folks interested in Codea will find this series useful.

Comments and questions are welcome.

https://ronjeffries.com/categories/asteroids/

«1

Comments

  • edited May 25 Posts: 1,757

    @RonJeffries - been following your development of Asteroids with interest, like to see how real coders approach projects. I never seem to be able to finish mine, hit a snag, shelve and start a new project. Trying to get round that now.

    I have made a few mods to your project on my pad - deleted the testAsteroids tab. Moved and modified the score display (just personal preferences).


    function drawScore() local s= "000000"..tostring(Score) s = string.sub(s,-5) fill(255, 253, 0) fontSize(32) text(s, WIDTH/2, HEIGHT-60) end

    Slightly modified the drawAsteroid() function to partially hide the circle showing the asteroid size by making it more transparent (still retained it for development).


    function drawAsteroid(asteroid) pushMatrix() pushStyle() translate(asteroid.pos.x, asteroid.pos.y) stroke(253, 41) ellipse(0,0,2*killDist(asteroid)) scale(asteroid.scale) stroke(255) strokeWidth(1/asteroid.scale) for i,l in ipairs(asteroid.shape) do line(l.x, l.y, l.z, l.w) end popStyle() popMatrix() end

    Also, I increased the stroke and changed the colour on the ship to make it more prominent - just my preference.

    I know these deviate from the original, but I think they do lift it a bit. Are you intending explosion of the ship on collision with asteroids?

    Will continue to follow this, very neat and very professional. Thanks.

    p.s. made me go back to my own thread with space invaders, which I hoped would suck in ideas, suggestions and code ideas. Must finish that!!!

    p.p.s - not your code, but I have an issue with buttons near the iPad screen edge, dragging in other windows - distracts the concentration of us space pioneers dealing with the asteroid storm!!! I have raised this with @Simeon to see if we can switch out some iOS features to avoid this.

  • Posts: 659

    i'm glad you're finding value. i plan to build in most of the function of the original, so, yes, the ship will explode when hit by an asteroid. i removed the circle entirely this afternoon, as it had served its purpose.

    the ship is not drawn properly so will be changed to better match the original. making it a bit more visible is probably a good idea.

    one challenging bit may be the sounds, either emulating them or ideally getting the originals somehow.

    questions and comments are welcome!

  • Posts: 789

    @RonJeffries very interesting blog - only just come across it now so have a backlog to work through. Though you say its aimed at those new to Codea - I’ve been using it for years and have taken a lot from it already.

    Here’s a zip of a step by step tutorial I put together 6 or so years ago for asteroids. It’s been interesting going back over it (and seeing a bunch of stuff which has been deprecated and I’m not sure that the step by step one from the github works anymore). One thing that jumped out at me was that when putting it together I focussed on getting the player’s ship up and moving then firing bullets before adding the asteroids. I note that you’re going for a depth first approach on the asteroids - getting them faithful to the original before moving on. Not a criticism, just an observation.

    The second zip is the game fleshed out a bit. Different asteroid types (custom graphics), different power ups, levels named after actual asteroids. Still very rough (and from a few years back). Like @Bri_G I don’t tend to finish things, but this was one of my more complete efforts.

    Anyway, just thought some of it might spark some food for thought.

    Looking forward to the rest of the series

  • Posts: 659

    Thanks for the look and feedback. I did the asteroid shapes mostly because I was studying the 6502 code for the original and decided to try to draw the asteroids to check my understanding, so I went at it when my mind was (relatively) fresh. In any reasonable sense, round ones would have been a good place to move on, coming back to refine the shapes later.

    Naturally, at some point, I plan to replace them with more attractive sprites, but shhh that's a secret, and I want to get close to the original first anyway.

    I'll have a look at your work but am trying not to contaminate my mind, so that the mistakes I make (and always document) are not made less likely by seeing something sensible. :smile:

    Thanks!

  • Posts: 1,757

    @West - nice package, very good finish. Sure I saw your tutorials before but working through them slowly now. Thanks.

  • Posts: 659

    The tab trick is interesting but deep in the guts.

  • Posts: 789

    @RonJeffries the tab trick was something another forum member passed on way back - it was trying to convey the slow build up of a program in a self contained package, however I do think blog posts allow for much more depth

    Thanks @Bri_G ther should be lander and snake somewhere, but not sure if they still work

  • Posts: 659

    Yes, I saw that you had borrowed the tab trick. I must try to figure out how it works one of these first days. :smile:

  • Posts: 1,757

    @RonJeffries - think kill the ship killed the score.

  • dave1707dave1707 Mod
    Posts: 8,464

    In the drawScore function, change self.score to Score.

  • edited May 27 Posts: 1,757

    @dave1707 - thank ‘ee your a gent!! Now when the ship goes blammo I get a 20 point lift in my score. Now checking for collisions to see where the score increase comes from.

    Edit: looks like it comes from the asteroid score, so when you go blammo the asteroid takes damage but the ship is splatted.

  • Posts: 659

    ah. prob should be fixed the other way, i'll treat it as a bug report for next time. thanks! i wonder how to prevent these glitches.

  • dave1707dave1707 Mod
    Posts: 8,464

    @RonJeffries You can’t prevent glitches, you can only do a lot of testing to find and fix them. Question: If you have a bug in your code and nobody finds it, is it still a bug.

  • edited May 27 Posts: 1,757
    @dave1707 - remeniscent of the 'if a tree fell...' or more lately, from Terry Pratchett - "If an acorn fell ..." which is more relevant to us!!!

    @RonJeffries - the answer to your question is play, play play!! Or, more sensibly, hand over the bug testing to a lot of willing hands/digits/minds - US !!!! Thanks.
  • Posts: 659

    better microtests would help a lot, and i've not been doing a good job of that. it's tricky with games.

  • Posts: 1,757
    @RonJeffries - microtests? Is this testing small routines in isolation?
  • Posts: 659

    not precisely. but small test, taking short time to write first, short time to make run next, then nailing that bit down thereafter

  • Posts: 1,757
    @RonJeffries - aha, so correct me if I'm wrong. You are building a skeleton of small routines then concentrating on one to develop required features within it. Then moving onto next routine and ensuring overall compatibility.
  • Posts: 659

    not quite. too tricky to fully explain here. Basically "Test-Driven Development" is the root discipline but in the case in hand, I've not been doing full TDD, for reasons, but using it sometimes. It can be used to test-drive anything, just that the tests are small steps as are the implementations. If you read my articles on Asteroids, you'll see that everything I do is in very small chunks which I make work before doing another chunk. With decent micro tests, each chunk would also have a tiny test to show / confirm that it works. Normally I always do that, but with a graphical game I don't see quite how to do it effectively yet.

  • edited May 28 Posts: 659

    A question. It's a pain to copy / paste the code from my web site, as it is large. I could save it as a text file to click on, or as a zip file. I'm not quite up to doing a magic copy-to-clipboard function, as the code is heavily formatted on the site.

    Which of text file or zip file would be easier for someone loading into Codea? I only know how to load a text file into Codea: is there even a way to load a zip? What third way might be better?

    Thanks!

  • edited May 28 Posts: 1,757

    @RonJeffries - you can export your Codea file as a zip and then post that. Codea users can then open the zip, using the iOS system, with Codea. That should ease any issues with copy/paste.

    p.s. I approach development in a similar manner, but - having a large amount of code accumulated of years of playing with Codea I tend to make shortcuts by building on existing code and refining. I must say often running into many issues, but then again it’s all learning.

  • Posts: 659

    how does one open a zip with codea?

  • Posts: 1,757
    @RonJeffries - provided the zip is an exported project from Codea - a long press on the zip file will bring up the sharing dialogue from iOS. Along the top is a row of icons relating to apps which may be able to open the zip. Codea should be one of them, select it and Codea is opened to import the file. If Codea is not in the top list then the far right icon allows you to select from a further list of apps (vertically displayed list) Codea may be in that list

    Failing that under the icon row iOS shows a text list of options, one of which is Open With - that should get you there.

    I find the whole iOS filing a bit of a mess but you can usually find an option that works.

    The sharing list can be configured, up to a point, through iOS.

    Oh, by the way it depends on which iOS version you are running.
  • Posts: 659

    Ah, good. Odd that to import a paste you long-press the + that adds a project and that that does NOT bring up a file accessor. Thanks!

  • edited May 29 Posts: 789

    @RonJeffries worked my way through the blog - really useful and informative. Two major takeaways for me so far:

    • Now using working copy - I would always get the FOBC (fear of breaking code) if I wanted to change stuff - and would copy and comment out working sections as a backup - unsustainable so glad I've found a painless alternative

    • Splats[self] = self : An alternative to the way I normally manage objects (which was sub optimal and messy) - still am not 100% comfortable with it but getting there

    Really looking forward to seeing where you take it

  • Posts: 659

    Hi @West Yes, working copy is quite good, if a bit hard to set up. It has way more than I ever use and seems quite solid.

    Yes, the Splats[self] thing is odd but seems to work more nicely than updating an array. I imagine odd things can still happen if you nil out one while looping but so far I've not seen a timing issue arise.

    I look forward to seeing where I go too. :) It's certainly fun.

  • Posts: 659

    so, i pretty much hate the controls on this game. was thinking about whether i could interpret swirling one or two fingers on the left as a turn, etc. there doesn't seem to be a lot of support for such a thing. ideas? thanks!

  • Posts: 1,757
    @RonJeffries - there is a virtual joypad that has been used many times for ages. That could be a rotation control.

    A 'largeish' area near the bottom right corner could take a tap for fire, doesn't have to be a small circle. One thumb on the right and the other on the left could be an easy option.

    Alternatively you could reduce screen size and place controls in a panel at the bottom.

    Then again you could add mouse input with the new iOS features.
  • dave1707dave1707 Mod
    Posts: 8,464

    @RonJeffries You could do something like this to rotate the ship. Just slide your finger right or left. You can add whatever restrictions you want on it.

    displayMode(FULLSCREEN)
    
    function setup()
        ang=0    
    end
    
    function draw()
        background(0)
        translate(WIDTH/2,HEIGHT/2)
        rotate(ang*2)
        sprite(asset.builtin.Tyrian_Remastered.Boss_D,0,0)
    end
    
    function touched(t)
        if t.state==CHANGED then
            ang=ang-t.deltaX
        end    
    end
    
  • Posts: 659

    @dave1707 thanks, i was thinking about something similar as we had supper, may give that a go.

    @Bri_G i guess i could wait for mouse and pad support ... :smile:

    thanks!

  • Posts: 789

    How about a multitouch control? Very short duration touches (taps) fires bullets. For longer touches the start of the touch acts as an anchor and the angle relative to the start controls the angle of the ship. A second touch while this is active would turn on the boosters applying a force at whatever direction the ship was pointing.

    I’ve not had a look at the new gestures, which may make things simpler, but I’ve always relied on writing my own touch trackers. Let us know if you want me to share (though appreciate you are looking to figure things out by yourself for the purposes of the blog)

  • Posts: 659

    i like that idea of the "fixed" point and rotating around it. skips over the issue of recognizing something like a winding motion. thanks! got a few things to try now ...

  • Posts: 1,757
    @RonJeffries - I envisage a broad circle with a smaller circle within which adheres to the edge, you can rotate the inner circle within giving the direction of ship.

    Alternatively you could have a pointer from the centre to the edge.
  • Posts: 659

    @Bri_G Playing the game, I'm finding that a lot of my trouble is keeping my fingers in the designated areas. The circle idea seems to offer more of that trouble, though maybe I just don't understand the idea.

    For now, I'm going to try a simplified version of @West 's idea.

  • edited May 30 Posts: 1,757
    @RonJeffries - I find problems with touch controls, particularily with touches close to the screen limits - due to iOS sliding in other screens.

    A crucial feature is how you hold your iPad. Do you place it on a flat surface? Hold it in one hand and control with another? Or, hold it on your knee with fingers behind lower corners and thumbs for use on controls. I usually favour the latter.

    May be a good idea to generate a range of options to cover most eventualities in future projects.
  • edited May 30 Posts: 659

    my original plan was borrowed from my spacewar program, where you laid the ipad down and worked from opposite sides. for asteroids, more often i hold it in one hand on my lap. too many buttons for thumb only, i think.

    agreed that there may need to be options. after trying a few this morning (boring article coming soon) i've found nothing i really love. bigger buttons is the next release. :smile:

  • Posts: 789

    How about something like this?


    -- Tocuh based asteroid controls -- by West displayMode(FULLSCREEN) function setup() touches={} tsup={} --tsup contains the supplementary info about the start position of the touch pos=vec2(WIDTH/2,HEIGHT/2) angle=0 end -- This function gets called once every frame function draw() -- This sets a background color background(32, 39, 75) fill(100) text("Tap to fire. Touch and move to rotate. Second touch to boost",WIDTH/2,HEIGHT*0.9) noStroke() --pretty rough touch counter - also no consideration given to touch order local touchcount=0 for i,t in pairs(touches) do touchcount = touchcount + 1 if touchcount==1 then fill(255) stroke(255) --anchor ellipse(tsup[i].tstartx,tsup[i].tstarty,10) ellipse(t.x,t.y,10) strokeWidth(2) line(tsup[i].tstartx,tsup[i].tstarty,t.x,t.y) --might want to limit this if the current touch is too close to the anchor angle=math.deg(math.atan2(tsup[i].tstarty-t.y,tsup[i].tstartx-t.x)) else --boost pushMatrix() translate(pos.x,pos.y) rotate(angle+90) translate(0,-50) sprite(asset.builtin.Tyrian_Remastered.Flame_2,0,0) popMatrix() end end pushMatrix() translate(pos.x,pos.y) rotate(angle+90) sprite(asset.builtin.Tyrian_Remastered.Boss_D,0,0,50) popMatrix() end function touched(touch) if touch.state==MOVING then --record path if tsup[touch.id]~=nil then table.insert(tsup[touch.id].path,{pos=vec2(touch.x,touch.y),age=ElapsedTime}) end end if touch.state==ENDED or touch.state==CANCELLED then processTouch(touch) touches[touch.id] = nil tsup[touch.id]=nil else touches[touch.id] = touch --if there is no supplementary info associated with the current touch then add it if tsup[touch.id]==nil then tsup[touch.id]={tstartx=touch.x,tstarty=touch.y,starttime=ElapsedTime,path={}} end end end function processTouch(touch) if ElapsedTime-tsup[touch.id].starttime<0.2 then --FIRE!! sound(SOUND_SHOOT, 47516) --you may want a check to see if this is a second or subsequent touch - taps with an exisitng touch might want to be interpreted as boosts end end
  • Posts: 659

    interesting idea and implementation, use of tables. seems to be just about impossible to fire without turning. my main issue is that the original Asteroids did not have positive angular control, just constant rate of rotation. one of my experiments this morning used a different but equivalent implementation of the touch-hold idea to just trigger constant-rate turning, and i didn't find it very playable. some may disagree. thanks!

  • Posts: 789

    How about segmenting the leftmost part of the screen as a giant fire button (say touch.x<width*0.15 then fire). Remainder of screen use the rotation control provided anchor point starts to the left of the fire button. Should get round the turning when you want to fire.

  • Posts: 789

    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
  • dave1707dave1707 Mod
    edited May 31 Posts: 8,464

    @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
    
  • edited May 31 Posts: 659

    @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?

  • Posts: 659

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

  • Posts: 789
    @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?
  • Posts: 659

    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.

  • Posts: 1,757

    @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!!! ?

  • edited May 31 Posts: 1,757

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

  • Posts: 659

    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.

  • dave1707dave1707 Mod
    Posts: 8,464

    @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
    
  • Posts: 659

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

Sign In or Register to comment.