Corona SDK: Create a Balloon Game - Interaction
This is the second installment in our Corona SDK Bloons inspired Game tutorial. In today's tutorial, we'll add to our interface and start coding the game interaction. Read on!
Where We Left Off. . .
Please be sure to check part 1 of the series to fully understand and prepare for this tutorial.
Step 1: Declare Functions
Declare all functions as local at the start.
1 |
|
2 |
local Main = {}
|
3 |
local startButtonListeners = {}
|
4 |
local showCredits = {}
|
5 |
local hideCredits = {}
|
6 |
local showGameView = {}
|
7 |
local gameListeners = {}
|
8 |
local startCharge = {}
|
9 |
local charge = {}
|
10 |
local shot = {}
|
11 |
local onCollision = {}
|
12 |
local startGame = {}
|
13 |
local createBalloons = {}
|
14 |
local update = {}
|
15 |
local restartLvl = {}
|
16 |
local alert = {}
|
17 |
local restart = {}
|
Step 2: Constructor
Next, we'll create the function that will initialize all the game logic:
1 |
|
2 |
function Main() |
3 |
-- code... |
4 |
end |
Step 3: Add Title View
Now we place the TitleView in the stage and call a function that will add the tap listeners to the buttons.
1 |
|
2 |
titleBg = display.newImage('titleBg.png')
|
3 |
playBtn = display.newImage('playBtn.png', display.contentCenterX - 25.5, display.contentCenterY + 40)
|
4 |
creditsBtn = display.newImage('creditsBtn.png', display.contentCenterX - 40.5, display.contentCenterY + 85)
|
5 |
titleView = display.newGroup(titleBg, playBtn, creditsBtn) |
6 |
|
7 |
startButtonListeners('add')
|
Step 4: Start Button Listeners
This function adds the necesary listeners to the TitleView buttons.
1 |
|
2 |
function startButtonListeners(action) |
3 |
if(action == 'add') then |
4 |
playBtn:addEventListener('tap', showGameView)
|
5 |
creditsBtn:addEventListener('tap', showCredits)
|
6 |
else |
7 |
playBtn:removeEventListener('tap', showGameView)
|
8 |
creditsBtn:removeEventListener('tap', showCredits)
|
9 |
end |
10 |
end |
Step 5: Show Credits
The credits screen is shown when the user taps the about button. A tap listener is added to the credits view to remove it.
1 |
|
2 |
function showCredits:tap(e) |
3 |
playBtn.isVisible = false |
4 |
creditsBtn.isVisible = false |
5 |
creditsView = display.newImage('credits.png')
|
6 |
transition.from(creditsView, {time = 300, x = -creditsView.width, onComplete = function() creditsView:addEventListener('tap', hideCredits) creditsView.x = creditsView.x - 0.5 end})
|
7 |
end |
Step 6: Hide Credits
When the credits screen is tapped, it'll be tweened out of the stage and removed.
1 |
|
2 |
function hideCredits:tap(e) |
3 |
playBtn.isVisible = true |
4 |
creditsBtn.isVisible = true |
5 |
transition.to(creditsView, {time = 300, x = -creditsView.width, onComplete = function() creditsView:removeEventListener('tap', hideCredits) display.remove(creditsView) creditsView = nil end})
|
6 |
end |
Step 7: Show Game View
When the Start button is tapped the title view is tweened and removed, revealing the game view.
1 |
|
2 |
function showGameView:tap(e) |
3 |
transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners('rmv') display.remove(titleView) titleView = nil startGame() end})
|
4 |
|
5 |
-- Add GFX |
6 |
|
7 |
infoBar = display.newImage('infoBar.png', 0, 276)
|
8 |
restartBtn = display.newImage('restartBtn.png', 443, 286)
|
9 |
squirrel = display.newImage('squirrel.png', 70, 182)
|
10 |
gCircle = display.newImage('gCircle.png', 83, 216)
|
11 |
gCircle:setReferencePoint(display.CenterReferencePoint) |
12 |
|
13 |
targetTF = display.newText('0', 123, 287, native.systemFontBold, 14)
|
14 |
targetTF:setTextColor(238, 238, 238) |
15 |
|
16 |
scoreTF = display.newText('0', 196, 287, native.systemFontBold, 14)
|
17 |
scoreTF:setTextColor(238, 238, 238) |
18 |
|
19 |
acornsTF = display.newText('5', 49, 287, native.systemFontBold, 13)
|
20 |
acornsTF:setTextColor(238, 238, 238) |
21 |
end |
Step 8: Game Listeners
This code adds tap listeners to the game background. These will be used to shoot the acorns to the balloons. A tap listener is also added to the restart button.
1 |
|
2 |
function gameListeners(action) |
3 |
if(action == 'add') then |
4 |
bg:addEventListener('touch', startCharge)
|
5 |
bg:addEventListener('touch', shot)
|
6 |
restartBtn:addEventListener('tap', restartLvl)
|
7 |
Runtime:addEventListener('enterFrame', update)
|
8 |
else |
9 |
bg:removeEventListener('touch', startCharge)
|
10 |
bg:removeEventListener('touch', shot)
|
11 |
restartBtn:removeEventListener('tap', restartLvl)
|
12 |
Runtime:removeEventListener('enterFrame', update)
|
13 |
end |
14 |
end |
Step 9: Start Game
Here we start the game by hiding the direction indicator, adding the game listeners and calling the function that generates the balloons.
1 |
|
2 |
function startGame() |
3 |
-- Hide gCircle |
4 |
|
5 |
gCircle.isVisible = false |
6 |
|
7 |
-- Create balloon function |
8 |
|
9 |
gameListeners('add')
|
10 |
createBalloons(5, 3) |
11 |
end |
Step 10: Create Balloons
A double for loop is used to create and place the balloons on the stage. The balloon is then added to a table. This will grant us access to the balloons outside this function.
1 |
|
2 |
function createBalloons(h, v) |
3 |
for i = 1, h do |
4 |
for j = 1, v do |
5 |
local balloon = display.newImage('balloon.png', 300 + (i * 20), 120 + (j * 30))
|
6 |
balloon.name = 'balloon' |
7 |
physics.addBody(balloon) |
8 |
balloon.bodyType = 'static' |
9 |
table.insert(balloons, balloon) |
10 |
end |
11 |
end |
12 |
|
13 |
-- Set balloon counter |
14 |
|
15 |
targetTF.text = #balloons |
16 |
end |
Step 11: Collisions
This function handles the acorn-balloon collisions.
When this occur, the balloon is removed from the stage and a sound is played. We also update the score and target textfields.
1 |
|
2 |
function onCollision(e) |
3 |
if(e.other.name == 'balloon') then |
4 |
display.remove(e.other) |
5 |
e.other = nil |
6 |
audio.play(pop) |
7 |
scoreTF.text = scoreTF.text + 50 |
8 |
scoreTF:setReferencePoint(display.TopLeftReferencePoint) |
9 |
scoreTF.x = 196 |
10 |
targetTF.text = targetTF.text - 1 |
11 |
end |
12 |
|
13 |
if(targetTF.text == '0') then |
14 |
alert('win')
|
15 |
end |
16 |
end |
Step 12: Start Charge
This code will reveal the direction indicator, reset the acorn's impulse variable, and add a frame listener that will handle the aim and impulse value.
1 |
|
2 |
function startCharge:touch(e) |
3 |
if(e.phase == 'began') then |
4 |
impulse = 0 |
5 |
gCircle.isVisible = true |
6 |
Runtime:addEventListener('enterFrame', charge)
|
7 |
end |
8 |
end |
Step 13: Charge
The aim rotates accordingly to the direction that will take the acorn, which is set by the impulse variable.
1 |
|
2 |
function charge() |
3 |
gCircle.rotation = gCircle.rotation - 3 |
4 |
impulse = impulse - 0.2 |
5 |
|
6 |
-- Prevent over rotation |
7 |
|
8 |
if(gCircle.rotation < -46) then |
9 |
gCircle.rotation = -46 |
10 |
impulse = -3.2 |
11 |
end |
12 |
end |
Step 14: Code Review
Here is the full code written in this tutorial, alongside with the comments to help you identify each part:
1 |
|
2 |
-- Balloons Physics Game |
3 |
-- Developed by Carlos Yanez |
4 |
|
5 |
-- Hide Status Bar |
6 |
|
7 |
display.setStatusBar(display.HiddenStatusBar) |
8 |
|
9 |
-- Physics |
10 |
|
11 |
local physics = require('physics')
|
12 |
physics.start() |
13 |
|
14 |
-- Graphics |
15 |
|
16 |
-- [Background] |
17 |
|
18 |
local bg = display.newImage('gameBg.png')
|
19 |
|
20 |
-- [Title View] |
21 |
|
22 |
local titleBg |
23 |
local playBtn |
24 |
local creditsBtn |
25 |
local titleView |
26 |
|
27 |
-- [Credits] |
28 |
|
29 |
local creditsView |
30 |
|
31 |
-- [Game View] |
32 |
|
33 |
local gCircle |
34 |
local squirrel |
35 |
local infoBar |
36 |
local restartBtn |
37 |
|
38 |
-- [TextFields] |
39 |
|
40 |
local scoreTF |
41 |
local targetTF |
42 |
local acornsTF |
43 |
|
44 |
-- Load Sound |
45 |
|
46 |
local pop = audio.loadSound('pop.mp3')
|
47 |
|
48 |
-- Variables |
49 |
|
50 |
local titleView |
51 |
local credits |
52 |
local acorns = display.newGroup() |
53 |
local balloons = {}
|
54 |
local impulse = 0 |
55 |
local dir = 3 |
56 |
|
57 |
-- Functions |
58 |
|
59 |
local Main = {}
|
60 |
local startButtonListeners = {}
|
61 |
local showCredits = {}
|
62 |
local hideCredits = {}
|
63 |
local showGameView = {}
|
64 |
local gameListeners = {}
|
65 |
local startCharge = {}
|
66 |
local charge = {}
|
67 |
local shot = {}
|
68 |
local onCollision = {}
|
69 |
local startGame = {}
|
70 |
local createBalloons = {}
|
71 |
local update = {}
|
72 |
local restartLvl = {}
|
73 |
local alert = {}
|
74 |
local restart = {}
|
75 |
|
76 |
-- Main Function |
77 |
|
78 |
function Main() |
79 |
titleBg = display.newImage('titleBg.png')
|
80 |
playBtn = display.newImage('playBtn.png', display.contentCenterX - 25.5, display.contentCenterY + 40)
|
81 |
creditsBtn = display.newImage('creditsBtn.png', display.contentCenterX - 40.5, display.contentCenterY + 85)
|
82 |
titleView = display.newGroup(titleBg, playBtn, creditsBtn) |
83 |
|
84 |
startButtonListeners('add')
|
85 |
end |
86 |
|
87 |
function startButtonListeners(action) |
88 |
if(action == 'add') then |
89 |
playBtn:addEventListener('tap', showGameView)
|
90 |
creditsBtn:addEventListener('tap', showCredits)
|
91 |
else |
92 |
playBtn:removeEventListener('tap', showGameView)
|
93 |
creditsBtn:removeEventListener('tap', showCredits)
|
94 |
end |
95 |
end |
96 |
|
97 |
function showCredits:tap(e) |
98 |
playBtn.isVisible = false |
99 |
creditsBtn.isVisible = false |
100 |
creditsView = display.newImage('credits.png')
|
101 |
transition.from(creditsView, {time = 300, x = -creditsView.width, onComplete = function() creditsView:addEventListener('tap', hideCredits) creditsView.x = creditsView.x - 0.5 end})
|
102 |
end |
103 |
|
104 |
function hideCredits:tap(e) |
105 |
playBtn.isVisible = true |
106 |
creditsBtn.isVisible = true |
107 |
transition.to(creditsView, {time = 300, x = -creditsView.width, onComplete = function() creditsView:removeEventListener('tap', hideCredits) display.remove(creditsView) creditsView = nil end})
|
108 |
end |
109 |
|
110 |
function showGameView:tap(e) |
111 |
transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners('rmv') display.remove(titleView) titleView = nil startGame() end})
|
112 |
|
113 |
-- Add GFX |
114 |
|
115 |
infoBar = display.newImage('infoBar.png', 0, 276)
|
116 |
restartBtn = display.newImage('restartBtn.png', 443, 286)
|
117 |
squirrel = display.newImage('squirrel.png', 70, 182)
|
118 |
gCircle = display.newImage('gCircle.png', 83, 216)
|
119 |
gCircle:setReferencePoint(display.CenterReferencePoint) |
120 |
|
121 |
targetTF = display.newText('0', 123, 287, native.systemFontBold, 14)
|
122 |
targetTF:setTextColor(238, 238, 238) |
123 |
|
124 |
scoreTF = display.newText('0', 196, 287, native.systemFontBold, 14)
|
125 |
scoreTF:setTextColor(238, 238, 238) |
126 |
|
127 |
acornsTF = display.newText('5', 49, 287, native.systemFontBold, 13)
|
128 |
acornsTF:setTextColor(238, 238, 238) |
129 |
end |
130 |
|
131 |
function gameListeners(action) |
132 |
if(action == 'add') then |
133 |
bg:addEventListener('touch', startCharge)
|
134 |
bg:addEventListener('touch', shot)
|
135 |
restartBtn:addEventListener('tap', restartLvl)
|
136 |
Runtime:addEventListener('enterFrame', update)
|
137 |
else |
138 |
bg:removeEventListener('touch', startCharge)
|
139 |
bg:removeEventListener('touch', shot)
|
140 |
restartBtn:removeEventListener('tap', restartLvl)
|
141 |
Runtime:removeEventListener('enterFrame', update)
|
142 |
end |
143 |
end |
144 |
|
145 |
function startGame() |
146 |
-- Hide gCircle |
147 |
|
148 |
gCircle.isVisible = false |
149 |
|
150 |
-- Create balloon function |
151 |
|
152 |
gameListeners('add')
|
153 |
createBalloons(5, 3) |
154 |
end |
155 |
|
156 |
function createBalloons(h, v) |
157 |
for i = 1, h do |
158 |
for j = 1, v do |
159 |
local balloon = display.newImage('balloon.png', 300 + (i * 20), 120 + (j * 30))
|
160 |
balloon.name = 'balloon' |
161 |
physics.addBody(balloon) |
162 |
balloon.bodyType = 'static' |
163 |
table.insert(balloons, balloon) |
164 |
end |
165 |
end |
166 |
|
167 |
-- Set balloon counter |
168 |
|
169 |
targetTF.text = #balloons |
170 |
end |
171 |
|
172 |
function onCollision(e) |
173 |
--if(e.other.name == 'balloon' and e.phase == 'ended') then |
174 |
if(e.other.name == 'balloon') then |
175 |
display.remove(e.other) |
176 |
e.other = nil |
177 |
audio.play(pop) |
178 |
scoreTF.text = scoreTF.text + 50 |
179 |
scoreTF:setReferencePoint(display.TopLeftReferencePoint) |
180 |
scoreTF.x = 196 |
181 |
targetTF.text = targetTF.text - 1 |
182 |
end |
183 |
|
184 |
if(targetTF.text == '0') then |
185 |
alert('win')
|
186 |
end |
187 |
end |
188 |
|
189 |
function startCharge:touch(e) |
190 |
if(e.phase == 'began') then |
191 |
impulse = 0 |
192 |
gCircle.isVisible = true |
193 |
Runtime:addEventListener('enterFrame', charge)
|
194 |
end |
195 |
end |
196 |
|
197 |
function charge() |
198 |
gCircle.rotation = gCircle.rotation - 3 |
199 |
impulse = impulse - 0.2 |
200 |
|
201 |
-- Prevent over rotation |
202 |
|
203 |
if(gCircle.rotation < -46) then |
204 |
gCircle.rotation = -46 |
205 |
impulse = -3.2 |
206 |
end |
207 |
end |
Next Time...
In the next and final part of the series, we'll handle the acorn shooting, level restart, and the final steps to take prior to release like app testing, creating a start screen, adding an icon and, finally, building the app. Stay tuned for the final part!



