Corona SDK: Develop a Frenzic-like Game - Adding Interactivity
Welcome to the second post in our Corona SDK Frenzic-like game series. In today's tutorial, we'll be adding interactivity to our interface and coding the start of the game.
Where We Left Off. . .
Please be sure to check part 1 of the series to fully understand this tutorial.
Step 1: Declare Functions
Declare all functions as local at the start.
1 |
|
2 |
local Main = {} |
3 |
local addTitleView = {} |
4 |
local startButtonListeners = {} |
5 |
local showCredits = {} |
6 |
local hideCredits = {} |
7 |
local destroyCredits = {} |
8 |
local showGameView = {} |
9 |
local destroyTitleView = {} |
10 |
local addListeners = {} |
11 |
local newBlock = {} |
12 |
local timesUp = {} |
13 |
local placeBlock = {} |
14 |
local blockPlaced = {} |
15 |
local complete = {} |
16 |
local removeBlocks = {} |
17 |
local alert = {} |
18 |
local alertHandler = {} |
19 |
local restart = {} |
Step 2: Constructor
Next we'll create the function that will initialize all the game logic:
1 |
|
2 |
function Main() |
3 |
addTitleView() |
4 |
end
|
Step 3: Add Title View
Now we place the background and titleView in the stage.
1 |
|
2 |
function addTitleView() |
3 |
bg = display.newImage('bg.png') |
4 |
|
5 |
title = display.newImage('title.png') |
6 |
title.x = display.contentCenterX |
7 |
title.y = 100 |
8 |
|
9 |
startB = display.newImage('startButton.png') |
10 |
startB.x = display.contentCenterX |
11 |
startB.y = display.contentCenterY |
12 |
startB.name = 'startB' |
13 |
|
14 |
creditsB = display.newImage('creditsButton.png') |
15 |
creditsB.x = display.contentCenterX |
16 |
creditsB.y = display.contentCenterY + 40 |
17 |
creditsB.name = 'creditsB' |
18 |
|
19 |
titleView = display.newGroup() |
20 |
titleView:insert(title) |
21 |
titleView:insert(startB) |
22 |
titleView:insert(creditsB) |
23 |
|
24 |
startButtonListeners('add') |
25 |
end
|
Step 4: Button Listeners
In this function we add the tap listeners to the buttons in the title view, this will take us to the game screen or the credits screen.
1 |
|
2 |
function startButtonListeners(action) |
3 |
if(action == 'add') then |
4 |
creditsB:addEventListener('tap', showCredits) |
5 |
startB:addEventListener('tap', showGameView) |
6 |
else
|
7 |
creditsB:removeEventListener('tap', showCredits) |
8 |
startB:removeEventListener('tap', showGameView) |
9 |
end
|
10 |
end
|
Step 5: Show Credits
The credits screen is shown when the user taps the credits button, a tap listener is added to the credits view to remove it.
1 |
|
2 |
function showCredits() |
3 |
credits = display.newImage('credits.png') |
4 |
transition.from(credits, {time = 300, x = bg.contentWidth * 2, transition = easing.outExpo}) |
5 |
credits:addEventListener('tap', hideCredits) |
6 |
titleView.isVisible = false |
7 |
end
|
Step 6: Hide Credits
When the credits screen is tapped, it will be tweened out of the stage and removed.
1 |
|
2 |
function hideCredits() |
3 |
titleView.isVisible = true |
4 |
transition.to(credits, {time = 300, x = bg.contentWidth * 2, transition = easing.outExpo, onComplete = destroyCredits}) |
5 |
end
|
6 |
|
7 |
function destroyCredits() |
8 |
credits:removeEventListener('tap', hideCredits) |
9 |
display.remove(credits) |
10 |
credits = nil |
11 |
end
|
Step 7: Add Game View
When the Start button is pressed, the title view is tweened and removed, revealing the game view.
1 |
|
2 |
function showGameView(e) |
3 |
transition.to(titleView, {time = 300, y = -titleView.height, transition = easing.inExpo, onComplete = destroyTitleView}) |
4 |
end
|
Step 8: Remove the Title View
The title view is removed from memory and the game graphics are added to the stage.
1 |
|
2 |
function destroyTitleView() |
3 |
display.remove(titleView) |
4 |
titleView = nil |
5 |
|
6 |
-- Add GameView Graphics |
7 |
|
8 |
up = display.newImage('container.png') |
9 |
up:setReferencePoint(display.TopLeftReferencePoint) |
10 |
up.x = 125 |
11 |
up.y = 100 |
12 |
|
13 |
right = display.newImage('container.png') |
14 |
right:setReferencePoint(display.TopLeftReferencePoint) |
15 |
right.x = 230 |
16 |
right.y = 205 |
17 |
|
18 |
down = display.newImage('container.png') |
19 |
down:setReferencePoint(display.TopLeftReferencePoint) |
20 |
down.x = 125 |
21 |
down.y = 310 |
22 |
|
23 |
left = display.newImage('container.png') |
24 |
left:setReferencePoint(display.TopLeftReferencePoint) |
25 |
left.x = 20 |
26 |
left.y = 205 |
27 |
|
28 |
holder = display.newImage('container.png') |
29 |
holder:setReferencePoint(display.TopLeftReferencePoint) |
30 |
holder.x = 125 |
31 |
holder.y = 205 |
32 |
|
33 |
-- Lives & Score Text |
34 |
|
35 |
livesText = display.newText('Lives', 10, 10, 'Orbitron-Medium', 12) |
36 |
livesText:setTextColor(163, 255, 36) |
37 |
|
38 |
livesTF = display.newText('5', 24, 30, 'Orbitron-Medium', 12) |
39 |
livesTF:setTextColor(163, 255, 36) |
40 |
|
41 |
scoreText = display.newText('Score', 260, 10, 'Orbitron-Medium', 12) |
42 |
scoreText:setTextColor(163, 255, 36) |
43 |
|
44 |
scoreTF = display.newText('0', 274, 30, 'Orbitron-Medium', 12) |
45 |
scoreTF:setTextColor(163, 255, 36) |
46 |
|
47 |
gameView = display.newGroup() |
48 |
gameView:insert(up) |
49 |
gameView:insert(right) |
50 |
gameView:insert(down) |
51 |
gameView:insert(left) |
52 |
gameView:insert(holder) |
53 |
gameView:insert(livesText) |
54 |
gameView:insert(livesTF) |
55 |
gameView:insert(scoreText) |
56 |
gameView:insert(scoreTF) |
57 |
|
58 |
addListeners() |
59 |
end
|
Step 9: Game Listeners
This function will add a Tap Listener to the square containers so you can tap them and place the current block in the center (holder) container.
1 |
|
2 |
function addListeners() |
3 |
up:addEventListener('tap', placeBlock) |
4 |
right:addEventListener('tap', placeBlock) |
5 |
down:addEventListener('tap', placeBlock) |
6 |
left:addEventListener('tap', placeBlock) |
7 |
|
8 |
lives = 5 |
9 |
score = 0 |
Step 10: Container Variables
These variables are created inside the square containers, they are used to register the blocks, colors, and positions inside every square.
The letters represent the following positions:
- a: top-left
- b: top-right
- c: bottom-left
- d: bottom-right
1 |
|
2 |
-- Create a var for every container to determine when full |
3 |
|
4 |
up.blocks = 0 |
5 |
right.blocks = 0 |
6 |
down.blocks = 0 |
7 |
left.blocks = 0 |
8 |
|
9 |
-- Arrays used to remove blocks and detect color |
10 |
|
11 |
up.blocksGFX = {} |
12 |
right.blocksGFX = {} |
13 |
down.blocksGFX = {} |
14 |
left.blocksGFX = {} |
15 |
|
16 |
-- Create an boolean for every container to avoid placing blocks in the same position |
17 |
|
18 |
up.a = false |
19 |
right.a = false |
20 |
down.a = false |
21 |
left.a = false |
22 |
|
23 |
up.b = false |
24 |
right.b = false |
25 |
down.b = false |
26 |
left.b = false |
27 |
|
28 |
up.c = false |
29 |
right.c = false |
30 |
down.c = false |
31 |
left.c = false |
32 |
|
33 |
up.d = false |
34 |
right.d = false |
35 |
down.d = false |
36 |
left.d = false |
37 |
|
38 |
-- Give a name to the containers to identify them later |
39 |
|
40 |
up.name = 'up' |
41 |
right.name = 'right' |
42 |
down.name = 'down' |
43 |
left.name = 'left' |
44 |
|
45 |
newBlock(true) |
46 |
end
|
Step 11: Generate Random Block
This code picks a random block color from the Table, this will be used to instantiate the actual block. A parameter is used to determine if the timer needs to be started.
1 |
|
2 |
function newBlock(firstTime) |
3 |
-- New Block |
4 |
|
5 |
local randomBlock = math.floor(math.random() * 3) + 1 |
6 |
local block |
7 |
|
8 |
if(blockColor[randomBlock] == 'orange') then |
9 |
block = display.newImage('orangeBlock.png') |
10 |
block.name = 'orange' |
11 |
block:setReferencePoint(display.TopLeftReferencePoint) |
12 |
elseif(blockColor[randomBlock] == 'green') then |
13 |
block = display.newImage('greenBlock.png') |
14 |
block.name = 'green' |
15 |
block:setReferencePoint(display.TopLeftReferencePoint) |
16 |
elseif(blockColor[randomBlock] == 'purple') then |
17 |
block = display.newImage('purpleBlock.png') |
18 |
block.name = 'purple' |
19 |
block:setReferencePoint(display.TopLeftReferencePoint) |
20 |
end
|
Step 12: Add a New Block
After selecting the block color, the position where it will be placed its calculated using the positions table and then added to the blocks table and the stage.
1 |
|
2 |
currentXPosition = positions[math.floor(math.random() * 2) + 1] |
3 |
currentYPosition = positions[math.floor(math.random() * 2) + 1] |
4 |
|
5 |
block.x = holder.x + currentXPosition |
6 |
block.y = holder.y + currentYPosition |
7 |
table.insert(blocks, block) |
8 |
gameView:insert(block) |
Step 13: Check Available Space
Before continuing with the game, we must check that the newly created block can actually be placed in a square container. The following code checks every container array to make sure that there is a position available to place the block. If not, the block is destroyed and the function is called again to generate another one.
1 |
|
2 |
local position = {currentXPosition, currentYPosition} |
3 |
|
4 |
if(position[1] == 5 and position[2] == 5 and up.a == true and right.a == true and down.a == true and left.a == true ) then |
5 |
display.remove(block) |
6 |
block = nil |
7 |
newBlock(false) |
8 |
elseif(position[1] == 35 and position[2] == 5 and up.b == true and right.b == true and down.b == true and left.b == true ) then |
9 |
display.remove(block) |
10 |
block = nil |
11 |
newBlock(false) |
12 |
elseif(position[1] == 5 and position[2] == 35 and up.c == true and right.c == true and down.c == true and left.c == true ) then |
13 |
display.remove(block) |
14 |
block = nil |
15 |
newBlock(false) |
16 |
elseif(position[1] == 35 and position[2] == 35 and up.d == true and right.d == true and down.d == true and left.d == true ) then |
17 |
display.remove(block) |
18 |
block = nil |
19 |
newBlock(false) |
20 |
end
|
Step 14: Start Timer
The timer starts counting when the function is called for the first time.
1 |
|
2 |
if(firstTime) then |
3 |
-- Start Timer |
4 |
timerSource = timer.performWithDelay(3000, timesUp, 0) |
5 |
end
|
6 |
end
|
Step 15: Lives
Three seconds are given to place a block in a square container, if that time passes and the block is still in the center square, a life will be removed.
1 |
|
2 |
function timesUp:timer(e) |
3 |
-- Remove Life |
4 |
|
5 |
lives = lives - 1 |
6 |
livesTF.text = lives |
7 |
media.playEventSound('buzz.caf') |
Step 16: Unused Blocks
After removing the life, the block in the center square will be destroyed and a new block will be generated.
1 |
|
2 |
display.remove(blocks[#blocks]) |
3 |
table.remove(blocks) |
Step 17: Check for Game Over
This code checks if the player is out of lives and calls a function that will handle that.
1 |
|
2 |
if(lives < 1) then |
3 |
alert() |
4 |
else
|
5 |
-- Next Block |
6 |
|
7 |
newBlock(false) |
8 |
end
|
9 |
end
|
Step 18: Code Review
Here is the full code written in this tutorial alongside with comments to help you identify each part:
1 |
|
2 |
-- Sort 'Frenzic' like Game |
3 |
-- Developed by Carlos Yanez |
4 |
|
5 |
-- Hide Status Bar |
6 |
|
7 |
display.setStatusBar(display.HiddenStatusBar) |
8 |
|
9 |
-- Graphics |
10 |
-- [Background] |
11 |
|
12 |
local bg |
13 |
|
14 |
-- [Title View] |
15 |
|
16 |
local title |
17 |
local startB |
18 |
local creditsB |
19 |
|
20 |
-- [TitleView Group] |
21 |
|
22 |
local titleView |
23 |
|
24 |
-- [Score & Lives] |
25 |
|
26 |
local livesText |
27 |
local livesTF |
28 |
local lives |
29 |
local scoreText |
30 |
local scoreTF |
31 |
local score |
32 |
|
33 |
-- [GameView] |
34 |
|
35 |
local up |
36 |
local right |
37 |
local down |
38 |
local left |
39 |
local holder |
40 |
|
41 |
--[GameView Group] |
42 |
|
43 |
local gameView |
44 |
|
45 |
-- [CreditsView] |
46 |
|
47 |
local credits |
48 |
|
49 |
-- Variables |
50 |
|
51 |
local blockColor = {'orange', 'green', 'purple'} |
52 |
local blocks = {} |
53 |
local positions = {5, 35} |
54 |
local currentXPosition |
55 |
local currentYPosition |
56 |
local eventTarget |
57 |
local timerSource |
58 |
local lives |
59 |
local score |
60 |
local bell |
61 |
local bell4 |
62 |
local buzz |
63 |
|
64 |
-- Functions |
65 |
|
66 |
local Main = {} |
67 |
local addTitleView = {} |
68 |
local startButtonListeners = {} |
69 |
local showCredits = {} |
70 |
local hideCredits = {} |
71 |
local destroyCredits = {} |
72 |
local showGameView = {} |
73 |
local destroyTitleView = {} |
74 |
local addListeners = {} |
75 |
local newBlock = {} |
76 |
local timesUp = {} |
77 |
local placeBlock = {} |
78 |
local blockPlaced = {} |
79 |
local complete = {} |
80 |
local removeBlocks = {} |
81 |
local alert = {} |
82 |
local alertHandler = {} |
83 |
local restart = {} |
84 |
|
85 |
function Main() |
86 |
addTitleView() |
87 |
end
|
88 |
|
89 |
function addTitleView() |
90 |
bg = display.newImage('bg.png') |
91 |
|
92 |
title = display.newImage('title.png') |
93 |
title.x = display.contentCenterX |
94 |
title.y = 100 |
95 |
|
96 |
startB = display.newImage('startButton.png') |
97 |
startB.x = display.contentCenterX |
98 |
startB.y = display.contentCenterY |
99 |
startB.name = 'startB' |
100 |
|
101 |
creditsB = display.newImage('creditsButton.png') |
102 |
creditsB.x = display.contentCenterX |
103 |
creditsB.y = display.contentCenterY + 40 |
104 |
creditsB.name = 'creditsB' |
105 |
|
106 |
titleView = display.newGroup() |
107 |
titleView:insert(title) |
108 |
titleView:insert(startB) |
109 |
titleView:insert(creditsB) |
110 |
|
111 |
startButtonListeners('add') |
112 |
end
|
113 |
|
114 |
function startButtonListeners(action) |
115 |
if(action == 'add') then |
116 |
creditsB:addEventListener('tap', showCredits) |
117 |
startB:addEventListener('tap', showGameView) |
118 |
else
|
119 |
creditsB:removeEventListener('tap', showCredits) |
120 |
startB:removeEventListener('tap', showGameView) |
121 |
end
|
122 |
end
|
123 |
|
124 |
function showCredits() |
125 |
credits = display.newImage('credits.png') |
126 |
transition.from(credits, {time = 300, x = bg.contentWidth * 2, transition = easing.outExpo}) |
127 |
credits:addEventListener('tap', hideCredits) |
128 |
titleView.isVisible = false |
129 |
end
|
130 |
|
131 |
function hideCredits() |
132 |
titleView.isVisible = true |
133 |
transition.to(credits, {time = 300, x = bg.contentWidth * 2, transition = easing.outExpo, onComplete = destroyCredits}) |
134 |
end
|
135 |
|
136 |
function destroyCredits() |
137 |
credits:removeEventListener('tap', hideCredits) |
138 |
display.remove(credits) |
139 |
credits = nil |
140 |
end
|
141 |
|
142 |
function showGameView(e) |
143 |
transition.to(titleView, {time = 300, y = -titleView.height, transition = easing.inExpo, onComplete = destroyTitleView}) |
144 |
end
|
145 |
|
146 |
function destroyTitleView() |
147 |
display.remove(titleView) |
148 |
titleView = nil |
149 |
|
150 |
-- Add GameView Graphics |
151 |
|
152 |
up = display.newImage('container.png') |
153 |
up:setReferencePoint(display.TopLeftReferencePoint) |
154 |
up.x = 125 |
155 |
up.y = 100 |
156 |
|
157 |
right = display.newImage('container.png') |
158 |
right:setReferencePoint(display.TopLeftReferencePoint) |
159 |
right.x = 230 |
160 |
right.y = 205 |
161 |
|
162 |
down = display.newImage('container.png') |
163 |
down:setReferencePoint(display.TopLeftReferencePoint) |
164 |
down.x = 125 |
165 |
down.y = 310 |
166 |
|
167 |
left = display.newImage('container.png') |
168 |
left:setReferencePoint(display.TopLeftReferencePoint) |
169 |
left.x = 20 |
170 |
left.y = 205 |
171 |
|
172 |
holder = display.newImage('container.png') |
173 |
holder:setReferencePoint(display.TopLeftReferencePoint) |
174 |
holder.x = 125 |
175 |
holder.y = 205 |
176 |
|
177 |
-- Lives & Score Text |
178 |
|
179 |
livesText = display.newText('Lives', 10, 10, 'Orbitron-Medium', 12) |
180 |
livesText:setTextColor(163, 255, 36) |
181 |
|
182 |
livesTF = display.newText('5', 24, 30, 'Orbitron-Medium', 12) |
183 |
livesTF:setTextColor(163, 255, 36) |
184 |
|
185 |
scoreText = display.newText('Score', 260, 10, 'Orbitron-Medium', 12) |
186 |
scoreText:setTextColor(163, 255, 36) |
187 |
|
188 |
scoreTF = display.newText('0', 274, 30, 'Orbitron-Medium', 12) |
189 |
scoreTF:setTextColor(163, 255, 36) |
190 |
|
191 |
gameView = display.newGroup() |
192 |
gameView:insert(up) |
193 |
gameView:insert(right) |
194 |
gameView:insert(down) |
195 |
gameView:insert(left) |
196 |
gameView:insert(holder) |
197 |
gameView:insert(livesText) |
198 |
gameView:insert(livesTF) |
199 |
gameView:insert(scoreText) |
200 |
gameView:insert(scoreTF) |
201 |
|
202 |
addListeners() |
203 |
end
|
204 |
|
205 |
function addListeners() |
206 |
up:addEventListener('tap', placeBlock) |
207 |
right:addEventListener('tap', placeBlock) |
208 |
down:addEventListener('tap', placeBlock) |
209 |
left:addEventListener('tap', placeBlock) |
210 |
|
211 |
lives = 5 |
212 |
score = 0 |
213 |
|
214 |
-- Create a var for every container to determine when full |
215 |
|
216 |
up.blocks = 0 |
217 |
right.blocks = 0 |
218 |
down.blocks = 0 |
219 |
left.blocks = 0 |
220 |
|
221 |
-- Arrays used to remove blocks and detect color |
222 |
|
223 |
up.blocksGFX = {} |
224 |
right.blocksGFX = {} |
225 |
down.blocksGFX = {} |
226 |
left.blocksGFX = {} |
227 |
|
228 |
-- Create an boolean for every container to avoid placing blocks in the same position |
229 |
|
230 |
up.a = false |
231 |
right.a = false |
232 |
down.a = false |
233 |
left.a = false |
234 |
|
235 |
up.b = false |
236 |
right.b = false |
237 |
down.b = false |
238 |
left.b = false |
239 |
|
240 |
up.c = false |
241 |
right.c = false |
242 |
down.c = false |
243 |
left.c = false |
244 |
|
245 |
up.d = false |
246 |
right.d = false |
247 |
down.d = false |
248 |
left.d = false |
249 |
|
250 |
-- Give a name to the containers to identify them later |
251 |
|
252 |
up.name = 'up' |
253 |
right.name = 'right' |
254 |
down.name = 'down' |
255 |
left.name = 'left' |
256 |
|
257 |
newBlock(true) |
258 |
end
|
259 |
|
260 |
function newBlock(firstTime) |
261 |
-- New Block |
262 |
|
263 |
local randomBlock = math.floor(math.random() * 3) + 1 |
264 |
local block |
265 |
|
266 |
if(blockColor[randomBlock] == 'orange') then |
267 |
block = display.newImage('orangeBlock.png') |
268 |
block.name = 'orange' |
269 |
block:setReferencePoint(display.TopLeftReferencePoint) |
270 |
elseif(blockColor[randomBlock] == 'green') then |
271 |
block = display.newImage('greenBlock.png') |
272 |
block.name = 'green' |
273 |
block:setReferencePoint(display.TopLeftReferencePoint) |
274 |
elseif(blockColor[randomBlock] == 'purple') then |
275 |
block = display.newImage('purpleBlock.png') |
276 |
block.name = 'purple' |
277 |
block:setReferencePoint(display.TopLeftReferencePoint) |
278 |
end
|
279 |
|
280 |
currentXPosition = positions[math.floor(math.random() * 2) + 1] |
281 |
currentYPosition = positions[math.floor(math.random() * 2) + 1] |
282 |
|
283 |
block.x = holder.x + currentXPosition |
284 |
block.y = holder.y + currentYPosition |
285 |
table.insert(blocks, block) |
286 |
gameView:insert(block) |
287 |
|
288 |
-- Check for an available space to move the block |
289 |
|
290 |
local position = {currentXPosition, currentYPosition} |
291 |
|
292 |
if(position[1] == 5 and position[2] == 5 and up.a == true and right.a == true and down.a == true and left.a == true ) then |
293 |
display.remove(block) |
294 |
block = nil |
295 |
newBlock(false) |
296 |
elseif(position[1] == 35 and position[2] == 5 and up.b == true and right.b == true and down.b == true and left.b == true ) then |
297 |
display.remove(block) |
298 |
block = nil |
299 |
newBlock(false) |
300 |
elseif(position[1] == 5 and position[2] == 35 and up.c == true and right.c == true and down.c == true and left.c == true ) then |
301 |
display.remove(block) |
302 |
block = nil |
303 |
newBlock(false) |
304 |
elseif(position[1] == 35 and position[2] == 35 and up.d == true and right.d == true and down.d == true and left.d == true ) then |
305 |
display.remove(block) |
306 |
block = nil |
307 |
newBlock(false) |
308 |
end
|
309 |
|
310 |
-- Start Timer the first time the function is called |
311 |
|
312 |
if(firstTime) then |
313 |
-- Start Timer |
314 |
timerSource = timer.performWithDelay(3000, timesUp, 0) |
315 |
end
|
316 |
end
|
317 |
|
318 |
function timesUp:timer(e) |
319 |
-- Remove Live |
320 |
|
321 |
lives = lives - 1 |
322 |
livesTF.text = lives |
323 |
media.playEventSound('buzz.caf') |
324 |
|
325 |
-- Remove Unused Block |
326 |
|
327 |
display.remove(blocks[#blocks]) |
328 |
table.remove(blocks) |
329 |
|
330 |
-- Check if out of lives |
331 |
|
332 |
if(lives < 1) then |
333 |
--alert() |
334 |
else
|
335 |
-- Next Block |
336 |
|
337 |
newBlock(false) |
338 |
end
|
339 |
end
|
Next Time...
In the next and final part of the series, we'll handle the blocks behavior, scores, and the final steps to take prior to releasing the app -like app testing, creating a start screen, adding an icon, and building the app. Stay tuned for the final part!