Build an endless runner game from scratch: game over & scoring

GFX9.COM share Build an endless runner game from scratch: game over & scoring, you can download now.



In this tutorial we'll be creating two new features that will really add some shine to our game: scoring and game over. It may sound like a lot of work, but our game is set up so as to make this quick and easy to do. So, let's get those features in there and complete our game!

The first feature we'll tackle is the scoring system. In order to get a good scoring system in place, we need to add some visually pleasing text to our game. You can start by downloading a new font of your choosing or by downloading the one I've included in this tutorial's download file. If you choose your own, be sure you are downloading a ".ttf" file. There are a lot of great sites available with free fonts; you can start by simply searching for "free fonts" and browse until you find something you like. The downloads file is set up the same way as in the previous projects. There is an old and a new folder: The old folder contains everything we have done up until now, and the new folder contains the project the way it will look at the end of this tutorial.

Once you have that in the folder that contains your main.lua file, go ahead and open up both the main.lua file and your build.settings file and let's make it work. The first thing that we are going to do is work with our build.settings file. It is from here that we are going to let the program know to include the custom font.

Change your build.settings file to look like this:

settings = {
     orientation = {
          default = "landscapeRight",
          content = "landscapeRight",
          supported = {
               "landscapeRight"
          },
     },
     --adds the font BorisBlackBloxx.ttf to our game
     --you will still need to reference the name in the
     --code, but if you don't have it referenced here it
     --will not work
     iphone = {
          plist = {
                UIAppFonts = {
                    "BorisBlackBloxx.ttf"
               }
          },
     },
}

Note that this will be the same even if you have an Android device. I am mainly an iOS developer when it comes to game development, so I haven't tested this on an Android device. However, this exact script should work on your Android device as well as your iOS device. (If any Android users going over this tutorial encounter issues, please let me know and I will look into it further!) Now that the above code is in place, you can go ahead and close that file as we will not need to touch it any more. We will verify that it worked by adding some text to the game.

Let's open up our main.lua file and give our players a scoring system! Right above where we create our display groups, insert the following:

--variable to hold our game's score
local score = 0
--scoreText is another variable that holds a string that has the score information
--when we update the score we will always need to update this string as well
--*****Note for android users, you may need to include the file extension of the font
-- that you choose here, so it would be BorisBlackBloxx.ttf there******
local scoreText = display.newText("score: " .. score, 0, 0, "BorisBlackBloxx", 50)
--This is important because if you dont have this line the text will constantly keep
--centering itself rather than aligning itself up neatly along a fixed point
scoreText:setReferencePoint(display.CenterLeftReferencePoint)
scoreText.x = 0
scoreText.y = 30

The next thing we'll do is add it to our our displayGroup screen. Put this after all of the other groups have been added to the screen group:

screen:insert(scoreText)

Go ahead and run that. This is going to progress very quickly, so be sure everything is working correctly before you move on. You should see something like this:

Once you've tested that to make sure it's working, we will update the score so that it will have immediate value in our game. In the updateBlocks() function there right below the line that says:

if((blocks[a]).x < -40) then, put this code:

score = score + 1
scoreText.text = "score: " .. score
scoreText:setReferencePoint(display.CenterLeftReferencePoint)
scoreText.x = 0
scoreText.y = 30

This will simply update our score by one point every time a block passes from the left side of the screen and is reinserted to the right. This can be used in many different ways, of course. It's a smart idea to update the score here because it will keep it consistent across every game. It bases the scores on how far the players have traveled. In fact, if you wanted to record how far they have traveled you would simply store the score into the variable called distanceTraveled, meters, or something similar to that.

The next feature we'll tackle addresses what happens when a player dies. We will keep it simple in this tutorial, but you should be able to see how you might incorporate more into this once we have finished. Keep in mind that adding to the score is really pretty simple to do. Let's say that you wanted to make the score increase by five every time you kill a ghost or an obstacle. You would simply put the above code in the collision detection section right where you destroy said objects.

When our character dies we are going to do several things:

1) Stop the player's movement.

2) Make the player spin in circles for dramatic effect. We're doing this here because I just want to show you the rotate function for display objects. You can of course change his death into whatever you want it to be.

3) Display a screen over the game that lets the player restart the game if they choose.

First, we need to make sure that the game speed is set to 0. Next, we need to add a variable to our monster display object. Do this by adding the following line of code where we instantiate monster:

monster.isAlive = true

With that in place we will check its status in a couple of different spots in the game. The first change we are going to make is to updateMonster(), go ahead and make updateMonster() look like this:

function updateMonster()
     --if our monster is jumping then switch to the jumping animation
     --if not keep playing the running animation
     if(monster.isAlive == true) then
          if(onGround) then
               if(wasOnGround) then

               else
                    monster:prepare("running")
                    monster:play()
               end
          else
               monster:prepare("jumping")
               monster:play()
          end
          if(monster.accel > 0) then
               monster.accel = monster.accel - 1
          end
          monster.y = monster.y - monster.accel
          monster.y = monster.y - monster.gravity
     else
          monster:rotate(5)
     end
     --update the collisionRect to stay in front of the monster
     collisionRect.y = monster.y
end

Notice that before we update the monster's position, we first check to see if the player is alive or not. If the player is alive we simply move on as we normally would. If the player is not alive, we send our monster into an endless spin. Next, we need to actually change the monster.isAlive status. Go to the checkCollisions() function and we'll make the changes. Inside the check collisions our monster's position is checked against 3 different items for collisions: blocks, ghosts, and spikes. In each of these sections we set the speed of the game to 0 when a collsion is detected. What you need to do now is add this code to each of the three sections right after we set the speed to 0.

monster.isAlive = false
--this simply pauses the current animation
monster:pause()

Now it is set so that whatever animation is playing will stop when you die. Also, the monster will spin in place because of the code we already added to updateMonster(). Run the game again and you should see this:

At this point we have two of the three things done that we needed in order to make the death process better in our game. The last thing is to make a little death screen that lets the player restart the game if they choose. Go back up to the top of file where we add all of the displayObjects and add this:

local gameOver = display.newImage("gameOver.png")
gameOver.name = "gameOver"
gameOver.x = 0
gameOver.y = 500

Because this is a simple menu I went ahead and made it one big image. I used the same method that we have been doing for the rest of the images, and stored it off-screen for later use. Now what we need to do is move this on the screen every time our player dies. Go back to the section of code we just worked with where we set monster.isAlive = false. Right below that add the following to each of those sections:

gameOver.x = display.contentWidth*.65
gameOver.y = display.contentHeight/2

Be sure to add

screen:insert(gameOver)

to your code so that gameOver shows up correctly. Put it as the last thing to be added right after the textScore. That way it is sure to be visible when we move it.

This will put the "Game Over" screen just to the right of the spinning monster so we can still see our final score in the upper left hand side of the screen. Lastly, for all of this to work, we need to to make it actually do something when we touch it. So next we will edit is the touched() function. Change your touched function to look like this:

function touched( event )
     if(event.x < gameOver.x + 150 and event.x > gameOver.x - 150 and event.y < gameOver.y + 95 and event.y > gameOver.y - 95) then
          restartGame()
     else
          if(monster.isAlive == true) then
               if(event.phase == "began") then
                    if(event.x < 241) then
                         if(onGround) then
                              monster.accel = monster.accel + 20
                         end
                    else
                         for a=1, blasts.numChildren, 1 do
                              if(blasts[a].isAlive == false) then
                                   blasts[a].isAlive = true
                                   blasts[a].x = monster.x + 50
                                   blasts[a].y = monster.y
                                   break
                              end
                         end
                    end
               end
          end
     end
end

Notice that we only made one change to this: instead of first checking to see what side of the screen the touch is on (how we determine jump or fire), we check to see if the gameOverdisplay object was touched. If it wasn't touched and monster is alive, keep jumping and firing like normal. But if gameOver was touched, it means the game is over because it is now visible and we simply call the function restartGame(). Somewhere above that add the restartGame() function and that will do everything for us.

function restartGame()
     --move menu
     gameOver.x = 0
     gameOver.y = 500
     --reset the score
     score = 0
     --reset the game speed
     speed = 5
     --reset the monster
     monster.isAlive = true
     monster.x = 110
     monster.y = 200
     monster:prepare("running")
     monster:play()
     monster.rotation = 0
     --reset the groundLevel
     groundLevel = groundMin
     for a = 1, blocks.numChildren, 1 do
          blocks[a].x = (a * 79) - 79
          blocks[a].y = groundLevel
     end
     --reset the ghosts
     for a = 1, ghosts.numChildren, 1 do
          ghosts[a].x = 800
          ghosts[a].y = 600
     end
     --reset the spikes
     for a = 1, spikes.numChildren, 1 do
          spikes[a].x = 900
          spikes[a].y = 500
     end
     --reset the blasts
     for a = 1, blasts.numChildren, 1 do
          blasts[a].x = 800
          blasts[a].y = 500
     end
     --reset the backgrounds
     backgroundfar.x = 480
     backgroundfar.y = 160
     backgroundnear1.x = 240
     backgroundnear1.y = 160
     backgroundnear2.x = 760
     backgroundnear2.y = 160
end

Run that and hopefully you should now have a small function death-screen that you can touch to restart the game.

With those small changes in place we should now have a fully functional game! The player earns a score and we handle death a little more gracefully. Everything we did here was very simple, but hopefully now you have an idea of how you might create larger menus or how you might make a more intricate death scene when you die. An easy way would simply be to add a couple of new animations that fit the different deaths, but I will let you decide what suits your game best. If you have any questions about the changes we made, please let me know in the comments. Thanks for following along!





Similar content