Share Your Code

How to make an Angry Birds catapult

Posted by Fixdit, Posted on May 18, 2011, Last updated January 25, 2012

GitHub URL: 
https://github.com/Fixdit/Hot_Cross_Bunnies-Tutorial

Catapults like that found in Angry Birds and Hot Cross Bunnies are a staple weapon in the arsenal of any mobile gamer. We thought we'd share with you how to put together a slingshot catapult that looks great and feels authentic.

This walkthrough is intended to help you pick the bulk of the catapult code apart and understand how it works. The article will imply that you have prior working knowledge of mobile development and CoronaSDK. The accompanying files are well commented and FREE to download, so you should have everything you need to put together a catapult of your own for your next hit app!
Step 1: The setup
Lets first get a look at what we've got in the project files.

  1. Download the project from here
  2. Open CoronaSDK
  3. Go to the top menu File > Open and navigate to the unzipped Hot_Cross_Bunnies-Tutorial directory. Select main.lua.

Predictions for 3D graphics on the web in 2011
Opening up in the simulator you should be able to click down and pull back on the projectile in between the bunny's ears and then release to fire it. Once you've had a play around, lets take a look at what's going on under the hood.
Step 2: The structure
Our catapult consists of:

  • Two catapult struts, one in front of the projectile and one behind.
  • The band of elastic attached to the top of the struts which cradles the projectile.
  • The projectile to sit between the foreground and background strut.
  • The elastic stretch and release sounds.

There are a number of .lua files inside the Hot_Cross_Bunnies-Tutorial folder, but we just need to concentrate on explaining one of them in detail and how it controls the elements listed above.
Step 3: The code
There are 3 main functions we're interested in inside 'main.lua':

projectileTouchListener(e)
which listens for the player touching the projectile and handles it's movement before being released.

spawnProjectile()
is a function called when you want to generate a new bullet. It handles each new projectile's set-up.

state:change(e)
is a pretty standard function and an extremely useful way to monitor events that take place in your game. We'll use this to monitor the release of each projectile.

Breaking down the code, it's always useful to explain in the order in which it'll be processed. In this case it all starts at the top. First we set variables, import external classes and build all the catapult elements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
-- Global vars set up
_W = display.contentWidth;
_H = display.contentHeight;
 
display.setStatusBar( display.HiddenStatusBar )
 
m = {}
m.random = math.random;
 
local state = display.newGroup();
 
-- Imports
local movieclip = require("movieclip");
local physics = require("physics");
physics.start();
-- Import projectile classes
local projectile = require("projectile");
 
-- Variables setup
local projectiles_container = nil;
local force_multiplier = 10;
local velocity = m.random(50,100);
 
-- Visible groups setup
local background = display.newGroup();
local slingshot_container = display.newGroup();
 
-- Build the catapult
-- Front strut
local slingshot_strut_front = display.newImage("images/slingshot_strut_front.png",true);
slingshot_strut_front.x = 210;
slingshot_strut_front.y = _H - 25;
-- Back strut
local slingshot_strut_back = display.newImage("images/slingshot_strut_back.png",true);
slingshot_strut_back.x = 210;
slingshot_strut_back.y = _H - 25;
 
-- Animated bunny eyes
bunny_eyes = movieclip.newAnim{"images/bunny-eyes-1.png", "images/bunny-eyes-2.png", "images/bunny-eyes-3.png"};
bunny_eyes.x = 228;
bunny_eyes.y = _H + 10;
 
-- Move catapult up
slingshot_container.y = -25;
 
local state_value = nil;
 
-- Audio
local shot = audio.loadSound("sounds/band-release.aif");
local band_stretch = audio.loadSound("sounds/stretch-1.aif");
 
-- Transfer variables to the projectile classes
projectile.shot = shot;
projectile.band_stretch = band_stretch;
 
-- Background image
local bg_image = display.newImage("images/bg-default.png",true);
background:insert(bg_image);

Moving to the bottom lines of code in the main.lua file, we call the spawnProjectile function:

1
2
3
4
5
6
-- Tell the projectile it's good to go!
projectile.ready = true;
-- Spawn the first projectile.
spawnProjectile();
-- Create listnener for state changes in the game
state:addEventListener("change", state);

spawnProjectile indexes the catapults struts around the new projectile and adds a touch listener it to make it interactive:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
--[[
 
SPAWN projectile FUNCTION
 
]]--
local function spawnProjectile()
 
        -- If there is a projectile available then...
        if(projectile.ready)then
        
                projectiles_container = projectile.newProjectile();
                -- Flag projectiles for removal
                projectiles_container.ready = true;
                projectiles_container.remove = true;
                
                -- Reset the indexing for the visual attributes of the catapult.
                slingshot_container:insert(slingshot_strut_back);
                slingshot_container:insert(projectiles_container);
                slingshot_container:insert(slingshot_strut_front);
                slingshot_container:insert(bunny_eyes);
                -- Reset bunny eyes animation
                bunny_eyes:stopAtFrame(1);
                
                -- Add an event listener to the projectile.
                projectiles_container:addEventListener("touch", projectileTouchListener);
                
        end
 
end

Everything is now ready and set-up for interaction and animation. The last function is linked to the projectile from spawnProjectile and is where we inject some life into the scene. We use 'if else' statements to split it up into appropriate stages. When the touch interaction on the projectile begins, we want to play the elastic band stretch audio, set the projectile as the focus and initiate the elastic band line drawing. When the projectile is moved and still the focus, we have two lines drawn each frame to mimic it's x and y coordinates. Once the touch is released and the event ends we play the shot audio, clean up the elastic band graphics and call the global 'state' listener with a 'fire' value. This spawns a fresh projectile and crates a loop in the code for the process of aiming and firing to begin again.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
--[[
 
projectile TOUCH FUNCTION
 
]]--
local function projectileTouchListener(e)
        -- The current projectile on screen
        local t = e.target;
        -- If the projectile is 'ready' to be used
        if(t.ready) then
                -- if the touch event has started...
                if(e.phase == "began") then
                        -- Play the band stretch
                        audio.play(band_stretch);
                        -- Set the stage focus to the touched projectile
                        display.getCurrentStage():setFocus( t );
                        t.isFocus = true;
                        t.bodyType = "kinematic";
                        
                        -- Stop current physics motion, if any
                        t:setLinearVelocity(0,0);
                        t.angularVelocity = 0;
                        
                        -- Init the elastic band.
                        local myLine = nil;
                        local myLineBack = nil;
                        
                        -- Bunny eyes animation
                        bunny_eyes:stopAtFrame(2);
                
                -- If the target of the touch event is the focus...
                elseif(t.isFocus) then
                        -- If the target of the touch event moves...
                        if(e.phase == "moved") then
                                
                                -- If the band exists... refresh the drawing of the line on the stage.
                                if(myLine) then
                                        myLine.parent:remove(myLine); -- erase previous line
                                        myLineBack.parent:remove(myLineBack); -- erase previous line
                                        myLine = nil;
                                        myLineBack = nil;
                                end
                                                        
                                -- If the projectile is in the top left position
                                if(t.x < 105 and t.y < _H - 165)then
                                        myLineBack = display.newLine(t.x - 30, t.y, 196, _H - 165);
                                        myLine = display.newLine(t.x - 30, t.y, 120, _H - 152);
                                -- If the projectile is in the top right position
                                elseif(t.x > 105 and t.y < _H - 165)then
                                        myLineBack = display.newLine(t.x + 10, t.y - 25, 196, _H - 165);
                                        myLine = display.newLine(t.x + 10, t.y - 25, 120, _H - 152);
                                -- If the projectile is in the bottom left position
                                elseif(t.x < 105 and t.y > _H - 165)then
                                        myLineBack = display.newLine(t.x - 25, t.y + 20, 196, _H - 165);
                                        myLine = display.newLine(t.x - 25, t.y + 20, 120, _H - 152);
                                -- If the projectile is in the bottom right position
                                elseif(t.x > 105 and t.y > _H - 165)then
                                        myLineBack = display.newLine(t.x - 15, t.y + 30, 196, _H - 165);
                                        myLine = display.newLine(t.x - 15, t.y + 30, 120, _H - 152);
                                else
                                -- Default position (just in case).
                                        myLineBack = display.newLine(t.x - 25, t.y, 196, _H - 165);
                                        myLine = display.newLine(t.x - 25, t.y, 120, _H - 152);
                                end
                                
                                -- Set the elastic band's visual attributes
                                myLineBack:setColor(214,184,130);
                                myLineBack.width = 8;
                                
                                myLine:setColor(243,207,134);
                                myLine.width = 10;
                                
                                -- Insert the components of the catapult into a group.
                                slingshot_container:insert(slingshot_strut_back);
                                slingshot_container:insert(myLineBack);
                                slingshot_container:insert(t);
                                slingshot_container:insert(myLine);
                                slingshot_container:insert(slingshot_strut_front);
                                slingshot_container:insert(bunny_eyes);
                                
                                -- Boundary for the projectile when grabbed                     
                                local bounds = e.target.stageBounds;
                                bounds.xMax = 200;
                                bounds.yMax = _H - 250;
                                
                                if(e.y > bounds.yMax) then
                                        t.y = e.y;
                                else
                                
                                end
                                
                                if(e.x < bounds.xMax) then
                                        t.x = e.x;
                                else
                                        -- Do nothing
                                end
                        
                        -- If the projectile touch event ends (player lets go)...
                        elseif(e.phase == "ended" or e.phase == "cancelled") then
                        
                                -- Open bunny eyes
                                bunny_eyes:stopAtFrame(3);
                                -- Remove projectile touch so player can't grab it back and re-use after firing.
                                projectiles_container:removeEventListener("touch", projectileTouchListener);
                                -- Reset the stage focus
                                display.getCurrentStage():setFocus(nil);
                                t.isFocus = false;
                                
                                -- Play the release sound
                                audio.play(shot);
                                
                                -- Remove the elastic band
                                if(myLine) then
                                        myLine.parent:remove(myLine); -- erase previous line
                                        myLineBack.parent:remove(myLineBack); -- erase previous line
                                        myLine = nil;
                                        myLineBack = nil;
                                end
                                
                                -- Launch projectile
                                t.bodyType = "dynamic";
                                t:applyForce((160 - e.x)*force_multiplier, (_H - 160 - e.y)*force_multiplier, t.x, t.y);
                                t:applyTorque( 100 )
                                t.isFixedRotation = false;
                                                                
                                -- Wait a second before the catapult is reloaded (Avoids conflicts).
                                t.timer = timer.performWithDelay(1000, function(e)
                                state:dispatchEvent({name="change", state="fire"});
                                
                                if(e.count == 1) then
                                        timer.cancel(t.timer);
                                        t.timer = nil;
                                end
                                
                                end, 1)
                                        
                        end
                
                end
        
        end
 
end

Thanks for reading, I hope you’ve found this useful! Please don't hesitate to contact Fixdit if you have any questions or require further support.


Replies

rmbsoft
User offline. Last seen 12 hours 47 min ago. Offline
Joined: 5 Apr 2011

Thank you so much for sharing this!

I will definitely use this in the future!

holmes2870
User offline. Last seen 1 year 9 weeks ago. Offline
Joined: 3 Feb 2011

I tried downloading from the Git repo but it's empty?

I got this error;

Not Found: Bad archive command for Fixdit/Hot_Cross_Bunnies-Tutorial/master

iWalter
User offline. Last seen 1 week 1 day ago. Offline
Joined: 11 Oct 2010

Download the package not the source files. The source files are empty. Look at the last line of the dialog box.

iWalter
User offline. Last seen 1 week 1 day ago. Offline
Joined: 11 Oct 2010

@Fixdit...thx for that code...awesome!

Fixdit
User offline. Last seen 13 weeks 1 day ago. Offline
Joined: 19 Jan 2011

Currently adding the source files to the repository so you can download using the .zip and .tar options. In the meantime you can indeed download the package from the 'Hot_Cross_Bunnies-Tutorial.zip' link.

UPDATE: The project is now available as source from GitHub: https://github.com/Fixdit/Hot_Cross_Bunnies-Tutorial

Many thanks,

Fixdit
http://www.fixdit.com/

ChunkyApps
User offline. Last seen 8 hours 38 min ago. Offline
Joined: 19 Jan 2011

Awesome guys! Thanks so much for your willingness to share. I'm off to purchase your game and show my support! Hopefully everyone does the same!

Antheor
User offline. Last seen 15 weeks 3 days ago. Offline
Joined: 22 Sep 2010

Thx for your great code.

Just to be sure : you use state:dispatch but you make no use of it in your sample code, right ?

Fixdit
User offline. Last seen 13 weeks 1 day ago. Offline
Joined: 19 Jan 2011

The state function is included and utilised in the full download code from GitHub.

Thanks Antheor,

Fixdit
http://www.fixdit.com/

d10brigade
User offline. Last seen 3 years 13 weeks ago. Offline
Joined: 30 May 2011
Antheor
User offline. Last seen 15 weeks 3 days ago. Offline
Joined: 22 Sep 2010

yes indeed ...
thx for your answer.

jakescarano
User offline. Last seen 1 year 8 weeks ago. Offline
Joined: 8 Apr 2011

Great code exchange! I was able to put in my own images very easily! Theres so much I can do with this!

r.kenshin182
User offline. Last seen 51 weeks 3 days ago. Offline
Joined: 19 Sep 2011

excellent,but i try add a body in the bottom like ground but the bullet not bounce when this are in collision the bullet(bun) pass for back of this any solutions

mila.habaer
User offline. Last seen 37 weeks 1 day ago. Offline
Joined: 13 Aug 2011

great article and code.

i have a question, iwould like it work in landscape mode, and like the catapult at the middle of the bottom the the screen, how can i do that.

thank you

emanouel
User offline. Last seen 1 week 4 days ago. Offline
Joined: 17 Jan 2011

mila.habaer,

to make it landscape, you need to editing the build settings file

replace the old file with these lines of code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
settings =
{
 
        orientation = 
        {
                default = "landscape",
                supported = 
                {
                "landscapeLeft", "landscapeRight"
                },
        },
        androidPermissions =
        {
                "android.permission.INTERNET"
        },
}

magadistudio
User offline. Last seen 1 week 2 days ago. Offline
Joined: 10 Jul 2011

I know this is old and all that, but I am having a problem here: I am trying to make it so that it detects collision:

What I mean is, when the projectile is release, I want to be able to hit something. Right now, all it does going through the other object which was supposed to hit and create an event. Any help would be highly appreciated.

Antheor
User offline. Last seen 15 weeks 3 days ago. Offline
Joined: 22 Sep 2010

you have a great example of this done here http://www.cheetomoskeeto.com/
(balloon burst)

Incicive
User offline. Last seen 2 years 26 weeks ago. Offline
Joined: 16 Dec 2011

Hey Fixdit,

Ive made a slingshot smaller,
and looking to move the location start of the Elastic entities,

Anyone know, what area of code i need to edit to change the location of the elastic ?

Fixdit
User offline. Last seen 13 weeks 1 day ago. Offline
Joined: 19 Jan 2011

I believe it's in the 'projectileTouchListener' function. This is where the elastic is created.
I haven't looked at this in a while and I'd love to make some improvements if we get any time.

Fixdit
http://www.fixdit.com/

Incicive
User offline. Last seen 2 years 26 weeks ago. Offline
Joined: 16 Dec 2011

Ok thanks, Ill have a look, and love the code,
Its a great basis too get started with and very del coded so its easy too learn basics :)

If i have any problems can i contact you anyway ?
Email or such ?

Incicive
User offline. Last seen 2 years 26 weeks ago. Offline
Joined: 16 Dec 2011

No Worries Fixdit, I worked it out :P
Had to change the values under the Projectile Function
To the same value for every variable :)

Thanks,

Fixdit
User offline. Last seen 13 weeks 1 day ago. Offline
Joined: 19 Jan 2011

Thanks Qunny-2k8-,
there are a number of catapult examples out there using different methods, but we wanted this to showcase something visually, a little more interesting.

It's probably best to share your questions here and spread the knowledge :)

Cheers,

Fixdit
http://www.fixdit.com/

Incicive
User offline. Last seen 2 years 26 weeks ago. Offline
Joined: 16 Dec 2011

Ill be more then glad to overview this topic constantly as I've become quite accustom too your code :)

Joe,

mila.habaer
User offline. Last seen 37 weeks 1 day ago. Offline
Joined: 13 Aug 2011

hi Fixdit,

In your project, you have two sounds: band-release and stretch-1, Can I freely use these sounds for my commercial game? if not, can you please share where you get these sounds?

borjaperez
User offline. Last seen 22 weeks 4 days ago. Offline
Joined: 5 May 2011

Hi Incicive, I've seen you have succeeded on changing the start position of the elastic entities. Can you show me which part of the code do I have to change, please?

Thanks