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!
Comments