Game Over, Man!
Insert the game over code after the runtimeListener function:
function gameOver() gameOverText = display.newText("Game Over", 50, 240, native.systemFontBold, 40) local function removeGOText() gameOverText:removeSelf() end timer.performWithDelay(2000, removeGOText) player:removePlayer() Runtime:removeEventListener("enterFrame", runtimeListener) Runtime:removeEventListener("accelerometer", accelerometerCall) Runtime:removeEventListener("touch", touchListener) loader:removeAllSprites() localGroup = nil timer.performWithDelay(2000, startOver) end
The first line should look familiar from the very beginning of the tutorial. We're using the display.newText function to display the text "Game Over" to the screen.
Next we create a function to remove the text, for when we restart, and we schedule that function to run after 2000 ms.
Then we start cleaning up all the objects that we've created. The player is removed first, with a removePlayer function that we need to build. Next all the global Runtime listeners are removed. Don't worry about the accelerometer listener, we'll add that in a second.
Next we use a LevelHelper function 'removeAllSprites.' This is a great way to remove all the objects created by LevelHelper. As stated earlier, this function will clean everything up that was initially created by LevelHelper. If we removed something without using the LevelHelper remove functions, this would throw an error.
We set localGroup to nil. This allows the garbage collector to do it's work. It should be empty, but we do it as good practice. Finally, we call a new function, startOver. It will reinitialize our level by invoking the loadLevel function, creating a new player, etc.
Lets create the code that removes the player. The following code should appear right before the return p line in the newPlayer() function:
function p:removePlayer() Runtime:removeEventListener("enterFrame", self) loader:removeSpriteWithUniqueName(self.uniqueName) loader:removeSpriteWithUniqueName(backarm.uniqueName) loader:removeSpriteWithUniqueName(frontarm.uniqueName) end
Most callback functions will remove themselves when the sprite is removed. However, the enterFrame function is an exception to that. It will continue to fire and throw an error if the sprite has been removed from memory (referring to a nil variable). So, we remove that first.
When that's done we can go ahead and remove the rest of the sprites from our level.
Here's the startOver function, you can place it before or after the gameOver() function:
function startOver() loadLevel() Runtime:addEventListener("enterFrame", runtimeListener) Runtime:addEventListener("accelerometer", accelerometerCall) Runtime:addEventListener("touch", touchListener) end
This should make sense. We're just calling the same method to load the level as we did in the first place. Then we're adding the listeners back to the Runtime object.
One thing we haven't done yet is create the accelerometer code that will allow us to use tilt to move our player like Doodle Jump. We can touch/click to move him, but it would be more fun if we could use the tilt. That's an easy fix. Lets go ahead create the accelerometer function now:
function accelerometerCall(e) px, py = player:getLinearVelocity() player:setLinearVelocity(e.xGravity * 700, py) end Runtime:addEventListener("accelerometer", accelerometerCall)
The accelerometer event has an xGravity and a yGravity property. Here we are simply getting the y velocity, so we don't interrupt the momentum in the vertical direction, and then applying the force of the e.xGravity value times 700 to the player. The 700 value is arbitrary, I just played with it until it felt about right. This provides a tilt control of our player if we are playing on a device.
In order to build for the device in corona, you must have an apple developer account. If you are using the trial version of Corona, you can use a development certificate to create an app build for the device. The resulting .app file can be installed on the device through itunes or Xcode organizer. For more information on this process go here.
Go back to the runtime listener now and remove the comment dashes before the call to gameOver(). Save and run in the simulator. You should now be able to die and restart the game by running into a monster or falling off screen.
We're almost finished, there are just a few odds and ends left. We want to have the monsters look where they are going, currently they always look to the left. We'll do that with a flipMonsters function in our runtimeListener function. Go ahead and uncomment that call in the runtimeListener function and add the following code before the newPlayer function towards the beginning of the file:
function flipMonsters() local myMonsters = loader:spritesWithTag(LevelHelper_TAG.MONSTER) for n = 1, #myMonsters do if myMonsters[n].prevX == nil then myMonsters[n].prevX = myMonsters[n].x elseif myMonsters[n].prevX - myMonsters[n].x > 0 then myMonsters[n].xScale = -1 else myMonsters[n].xScale = 1 end myMonsters[n].prevX = myMonsters[n].x end end
The spritesWithTag call will generate an array, technically a table, of any of the tagged sprites in the level. Next we create a for loop. A couple of things of note in lua. Lua tables are indexed starting with a 1 instead of 0. Any array length can be accessed by prefixing the array variable name with #.
In this loop we have to create a new variable the was the previous x position of the sprite. Because the sprites are static in type, they don't have linear velocity values. We need to first check if the monster has a prevX attribute already populated. The first time this function runs trying to access the prevX attribut will throw an error (prevX will be nil).
In Lua, all objects are tables and we can add attributes at any time.
On the second time around, once prevX exists, we test whether the sprite is moving left or right (prevX - x is negative=left or positive=right). We then set the xScale property accordingly. Finally we reset the prevX value to the current x, in preparation for the next time around.
If you save and run now your monsters should face the direction they are moving. Make sure that you have removed the comment dashes in front of the flipMonster() call in the runtimeListener function.
Now we'e done right? Well, almost - we have to let the user win (so Charlie Sheen can play!)
So let's create a gameWon function and fire it when we get to the top of the level.
Here's the function, place it somewhere after the startOver() function:
function gameWon() print("you win") timer.performWithDelay(2000, function() physics.pause() end) gameWonText = display.newText("YOU WIN!", 50, 240, native.systemFontBold, 40) end
We simply print to the console and on screen that you have won. Also, after a two second delay, we pause the physics engine so you don't keep bouncing. Notice that in lua we can create a function declaration and pass it in as an argument to a function. When we do this we needn't give it a name.
We'll fire this function by creating one last bezier path. Go back to LevelHelper and create a new bezier shape. A line created by two points will do. Highlight the new shape and click 'Is Sensor.' Also, give it the CHECKPOINT tag. Make sure it has a category bit of 1. That's right, ladies and gents, bezier shapes can have physics properties.
Now add the following code to the pCollision function (in the newPlayer() function) to call the gameWon() function. This code should appear right after the object = event.other line:
if object.tag == LevelHelper_TAG.CHECKPOINT then print("CheckPoint") gameWon() end
Save and run, you should now be able to climb to the top of your level and WIN THE GAME!!! Feel the tiger blood running through your veins.
Congratulations, you have the skills to create level upon level of 2D scrolling goodness!