Posted on by

highscoreOne frequently-asked question in the forums, especially from those developers new to Corona SDK and Lua, is “How do I keep score?”. Today’s tutorial will walk you through the entire process, including:

  1. Setting up a variable to hold the score.
  2. Displaying the score.
  3. Saving (and retrieving) the score for future use.

The Score Module

Corona does not have a built-in score module, so let’s build one which you can implement in your apps, beginning with a basic “initialization” function:

local M = {}  --create the local module table (this will hold our functions and data)
M.score = 0  --set the initial score to 0

function M.init( options )
   local customOptions = options or {}
   local opt = {}
   opt.fontSize = customOptions.fontSize or 24
   opt.font = customOptions.font or native.systemFontBold
   opt.x = customOptions.x or display.contentCenterX
   opt.y = customOptions.y or opt.fontSize*0.5
   opt.maxDigits = customOptions.maxDigits or 6
   opt.leadingZeros = customOptions.leadingZeros or false
   M.filename = customOptions.filename or "scorefile.txt"

   local prefix = ""
   if ( opt.leadingZeros ) then 
      prefix = "0"
   end
   M.format = "%" .. prefix .. opt.maxDigits .. "d"

   M.scoreText = display.newText( string.format(M.format, 0), opt.x, opt.y, opt.font, opt.fontSize )
   return M.scoreText
end

As you can see, the options we support are:

  • fontSize — The size of the displayed score text.
  • font — The font used for the displayed score text.
  • x — The x location to draw the score display.
  • y — The y location to draw the score display.
  • maxDigits — The estimated number of the max score.
  • leadingZeros true or false: do you want leading zeros?
  • filename — The local filename to save the score to.

Most of these are straightforward, but we could extend this with alignment properties and other settings. For example, we could add options to control the anchor points for left- or right-aligned score text.

The score text uses the string.format() API call to format the number, and the string will either be prefixed by spaces or by zeros, depending on the settings. This format setting (M.format) is also saved to the module for usage in other functions. If any settings aren’t specified, then we fall back to the reasonable defaults, in this case, 24-point Helvetica, centered at the top of the screen, with a maximum of 6 digits. By default, the local save file target is scorefile.txt, but this can be changed to another file name.

Set and Get Functions

Next, let’s write some functions for settinggetting, and adding to the score:

function M.set( value )
   M.score = value
   M.scoreText.text = string.format( M.format, M.score )
end

function M.get()
   return M.score
end

function M.add( amount )
   M.score = M.score + amount
   M.scoreText.text = string.format( M.format, M.score )
end

If we set the score, the display will update and overwrite the current value with the new one. The get method simply returns the current score for some other use. Finally, the add function allows us to add to the current score and update the display. This could be extended to a subtract function, but it’s more efficient to just pass a negative value to the add function.

Saving and Loading the Score

The last thing our module needs is the ability to save and load the score to a file. This is required so that the score can be saved and retrieved between app sessions.

function M.save()
   local path = system.pathForFile( M.filename, system.DocumentsDirectory )
   local file = io.open(path, "w")
   if ( file ) then
      local contents = tostring( M.score )
      file:write( contents )
      io.close( file )
      return true
   else
      print( "Error: could not read ", M.filename, "." )
      return false
   end
end

function M.load()
   local path = system.pathForFile( M.filename, system.DocumentsDirectory )
   local contents = ""
   local file = io.open( path, "r" )
   if ( file ) then
      -- read all contents of file into a string
      local contents = file:read( "*a" )
      local score = tonumber(contents);
      io.close( file )
      return score
   else
      print( "Error: could not read scores from ", M.filename, "." )
   end
   return nil
end

return M

As seen on lines 40 and 54, the score file exists in the system.DocumentsDirectory. We cannot write it to system.ResourceDirectory since it’s read-only, and both system.TemporaryDirectory and system.CachesDirectory are prone to automatic cleanup by the system. So, logically, we create and keep this file inside the documents directory so it persists between app sessions.

Let’s quickly inspect the load() function in more depth. This function opens the file, reads the value into a local variable, and returns it. It does not, by design, update the text value on the screen. In many cases, this function will be used to load the last saved score, compare it to the current score, and check if we have a new “high score.” However, if we need to both load the score and display the last saved value, we can simply call the set() function using the value we just retrieved from the load() function.

Finally, on line 68, we return the module’s M table back to the caller, thus making the functions and data available to the caller.

Putting it to Use

With our module created and the basic functions written, we can now use it within our app.

local score = require( "score" )

local scoreText = score.init({
   fontSize = 20,
   font = "Helvetica",
   x = display.contentCenterX,
   y = 20,
   maxDigits = 7,
   leadingZeros = true,
   filename = "scorefile.txt",
})

The first line is the mandatory require line which includes the module in our project, providing access to all of the functions we wrote above. The second block calls the init() function with a series of basic parameters. This creates the text display, centers it at the top of the screen, and sets it to a zero-filled seven-digit score.

From this point, the following actions can be executed with just one line:

  • score.set( value ) — sets the value.
  • local myscore = score.get() — gets the current value.
  • score.add( value ) — add value to the current score.
  • score.save() — save the score.
  • local highscore = score.load() — load the previously-saved score.

To see our code in action, please download a version of the ManyCrates sample app that has been modified to work with this module. When an object is touched, the score increases and the object is removed from the display. For variety, each object has a value set for it: 50 points for a small crate, 100 points for a large crate, and 500 points for a green container. Click on each object and watch the score increase. Two buttons have been added to showcase the save() and load() functions. To test it, save the score, reload the app (Control-R or Cmd-R), and then load the score that was just saved. Notice that the text display updates to that value!

Hopefully this tutorial exhibits just how easy it is to work with scores in Corona SDK, including saving and loading scores to the device for persistence between app sessions.


Posted by . Thanks for reading...

13 Responses to “Tutorial: How to Display and Save Scores”

  1. Bryan Mitchell

    Is there a way for Corona to hook into the NSUserDefaults? I’d like to update my game Geared to the corona engine but I saved all of my players data using NSUserDefaults. This was a silly mistake (I was pretty new to game development when I started that game in 2009), but the data was also super simple. (A level is either completed or its not).

    Reply
  2. Joel

    Hi Corona,

    Thanks for the article..

    Say in my game , I save the user data like currentLevel, currentScore etc .. when I want the users to update to a new version – will this data be erased ?.. how is this handled in Corona SDK , for Android ..

    regards,
    Joel

    Reply
    • Rob Miracle

      When people upgrade, IOS and Android preserve the app’s sandbox files ( system.DocumentsDirectory, system.CachesDirectory and probably system.TemporaryDirectory). The only thing that gets replaced is the app’s bundle itself, so your data files are protected unless they delete the app. If they delete the app, the data goes away too.

      But the upgrade process is non-destructive.

      Reply
  3. Arun

    I have used the code in this tutorial and it works great!

    But how do I show the highscore? When i put the highscore code in, it doesn’t come up with an error, but it doesn’t seem to do anything! How can I show the highscore on screen?

    thanks.

    Reply

Leave a Reply

  • (Will Not Be Published)