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