233. Angry Birds – starting to look good
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.
Il have a problems
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’
Help me