Corona SDK: Build a Space Shooter – Adding Interactivity

Corona SDK: Build a Space Shooter – Adding Interactivity

Tutorial Details
  • Technology: Corona SDK
  • Difficulty: Intermediate
  • Completion Time: 30 - 60 Minutes

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.

local shot = audio.loadSound('shot.mp3')
local explo = audio.loadSound('explo.mp3')
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.

local timerSource
local lives = display.newGroup()
local bullets = display.newGroup()
local enemies = display.newGroup()
local scoreN = 0
local bossHealth = 20

Step 3: Declare Functions

Declare all functions as local at the start.

local Main = {}
local addTitleView = {}
local showCredits = {}
local removeCredits = {}
local removeTitleView = {}
local addShip = {}
local addScore = {}
local addLives = {}
local listeners = {}
local moveShip = {}
local shoot = {}
local addEnemy = {}
local alert = {}
local update = {}
local collisionHandler = {}
local restart = {}

Step 4: Constructor

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

function Main()
	addTitleView()
end

Step 5: Add a Title View

Now we place the background and TitleView in the stage.

function addTitleView()
	title = display.newImage('title.png')
	playBtn = display.newImage('playBtn.png')
	playBtn.x = display.contentCenterX
	playBtn.y = display.contentCenterY + 10
	playBtn:addEventListener('tap', removeTitleView)
	creditsBtn = display.newImage('creditsBtn.png')
	creditsBtn.x = display.contentCenterX
	creditsBtn.y = display.contentCenterY + 60
	creditsBtn:addEventListener('tap', showCredits)
	titleView = display.newGroup(title, playBtn, creditsBtn)
end

Step 6: Remove the Title View

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

function removeTitleView:tap(e)
	transition.to(titleView,  {time = 300, y = -display.contentHeight, onComplete = function() display.remove(titleView) titleView = null addShip() end})
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.

function showCredits:tap(e)
	creditsBtn.isVisible = false
	creditsView = display.newImage('creditsView.png')
	creditsView:setReferencePoint(display.TopLeftReferencePoint)
	transition.from(creditsView, {time = 300, x = display.contentWidth})
	creditsView:addEventListener('tap', removeCredits)
end

Step 8: Hide Credits

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

function removeCredits:tap(e)
	creditsBtn.isVisible = true
	transition.to(creditsView, {time = 300, x = display.contentWidth, onComplete = function() display.remove(creditsView) creditsView = null end})
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:

function addShip()
	ship = movieclip.newAnim({'shipA.png', 'shipB.png'})
	ship.x = display.contentWidth * 0.5
	ship.y = display.contentHeight - ship.height
	ship.name = 'ship'
	ship:play()
	physics.addBody(ship)
	addScore()
end

Step 10: Add Score

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

function addScore()
	score = display.newText('Score: ', 1, 0, native.systemFontBold, 14)
	score.y = display.contentHeight - score.height * 0.5
	score.text = score.text .. tostring(scoreN)
	score:setReferencePoint(display.TopLeftReferencePoint)
	score.x = 1
	addLives()
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.

function addLives()
	for i = 1, 3 do
		live = display.newImage('live.png')
		live.x = (display.contentWidth - live.width * 0.7) - (5 * i+1) - live.width * i + 20
		live.y = display.contentHeight - live.height * 0.7
		lives.insert(lives, live)
	end
	listeners('add')
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.

function listeners(action)
	if(action == 'add') then
		bg:addEventListener('touch', moveShip)
		bg:addEventListener('tap', shoot)
		Runtime:addEventListener('enterFrame', update)
		timerSource = timer.performWithDelay(800, addEnemy, 0)
	else
		bg:removeEventListener('touch', moveShip)
		bg:removeEventListener('tap', shoot)
		Runtime:removeEventListener('enterFrame', update)
		timer.cancel(timerSource)
	end
end

Step 13: Move Ship

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

function moveShip:touch(e)
	if(e.phase == 'began') then
		lastX = e.x - ship.x
	elseif(e.phase == 'moved') then
		ship.x = e.x - lastX
	end
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.

function shoot:tap(e)
	local bullet = display.newImage('bullet.png')
	bullet.x = ship.x
	bullet.y = ship.y - ship.height
	bullet.name = 'bullet'
	physics.addBody(bullet)
	audio.play(shot)
	bullets.insert(bullets, bullet)
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.

function addEnemy(e)
	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'})
	enemy.x = math.floor(math.random() * (display.contentWidth - enemy.width))
	enemy.y = -enemy.height
	enemy.name = 'enemy'
	physics.addBody(enemy)
	enemy.bodyType = 'static'
	enemies.insert(enemies, enemy)
	enemy:play()
	enemy:addEventListener('collision', collisionHandler)
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.

function alert(e)
	listeners('remove')
	local alertView
	if(e == 'win') then
		alertView = display.newImage('youWon.png')
		alertView.x = display.contentWidth * 0.5
		alertView.y = display.contentHeight * 0.5
	else
		alertView = display.newImage('gameOver.png')
		alertView.x = display.contentWidth * 0.5
		alertView.y = display.contentHeight * 0.5
	end
	alertView:addEventListener('tap', restart)
end

Step 17: Code Review

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

-- Space Shooter Game
-- Developed by Carlos Yanez
-- Hide Status Bar
display.setStatusBar(display.HiddenStatusBar)
-- Import MovieClip Library
local movieclip = require('movieclip')
-- Import Physics
local physics = require('physics')
physics.start()
physics.setGravity(0, 0)
-- Graphics
-- Background
local bg = display.newImage('bg.png')
-- [Title View]
local title
local playBtn
local creditsBtn
local titleView
-- [Credits]
local creditsView
-- [Ship]
local ship
-- [Boss]
local boss
-- [Score]
local score
-- [Lives]
local lives
-- Load Sounds
local shot = audio.loadSound('shot.mp3')
local explo = audio.loadSound('explo.mp3')
local bossSound = audio.loadSound('boss.mp3')
-- Variables
local timerSource
local lives = display.newGroup()
local bullets = display.newGroup()
local enemies = display.newGroup()
local scoreN = 0
local bossHealth = 20
-- Functions
local Main = {}
local addTitleView = {}
local showCredits = {}
local removeCredits = {}
local removeTitleView = {}
local addShip = {}
local addScore = {}
local addLives = {}
local listeners = {}
local moveShip = {}
local shoot = {}
local addEnemy = {}
local alert = {}
local update = {}
local collisionHandler = {}
local restart = {}
-- Main Function
function Main()
	addTitleView()
end
function addTitleView()
	title = display.newImage('title.png')
	playBtn = display.newImage('playBtn.png')
	playBtn.x = display.contentCenterX
	playBtn.y = display.contentCenterY + 10
	playBtn:addEventListener('tap', removeTitleView)
	creditsBtn = display.newImage('creditsBtn.png')
	creditsBtn.x = display.contentCenterX
	creditsBtn.y = display.contentCenterY + 60
	creditsBtn:addEventListener('tap', showCredits)
	titleView = display.newGroup(title, playBtn, creditsBtn)
end
function removeTitleView:tap(e)
	transition.to(titleView,  {time = 300, y = -display.contentHeight, onComplete = function() display.remove(titleView) titleView = null addShip() end})
end
function showCredits:tap(e)
	creditsBtn.isVisible = false
	creditsView = display.newImage('creditsView.png')
	creditsView:setReferencePoint(display.TopLeftReferencePoint)
	transition.from(creditsView, {time = 300, x = display.contentWidth})
	creditsView:addEventListener('tap', removeCredits)
end
function removeCredits:tap(e)
	creditsBtn.isVisible = true
	transition.to(creditsView, {time = 300, x = display.contentWidth, onComplete = function() display.remove(creditsView) creditsView = null end})
end
function addShip()
	ship = movieclip.newAnim({'shipA.png', 'shipB.png'})
	ship.x = display.contentWidth * 0.5
	ship.y = display.contentHeight - ship.height
	ship.name = 'ship'
	ship:play()
	physics.addBody(ship)
	addScore()
end
function addScore()
	score = display.newText('Score: ', 1, 0, native.systemFontBold, 14)
	score.y = display.contentHeight - score.height * 0.5
	score.text = score.text .. tostring(scoreN)
	score:setReferencePoint(display.TopLeftReferencePoint)
	score.x = 1
	addLives()
end
function addLives()
	for i = 1, 3 do
		live = display.newImage('live.png')
		live.x = (display.contentWidth - live.width * 0.7) - (5 * i+1) - live.width * i + 20
		live.y = display.contentHeight - live.height * 0.7
		lives.insert(lives, live)
	end
	listeners('add')
end
function listeners(action)
	if(action == 'add') then
		bg:addEventListener('touch', moveShip)
		bg:addEventListener('tap', shoot)
		Runtime:addEventListener('enterFrame', update)
		timerSource = timer.performWithDelay(800, addEnemy, 0)
	else
		bg:removeEventListener('touch', moveShip)
		bg:removeEventListener('tap', shoot)
		Runtime:removeEventListener('enterFrame', update)
		timer.cancel(timerSource)
		--timerSource = nil
	end
end
function moveShip:touch(e)
	if(e.phase == 'began') then
		lastX = e.x - ship.x
	elseif(e.phase == 'moved') then
		ship.x = e.x - lastX
	end
end
function shoot:tap(e)
	local bullet = display.newImage('bullet.png')
	bullet.x = ship.x
	bullet.y = ship.y - ship.height
	bullet.name = 'bullet'
	physics.addBody(bullet)
	audio.play(shot)
	bullets.insert(bullets, bullet)
end
function addEnemy(e)
	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'})
	enemy.x = math.floor(math.random() * (display.contentWidth - enemy.width))
	enemy.y = -enemy.height
	enemy.name = 'enemy'
	physics.addBody(enemy)
	enemy.bodyType = 'static'
	enemies.insert(enemies, enemy)
	enemy:play()
	enemy:addEventListener('collision', collisionHandler)
end
function alert(e)
	listeners('remove')
	local alertView
	if(e == 'win') then
		alertView = display.newImage('youWon.png')
		alertView.x = display.contentWidth * 0.5
		alertView.y = display.contentHeight * 0.5
	else
		alertView = display.newImage('gameOver.png')
		alertView.x = display.contentWidth * 0.5
		alertView.y = display.contentHeight * 0.5
	end
	alertView:addEventListener('tap', restart)
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!

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • JudgeProphet

    Very interesting. It’s seem easy explain that way ;)

  • Rachel

    Thank you for releasing part 2! I am eagerly awaiting the final part!

  • Dan

    Enjoying the tutorial, thanks! Source download fails with permissions error.

    • http://mobile.tutsplus.com Mark Hammonds

      Download should be fixed now. Thanks for pointing this out!

  • Rachel

    When’s part 3 due out?

  • Joe

    Thanks for the tut. I’ve got a problem displaying the ‘lives’ images. It doesn’t show any compilation error but the don’t show up on screen.