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.
local Main = {} local addTitleView = {} local startButtonListeners = {} local showCredits = {} local hideCredits = {} local destroyCredits = {} local showGameView = {} local destroyTitleView = {} local addListeners = {} local newBlock = {} local timesUp = {} local placeBlock = {} local blockPlaced = {} local complete = {} local removeBlocks = {} local alert = {} local alertHandler = {} local restart = {}
Step 2: Constructor
Next we'll create the function that will initialize all the game logic:
function Main() addTitleView() end
Step 3: Add Title View
Now we place the background and titleView in the stage.
function addTitleView() bg = display.newImage('bg.png') title = display.newImage('title.png') title.x = display.contentCenterX title.y = 100 startB = display.newImage('startButton.png') startB.x = display.contentCenterX startB.y = display.contentCenterY startB.name = 'startB' creditsB = display.newImage('creditsButton.png') creditsB.x = display.contentCenterX creditsB.y = display.contentCenterY + 40 creditsB.name = 'creditsB' titleView = display.newGroup() titleView:insert(title) titleView:insert(startB) titleView:insert(creditsB) startButtonListeners('add') 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.
function startButtonListeners(action) if(action == 'add') then creditsB:addEventListener('tap', showCredits) startB:addEventListener('tap', showGameView) else creditsB:removeEventListener('tap', showCredits) startB:removeEventListener('tap', showGameView) end 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.
function showCredits() credits = display.newImage('credits.png') transition.from(credits, {time = 300, x = bg.contentWidth * 2, transition = easing.outExpo}) credits:addEventListener('tap', hideCredits) titleView.isVisible = false end
Step 6: Hide Credits
When the credits screen is tapped, it will be tweened out of the stage and removed.
function hideCredits() titleView.isVisible = true transition.to(credits, {time = 300, x = bg.contentWidth * 2, transition = easing.outExpo, onComplete = destroyCredits}) end function destroyCredits() credits:removeEventListener('tap', hideCredits) display.remove(credits) credits = nil end
Step 7: Add Game View
When the Start button is pressed, the title view is tweened and removed, revealing the game view.
function showGameView(e) transition.to(titleView, {time = 300, y = -titleView.height, transition = easing.inExpo, onComplete = destroyTitleView}) end
Step 8: Remove the Title View
The title view is removed from memory and the game graphics are added to the stage.
function destroyTitleView() display.remove(titleView) titleView = nil -- Add GameView Graphics up = display.newImage('container.png') up:setReferencePoint(display.TopLeftReferencePoint) up.x = 125 up.y = 100 right = display.newImage('container.png') right:setReferencePoint(display.TopLeftReferencePoint) right.x = 230 right.y = 205 down = display.newImage('container.png') down:setReferencePoint(display.TopLeftReferencePoint) down.x = 125 down.y = 310 left = display.newImage('container.png') left:setReferencePoint(display.TopLeftReferencePoint) left.x = 20 left.y = 205 holder = display.newImage('container.png') holder:setReferencePoint(display.TopLeftReferencePoint) holder.x = 125 holder.y = 205 -- Lives & Score Text livesText = display.newText('Lives', 10, 10, 'Orbitron-Medium', 12) livesText:setTextColor(163, 255, 36) livesTF = display.newText('5', 24, 30, 'Orbitron-Medium', 12) livesTF:setTextColor(163, 255, 36) scoreText = display.newText('Score', 260, 10, 'Orbitron-Medium', 12) scoreText:setTextColor(163, 255, 36) scoreTF = display.newText('0', 274, 30, 'Orbitron-Medium', 12) scoreTF:setTextColor(163, 255, 36) gameView = display.newGroup() gameView:insert(up) gameView:insert(right) gameView:insert(down) gameView:insert(left) gameView:insert(holder) gameView:insert(livesText) gameView:insert(livesTF) gameView:insert(scoreText) gameView:insert(scoreTF) addListeners() 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.
function addListeners() up:addEventListener('tap', placeBlock) right:addEventListener('tap', placeBlock) down:addEventListener('tap', placeBlock) left:addEventListener('tap', placeBlock) lives = 5 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
-- Create a var for every container to determine when full up.blocks = 0 right.blocks = 0 down.blocks = 0 left.blocks = 0 -- Arrays used to remove blocks and detect color up.blocksGFX = {} right.blocksGFX = {} down.blocksGFX = {} left.blocksGFX = {} -- Create an boolean for every container to avoid placing blocks in the same position up.a = false right.a = false down.a = false left.a = false up.b = false right.b = false down.b = false left.b = false up.c = false right.c = false down.c = false left.c = false up.d = false right.d = false down.d = false left.d = false -- Give a name to the containers to identify them later up.name = 'up' right.name = 'right' down.name = 'down' left.name = 'left' newBlock(true) 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.
function newBlock(firstTime) -- New Block local randomBlock = math.floor(math.random() * 3) + 1 local block if(blockColor[randomBlock] == 'orange') then block = display.newImage('orangeBlock.png') block.name = 'orange' block:setReferencePoint(display.TopLeftReferencePoint) elseif(blockColor[randomBlock] == 'green') then block = display.newImage('greenBlock.png') block.name = 'green' block:setReferencePoint(display.TopLeftReferencePoint) elseif(blockColor[randomBlock] == 'purple') then block = display.newImage('purpleBlock.png') block.name = 'purple' block:setReferencePoint(display.TopLeftReferencePoint) 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.
currentXPosition = positions[math.floor(math.random() * 2) + 1] currentYPosition = positions[math.floor(math.random() * 2) + 1] block.x = holder.x + currentXPosition block.y = holder.y + currentYPosition table.insert(blocks, block) 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.
local position = {currentXPosition, currentYPosition} 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 display.remove(block) block = nil newBlock(false) 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 display.remove(block) block = nil newBlock(false) 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 display.remove(block) block = nil newBlock(false) 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 display.remove(block) block = nil newBlock(false) end
Step 14: Start Timer
The timer starts counting when the function is called for the first time.
if(firstTime) then -- Start Timer timerSource = timer.performWithDelay(3000, timesUp, 0) end 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.
function timesUp:timer(e) -- Remove Life lives = lives - 1 livesTF.text = lives 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.
display.remove(blocks[#blocks]) 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.
if(lives < 1) then alert() else -- Next Block newBlock(false) end end
Step 18: Code Review
Here is the full code written in this tutorial alongside with comments to help you identify each part:
-- Sort 'Frenzic' like Game -- Developed by Carlos Yanez -- Hide Status Bar display.setStatusBar(display.HiddenStatusBar) -- Graphics -- [Background] local bg -- [Title View] local title local startB local creditsB -- [TitleView Group] local titleView -- [Score & Lives] local livesText local livesTF local lives local scoreText local scoreTF local score -- [GameView] local up local right local down local left local holder --[GameView Group] local gameView -- [CreditsView] local credits -- Variables local blockColor = {'orange', 'green', 'purple'} local blocks = {} local positions = {5, 35} local currentXPosition local currentYPosition local eventTarget local timerSource local lives local score local bell local bell4 local buzz -- Functions local Main = {} local addTitleView = {} local startButtonListeners = {} local showCredits = {} local hideCredits = {} local destroyCredits = {} local showGameView = {} local destroyTitleView = {} local addListeners = {} local newBlock = {} local timesUp = {} local placeBlock = {} local blockPlaced = {} local complete = {} local removeBlocks = {} local alert = {} local alertHandler = {} local restart = {} function Main() addTitleView() end function addTitleView() bg = display.newImage('bg.png') title = display.newImage('title.png') title.x = display.contentCenterX title.y = 100 startB = display.newImage('startButton.png') startB.x = display.contentCenterX startB.y = display.contentCenterY startB.name = 'startB' creditsB = display.newImage('creditsButton.png') creditsB.x = display.contentCenterX creditsB.y = display.contentCenterY + 40 creditsB.name = 'creditsB' titleView = display.newGroup() titleView:insert(title) titleView:insert(startB) titleView:insert(creditsB) startButtonListeners('add') end function startButtonListeners(action) if(action == 'add') then creditsB:addEventListener('tap', showCredits) startB:addEventListener('tap', showGameView) else creditsB:removeEventListener('tap', showCredits) startB:removeEventListener('tap', showGameView) end end function showCredits() credits = display.newImage('credits.png') transition.from(credits, {time = 300, x = bg.contentWidth * 2, transition = easing.outExpo}) credits:addEventListener('tap', hideCredits) titleView.isVisible = false end function hideCredits() titleView.isVisible = true transition.to(credits, {time = 300, x = bg.contentWidth * 2, transition = easing.outExpo, onComplete = destroyCredits}) end function destroyCredits() credits:removeEventListener('tap', hideCredits) display.remove(credits) credits = nil end function showGameView(e) transition.to(titleView, {time = 300, y = -titleView.height, transition = easing.inExpo, onComplete = destroyTitleView}) end function destroyTitleView() display.remove(titleView) titleView = nil -- Add GameView Graphics up = display.newImage('container.png') up:setReferencePoint(display.TopLeftReferencePoint) up.x = 125 up.y = 100 right = display.newImage('container.png') right:setReferencePoint(display.TopLeftReferencePoint) right.x = 230 right.y = 205 down = display.newImage('container.png') down:setReferencePoint(display.TopLeftReferencePoint) down.x = 125 down.y = 310 left = display.newImage('container.png') left:setReferencePoint(display.TopLeftReferencePoint) left.x = 20 left.y = 205 holder = display.newImage('container.png') holder:setReferencePoint(display.TopLeftReferencePoint) holder.x = 125 holder.y = 205 -- Lives & Score Text livesText = display.newText('Lives', 10, 10, 'Orbitron-Medium', 12) livesText:setTextColor(163, 255, 36) livesTF = display.newText('5', 24, 30, 'Orbitron-Medium', 12) livesTF:setTextColor(163, 255, 36) scoreText = display.newText('Score', 260, 10, 'Orbitron-Medium', 12) scoreText:setTextColor(163, 255, 36) scoreTF = display.newText('0', 274, 30, 'Orbitron-Medium', 12) scoreTF:setTextColor(163, 255, 36) gameView = display.newGroup() gameView:insert(up) gameView:insert(right) gameView:insert(down) gameView:insert(left) gameView:insert(holder) gameView:insert(livesText) gameView:insert(livesTF) gameView:insert(scoreText) gameView:insert(scoreTF) addListeners() end function addListeners() up:addEventListener('tap', placeBlock) right:addEventListener('tap', placeBlock) down:addEventListener('tap', placeBlock) left:addEventListener('tap', placeBlock) lives = 5 score = 0 -- Create a var for every container to determine when full up.blocks = 0 right.blocks = 0 down.blocks = 0 left.blocks = 0 -- Arrays used to remove blocks and detect color up.blocksGFX = {} right.blocksGFX = {} down.blocksGFX = {} left.blocksGFX = {} -- Create an boolean for every container to avoid placing blocks in the same position up.a = false right.a = false down.a = false left.a = false up.b = false right.b = false down.b = false left.b = false up.c = false right.c = false down.c = false left.c = false up.d = false right.d = false down.d = false left.d = false -- Give a name to the containers to identify them later up.name = 'up' right.name = 'right' down.name = 'down' left.name = 'left' newBlock(true) end function newBlock(firstTime) -- New Block local randomBlock = math.floor(math.random() * 3) + 1 local block if(blockColor[randomBlock] == 'orange') then block = display.newImage('orangeBlock.png') block.name = 'orange' block:setReferencePoint(display.TopLeftReferencePoint) elseif(blockColor[randomBlock] == 'green') then block = display.newImage('greenBlock.png') block.name = 'green' block:setReferencePoint(display.TopLeftReferencePoint) elseif(blockColor[randomBlock] == 'purple') then block = display.newImage('purpleBlock.png') block.name = 'purple' block:setReferencePoint(display.TopLeftReferencePoint) end currentXPosition = positions[math.floor(math.random() * 2) + 1] currentYPosition = positions[math.floor(math.random() * 2) + 1] block.x = holder.x + currentXPosition block.y = holder.y + currentYPosition table.insert(blocks, block) gameView:insert(block) -- Check for an available space to move the block local position = {currentXPosition, currentYPosition} 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 display.remove(block) block = nil newBlock(false) 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 display.remove(block) block = nil newBlock(false) 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 display.remove(block) block = nil newBlock(false) 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 display.remove(block) block = nil newBlock(false) end -- Start Timer the first time the function is called if(firstTime) then -- Start Timer timerSource = timer.performWithDelay(3000, timesUp, 0) end end function timesUp:timer(e) -- Remove Live lives = lives - 1 livesTF.text = lives media.playEventSound('buzz.caf') -- Remove Unused Block display.remove(blocks[#blocks]) table.remove(blocks) -- Check if out of lives if(lives < 1) then --alert() else -- Next Block newBlock(false) end 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!
Comments