Skip to content

233. Angry Birds – starting to look good

September 4, 2015

This post takes our game a long way. We not only get to shoot a bird at some pigs, but organise all the code to make things easier as they get more complicated.

Code organisation

The first thing is to organise the code so it doesn’t become a mess. I’ve set up four tabs

  • Main – for controlling the game
  • Levels – for creating and loading levels
  • Objects – for creating the game objects, eg the images
  • Utilities – for generally useful code

Main tab

In Main, I have the functions which Codea automatically runs for me

function setup()
    Settings() 
    LoadImages()
    SetupPigs() --set up different types of pig
    --(I will need to set up different birds, too (but not yet)
    CreateScene() --load level
end

function draw()
    DrawScene() --game objects
    DrawText()  --scores etc
end

function collide(c) --adjusts health of colliding objects
    AdjustHealthState(c.bodyA,c.normalImpulse)
    AdjustHealthState(c.bodyB,c.normalImpulse)
end

function touched(t) --shoots bird at pigs
    if t.state==ENDED then
        birds[1].body:applyLinearImpulse(vec2(10,10))
    end
end

I try to keep all these functions as clean and simple as possible, and put the detail into other functions, which makes the program easier to read and to test and debug.

The touched function

For our demo, we just need to give the bird a push, which we do with applyLinearImpulse.

Note that it’s called an impulse because it happens just once, whereas if you use the alternative physics command applyForce, it spreads the force over some time. As an example, if you hit a tennis ball, applyForce moves your arm into position using continuous force for a second or two, but when you hit the ball, the ball feels the impact at one moment, with applyLinearImpulse.

Anyway, an impulse is what we need here. It  consists of a vec2 telling Codea how fast to travel in the x and y directions. I chose vec2(10,10) because I want a 45 degree angle (ie x and y have the same positive value), and I chose 10 so that the ball would land nicely on the sticks. Later, players will choose the direction and speed themselves, of course.

Objects tab

This only contains two functions. One you have seen before, which reads in the images, and the other just sets up a little table for the pigs, mainly to store their (starting) health levels.

function SetupPigs()
    local p={}
    p["king"]={name="king",health=1000}
    p["mustache"]={name="mustache",health=600}
    p["medium"]={name="medium",health=400}
    p["small"]={name="small",health=150}
    p["tiny"]={name="tiny",health=50}
    p["helmet"]={name="helmet",health=750}
    pigTypes=p
end

Utilities tab

This tab currently has two functions which create and draw rectangular blocks which we can use in our scene. They can also  have a health score, so they can break if damaged enough.

function CreateBlock(x,y,w,h,health,c,r)
    local b=physics.body(POLYGON,vec2(0,0),vec2(w,0),vec2(w,h),vec2(0,h))
    b.x,b.y=x,y
    c=c or color(170, 118, 103, 255)
    b.restitution=r or 0.4 --bounciness
    b.health=health
    return {body=b,w=w,h=h,color=c}
end

function DrawBlock(p)
    local b=p.body
    pushMatrix()
    translate(b.x,b.y)
    rotate(b.angle)
    fill(p.color)
    rect(0,0,p.w,p.h)
    popMatrix()
end

Levels tab

This tab can contain data for multiple game levels, and code to load a chosen level. At the moment, it just loads a simple test level.

In CreateBirds, I set linearDamping so the bird doesn’t roll forever when it hits the ground.

The CreatePigs function uses the CreateAPig function to make three pigs. The ResetCounter function in there simply resets the timer that switches the animation images (adding a random number so the pigs don’t all animate at the same time), and it is used mostly when a pig’s health state changes during the game.

The state variable in CreateAPig will be 1, 2 or 3 (undamaged, one black eye, two black eyes) depending on the health of the pig. When a pig collides with something, I calculate its health divided by its original health, and if it is between 2/3 and 1, the state is 1 (undamaged), between 1/3 and 2/3, the state is 2, and if lower, the state is 3. You’ll see a couple of pigs getting bruised in the video above.

function CreateScene()
    --floor
    floorHeight=200
    wall=physics.body(EDGE,vec2(0,floorHeight),vec2(WIDTH,floorHeight))
    wall.restitution=0
    --objects
    objects={CreateBlock(600,200,10,100,500), CreateBlock(800,200,10,100,500),
             CreateBlock(600,300,210,8,500)}
    CreateBirds()
    CreatePigs()
end

function CreateBirds()
    birds={}
    local b={}
    b.type="redBird"
    b.size=images["redBird"].width*imgScale
    b.body=physics.body(CIRCLE,b.size/2)
    b.bodylinearDamping=3
    b.body.x,b.body.y=90,200
    birds[1]=b
end

function CreatePigs()
    pigs={CreateAPig("king",650,200),
          CreateAPig("small",620,315),
          CreateAPig("medium",750,200)}
end

function CreateAPig(name,x,y)
    local p={}
    p.type=name
    p.size=images[name][1][1].width*imgScale 
    ResetCounter(p)
    p.body=physics.body(CIRCLE,p.size/2)
    p.body.linearDamping=3
    p.body.health=pigTypes[name].health
    p.body.fullHealth=p.body.health
    p.body.x,p.body.y=x,y
    p.body.state=1
    p.body.states=#images[name][1]
    return p
end

function ResetCounter(p)
    p.img=1
    p.nextAnim=ElapsedTime+animDelay-math.random()*0.5
end

Main tab (again)

The Main tab has other functions, too

function DrawScene()
    background(200, 214, 220, 255)
    for _,p in pairs(objects) do DrawBlock(p) end
    DrawBirds()
    DrawPigs()
    DrawScenery()
end

--adjusts health of an object after a collision
--if the object is animated (eg pigs), calculates which
--set of images to use, by setting state variable
function AdjustHealthState(b,d)
    if b.health then b.health=b.health-d*10
        if b.state then
            b.state=math.min(b.states,math.ceil(b.states*(1-b.health/b.fullHealth)))
        end
    end
end

function DrawPigs()
    for i,p in pairs(pigs) do
        local j=images[p.type][p.body.state]
        pushMatrix()
        translate(p.body.x,p.body.y)
        rotate(p.body.angle)
        sprite(j[p.img],0,0,p.size)
        popMatrix()
        --go to next animation image based on timer
        if ElapsedTime>p.nextAnim then
            p.img=p.img+1
            if p.img>#j then p.img=1 end
            p.nextAnim=ElapsedTime+animDelay
        end
    end
end

function DrawBirds()
    for i,b in pairs(birds) do
        pushMatrix()
        translate(b.body.x,b.body.y)
        rotate(b.body.angle)
        sprite(images[b.type],0,0,b.size)
        popMatrix()
    end
end

The DrawText function, which I haven’t shown above, prints the health scores of all the objects at top left of the screen. I’m currently using this just to help me set health levels for the different pigs and objects in the scene. Later on, this function, will show useful information for the player.

The Code

The code is here. Don’t forget to use a “long press” (press on Add New Project in Codea until you get the option to copy into new project) to get the code put into separate tabs for you.

 

Advertisement
3 Comments
  1. Il have a problems

  2. Main:5: attempt to call a nil value (global ‘Settings’)
    stack traceback:
    Main:5: in function ‘setup’
    Main:30: bad argument #1 to ‘pairs’ (table expected, got nil)
    stack traceback:
    [C]: in function ‘pairs’
    Main:30: in function ‘DrawScene’
    Main:13: in function ‘draw’

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: