Advertisement
  1. Code
  2. Mobile Development
  3. Corona

Corona SDK: Build a Space Shooter - Adding Interactivity

Scroll to top
Read Time: 9 min

This is the second installment in our Corona SDK space shooter 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 read part 1 of this series to fully understand and prepare for this tutorial.

Step 1: Load Sounds

Sound effects used in the game will be loaded at start, this will make them ready for reproduction.

1
2
local shot = audio.loadSound('shot.mp3')
3
local explo = audio.loadSound('explo.mp3')
4
local bossSound = audio.loadSound('boss.mp3')

Step 2: Variables

These are the variables we'll use, read the comments in the code to know more about them. Some of their names are self explaining so there will be no comment there.

1
2
local timerSource
3
local lives = display.newGroup()
4
local bullets = display.newGroup()
5
local enemies = display.newGroup()
6
local scoreN = 0
7
local bossHealth = 20

Step 3: Declare Functions

Declare all functions as local at the start.

1
2
local Main = {}
3
local addTitleView = {}
4
local showCredits = {}
5
local removeCredits = {}
6
local removeTitleView = {}
7
local addShip = {}
8
local addScore = {}
9
local addLives = {}
10
local listeners = {}
11
local moveShip = {}
12
local shoot = {}
13
local addEnemy = {}
14
local alert = {}
15
local update = {}
16
local collisionHandler = {}
17
local restart = {}

Step 4: Constructor

Next we'll create the function that will initialize all the game logic:

1
2
function Main()
3
	addTitleView()
4
end

Step 5: Add a Title View

Now we place the background and TitleView in the stage.

1
2
function addTitleView()
3
	title = display.newImage('title.png')
4
	playBtn = display.newImage('playBtn.png')
5
	playBtn.x = display.contentCenterX
6
	playBtn.y = display.contentCenterY + 10
7
	playBtn:addEventListener('tap', removeTitleView)
8
	
9
	creditsBtn = display.newImage('creditsBtn.png')
10
	creditsBtn.x = display.contentCenterX
11
	creditsBtn.y = display.contentCenterY + 60
12
	creditsBtn:addEventListener('tap', showCredits)
13
	
14
	titleView = display.newGroup(title, playBtn, creditsBtn)
15
end

Step 6: Remove the Title View

The title view is removed from memory and the addShip function is called after.

1
2
function removeTitleView:tap(e)
3
	transition.to(titleView,  {time = 300, y = -display.contentHeight, onComplete = function() display.remove(titleView) titleView = null addShip() end})
4
end

Step 7: 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:tap(e)
3
	creditsBtn.isVisible = false
4
	creditsView = display.newImage('creditsView.png')
5
	creditsView:setReferencePoint(display.TopLeftReferencePoint)
6
	transition.from(creditsView, {time = 300, x = display.contentWidth})
7
	creditsView:addEventListener('tap', removeCredits)
8
end

Step 8: Hide Credits

When the credits screen is tapped, it'll be tweened out of the stage and removed.

1
2
function removeCredits:tap(e)
3
	creditsBtn.isVisible = true
4
	transition.to(creditsView, {time = 300, x = display.contentWidth, onComplete = function() display.remove(creditsView) creditsView = null end})
5
end

Step 9: Add Ship

When the Start button is pressed, the title view is tweened and removed revealing the game view, the ship movieclip will be added first by the next lines:

1
2
function addShip()
3
	ship = movieclip.newAnim({'shipA.png', 'shipB.png'})
4
	ship.x = display.contentWidth * 0.5
5
	ship.y = display.contentHeight - ship.height
6
	ship.name = 'ship'
7
	ship:play()
8
	physics.addBody(ship)
9
	
10
	addScore()
11
end

Step 10: Add Score

The following function creates and places the score text in the stage.

1
2
function addScore()
3
	score = display.newText('Score: ', 1, 0, native.systemFontBold, 14)
4
	score.y = display.contentHeight - score.height * 0.5
5
	score.text = score.text .. tostring(scoreN)
6
	score:setReferencePoint(display.TopLeftReferencePoint)
7
	score.x = 1
8
	
9
	addLives()
10
end

Step 11: Add Lives

The lives graphics are added by the next code, it also uses a table to store the lives number. This will help us later to detect when the player is out of lives.

1
2
function addLives()
3
	for i = 1, 3 do
4
		live = display.newImage('live.png')
5
		live.x = (display.contentWidth - live.width * 0.7) - (5 * i+1) - live.width * i + 20
6
		live.y = display.contentHeight - live.height * 0.7
7
		
8
		lives.insert(lives, live)
9
	end
10
	listeners('add')
11
end

Step 12: Listeners

In this function we add the necesary listeners to the interactive objects. We also start the timer that will add the enemies. A parameter is used to determine if the listeners should be added or removed.

1
2
function listeners(action)
3
	if(action == 'add') then	
4
		bg:addEventListener('touch', moveShip)
5
		bg:addEventListener('tap', shoot)
6
		Runtime:addEventListener('enterFrame', update)
7
		timerSource = timer.performWithDelay(800, addEnemy, 0)
8
	else
9
		bg:removeEventListener('touch', moveShip)
10
		bg:removeEventListener('tap', shoot)
11
		Runtime:removeEventListener('enterFrame', update)
12
		timer.cancel(timerSource)
13
	end
14
end

Step 13: Move Ship

The ship will be controlled moving the finger horizontally across the screen. This code handles that behavior:

1
2
function moveShip:touch(e)
3
	if(e.phase == 'began') then
4
		lastX = e.x - ship.x
5
	elseif(e.phase == 'moved') then
6
		ship.x = e.x - lastX
7
	end
8
end

Step 14: Shoot

Tapping anywhere in the screen will make the ship shoot a bullet, this bullet is added as a physics object to detect its collision later.

1
2
function shoot:tap(e)
3
	local bullet = display.newImage('bullet.png')
4
	bullet.x = ship.x
5
	bullet.y = ship.y - ship.height
6
	bullet.name = 'bullet'
7
	physics.addBody(bullet)
8
	
9
	audio.play(shot)
10
	
11
	bullets.insert(bullets, bullet)
12
end

Step 15: Add Enemy

The next function is executed by a timer every 800 milliseconds. It will add an enemy on the top of the screen. The enemy will be later moved by the update function.

1
2
function addEnemy(e)
3
	local enemy = movieclip.newAnim({'enemyA.png', 'enemyA.png','enemyA.png','enemyA.png','enemyA.png','enemyA.png','enemyB.png','enemyB.png','enemyB.png','enemyB.png','enemyB.png','enemyB.png'})
4
	enemy.x = math.floor(math.random() * (display.contentWidth - enemy.width))
5
	enemy.y = -enemy.height
6
	enemy.name = 'enemy'
7
	physics.addBody(enemy)
8
	enemy.bodyType = 'static'
9
	enemies.insert(enemies, enemy)
10
	enemy:play()
11
	enemy:addEventListener('collision', collisionHandler)
12
end

Step 16: Alert

The Alert View will be shown when the user reaches a game state (i.e. win or lose), a parameter is used to determine which screen to display.

1
2
function alert(e)
3
	listeners('remove')
4
	local alertView
5
	
6
	if(e == 'win') then
7
		alertView = display.newImage('youWon.png')
8
		alertView.x = display.contentWidth * 0.5
9
		alertView.y = display.contentHeight * 0.5
10
	else
11
		alertView = display.newImage('gameOver.png')
12
		alertView.x = display.contentWidth * 0.5
13
		alertView.y = display.contentHeight * 0.5
14
	end
15
	
16
	alertView:addEventListener('tap', restart)
17
end

Step 17: Code Review

Here is the full code written in this tutorial alongside with comments to help you identify each part:

1
2
-- Space Shooter Game
3
-- Developed by Carlos Yanez
4
5
-- Hide Status Bar
6
7
display.setStatusBar(display.HiddenStatusBar)
8
9
-- Import MovieClip Library
10
11
local movieclip = require('movieclip')
12
13
-- Import Physics
14
15
local physics = require('physics')
16
physics.start()
17
physics.setGravity(0, 0)
18
19
-- Graphics
20
21
-- Background
22
23
local bg = display.newImage('bg.png')
24
25
-- [Title View]
26
27
local title
28
local playBtn
29
local creditsBtn
30
local titleView
31
32
-- [Credits]
33
34
local creditsView
35
36
-- [Ship]
37
38
local ship
39
40
-- [Boss]
41
42
local boss
43
44
-- [Score]
45
46
local score
47
48
-- [Lives]
49
50
local lives
51
52
-- Load Sounds
53
54
local shot = audio.loadSound('shot.mp3')
55
local explo = audio.loadSound('explo.mp3')
56
local bossSound = audio.loadSound('boss.mp3')
57
58
-- Variables
59
60
local timerSource
61
local lives = display.newGroup()
62
local bullets = display.newGroup()
63
local enemies = display.newGroup()
64
local scoreN = 0
65
local bossHealth = 20
66
67
-- Functions
68
69
local Main = {}
70
local addTitleView = {}
71
local showCredits = {}
72
local removeCredits = {}
73
local removeTitleView = {}
74
local addShip = {}
75
local addScore = {}
76
local addLives = {}
77
local listeners = {}
78
local moveShip = {}
79
local shoot = {}
80
local addEnemy = {}
81
local alert = {}
82
local update = {}
83
local collisionHandler = {}
84
local restart = {}
85
86
-- Main Function
87
88
function Main()
89
	addTitleView()
90
end
91
92
function addTitleView()
93
	title = display.newImage('title.png')
94
	playBtn = display.newImage('playBtn.png')
95
	playBtn.x = display.contentCenterX
96
	playBtn.y = display.contentCenterY + 10
97
	playBtn:addEventListener('tap', removeTitleView)
98
	
99
	creditsBtn = display.newImage('creditsBtn.png')
100
	creditsBtn.x = display.contentCenterX
101
	creditsBtn.y = display.contentCenterY + 60
102
	creditsBtn:addEventListener('tap', showCredits)
103
	
104
	titleView = display.newGroup(title, playBtn, creditsBtn)
105
end
106
107
function removeTitleView:tap(e)
108
	transition.to(titleView,  {time = 300, y = -display.contentHeight, onComplete = function() display.remove(titleView) titleView = null addShip() end})
109
end
110
111
function showCredits:tap(e)
112
	creditsBtn.isVisible = false
113
	creditsView = display.newImage('creditsView.png')
114
	creditsView:setReferencePoint(display.TopLeftReferencePoint)
115
	transition.from(creditsView, {time = 300, x = display.contentWidth})
116
	creditsView:addEventListener('tap', removeCredits)
117
end
118
119
function removeCredits:tap(e)
120
	creditsBtn.isVisible = true
121
	transition.to(creditsView, {time = 300, x = display.contentWidth, onComplete = function() display.remove(creditsView) creditsView = null end})
122
end
123
124
function addShip()
125
	ship = movieclip.newAnim({'shipA.png', 'shipB.png'})
126
	ship.x = display.contentWidth * 0.5
127
	ship.y = display.contentHeight - ship.height
128
	ship.name = 'ship'
129
	ship:play()
130
	physics.addBody(ship)
131
	
132
	addScore()
133
end
134
135
function addScore()
136
	score = display.newText('Score: ', 1, 0, native.systemFontBold, 14)
137
	score.y = display.contentHeight - score.height * 0.5
138
	score.text = score.text .. tostring(scoreN)
139
	score:setReferencePoint(display.TopLeftReferencePoint)
140
	score.x = 1
141
	
142
	addLives()
143
end
144
145
function addLives()
146
	for i = 1, 3 do
147
		live = display.newImage('live.png')
148
		live.x = (display.contentWidth - live.width * 0.7) - (5 * i+1) - live.width * i + 20
149
		live.y = display.contentHeight - live.height * 0.7
150
		
151
		lives.insert(lives, live)
152
	end
153
	listeners('add')
154
end
155
156
function listeners(action)
157
	if(action == 'add') then	
158
		bg:addEventListener('touch', moveShip)
159
		bg:addEventListener('tap', shoot)
160
		Runtime:addEventListener('enterFrame', update)
161
		timerSource = timer.performWithDelay(800, addEnemy, 0)
162
	else
163
		bg:removeEventListener('touch', moveShip)
164
		bg:removeEventListener('tap', shoot)
165
		Runtime:removeEventListener('enterFrame', update)
166
		timer.cancel(timerSource)
167
		--timerSource = nil
168
	end
169
end
170
171
function moveShip:touch(e)
172
	if(e.phase == 'began') then
173
		lastX = e.x - ship.x
174
	elseif(e.phase == 'moved') then
175
		ship.x = e.x - lastX
176
	end
177
end
178
179
function shoot:tap(e)
180
	local bullet = display.newImage('bullet.png')
181
	bullet.x = ship.x
182
	bullet.y = ship.y - ship.height
183
	bullet.name = 'bullet'
184
	physics.addBody(bullet)
185
	
186
	audio.play(shot)
187
	
188
	bullets.insert(bullets, bullet)
189
end
190
191
function addEnemy(e)
192
	local enemy = movieclip.newAnim({'enemyA.png', 'enemyA.png','enemyA.png','enemyA.png','enemyA.png','enemyA.png','enemyB.png','enemyB.png','enemyB.png','enemyB.png','enemyB.png','enemyB.png'})
193
	enemy.x = math.floor(math.random() * (display.contentWidth - enemy.width))
194
	enemy.y = -enemy.height
195
	enemy.name = 'enemy'
196
	physics.addBody(enemy)
197
	enemy.bodyType = 'static'
198
	enemies.insert(enemies, enemy)
199
	enemy:play()
200
	enemy:addEventListener('collision', collisionHandler)
201
end
202
203
function alert(e)
204
	listeners('remove')
205
	local alertView
206
	
207
	if(e == 'win') then
208
		alertView = display.newImage('youWon.png')
209
		alertView.x = display.contentWidth * 0.5
210
		alertView.y = display.contentHeight * 0.5
211
	else
212
		alertView = display.newImage('gameOver.png')
213
		alertView.x = display.contentWidth * 0.5
214
		alertView.y = display.contentHeight * 0.5
215
	end
216
	
217
	alertView:addEventListener('tap', restart)
218
end

Next Time...

In the next and final part of the series, we'll handle the enter frame behavior, collisions, and the final steps to take prior to release, like app testing, creating a start screen, adding an icon and, building the app. Stay tuned for the final part!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.