In this chapter, we’ll create a scene to display the ten highest scores for the game. We’ll also explore how to save these scores to a persistent location.
For the most part, when you use Composer, scenes remain lives and score variables that we use within game.lua are associated only with that scene.
This is generally good for keeping an app clean and organized, but sometimes you’ll need to access variables/objects in one scene from a different scene that’s inherently unaware of their existence. In this chapter, for instance, we’ll need to access the player’s score score from game.lua)highscores.lua scene.
Lua itself provides various ways to pass and access data between modules, but Composer makes it even easier with the following commands:
composer.setVariable() — Sets a variable declared in one scene to be accessible throughout the entire composer.getVariable() — Allows you to retrieve the value of any variable previously set via composer.setVariable().Armed with these commands, let’s update the endGame() function:
Open your game.lua file.
Locate the endGame() function and replace its contents with the following two commands:
local function endGame()
composer.setVariable( "finalScore", score )
composer.gotoScene( "highscores", { time=800, effect="crossFade" } )
end
The first new command, composer.setVariable( "finalScore", score )finalScore with an assigned value of the score variable. With this in place, we’ll be able to retrieve the value from the high scores scene.
The second command simply redirects the app to the highscores.lua scene instead of the menu scene.
game.lua file.You can pass any standard Lua variable type, including tables, as the value for composer.setVariable(). You can even use it to make a local function from one scene accessible within another. This flexibility makes composer.setVariable() and composer.getVariable() two of the most useful APIs within the Composer toolset.
Our high scores scene will basically feature the ability to store and retrieve scores, determine the ten highest, and display them.
First, make a copy of the standard scene-template.luahighscores.lua, place it within your StarExplorer project folder, and open it using your chosen text editor.
As usual, we’ll start by initializing some variables. Place the following code in the
-- -----------------------------------------------------------------------------------
-- Code outside of the scene event functions below will only be executed ONCE unless
-- the scene is removed entirely (not recycled) via "composer.removeScene()"
-- -----------------------------------------------------------------------------------
-- Initialize variables
local json = require( "json" )
local scoresTable = {}
local filePath = system.pathForFile( "scores.json", system.DocumentsDirectory )
Let’s briefly examine these commands:
With the first command, we load a new resource for our app: the JSON library. JSON is a convenient format for working with data. If you’re not familiar with JSON, don’t worry — for the purpose of this app, you just need to learn how to use the json.encode() and json.decode() commands. Respectively, these commands let you take a Lua table, store (encode) its contents in a
The next command, local scoresTable = {}
The final line generates an absolute path to a JSON file (scores.json) which we’ll use to save the ten highest scores. Don’t worry that this file doesn’t actually exist yet — this command will create the file and initiate a link to it under the variable filePath.
You may wonder why we’re creating a file to store the high scores data. Why not just store them locally in a Lua table? The reason is fundamental to app development in general. Basically, variables which exist in an app’s local memory will be destroyed if the app quits/closes!
Essentially, any data which needs to be accessed at some point after the app quits/closes should be stored in a persistent state, and the easiest way to store persistent data is to save it to a file on the device. Furthermore, this file must be stored in a persistent location.
To ensure that the scores.json file is placed within a persistent location, we specify system.DocumentsDirectory as the second parameter of the command we just entered. This tells Solar2D to create the scores.json file within the app’s internal “documents” directory. While there are other places we could put the file, we won’t go into details on them here — just remember that the documents directory, referenced by the constant system.DocumentsDirectory, is the only place which provides truly persistent storage for files that are created from within the app. In other words, even if the player quits the app and doesn’t open it again until a month later, the scores.json file will still exist.
Now that we’ve initialized the file for storing scores, let’s write a function to check for any scores which were previously saved. Of course there won’t be any at this point, but we’ll need this function eventually.
Directly following the commands you already added to the loadScores() function:
local filePath = system.pathForFile( "scores.json", system.DocumentsDirectory )
local function loadScores()
local file = io.open( filePath, "r" )
if file then
local contents = file:read( "*a" )
io.close( file )
scoresTable = json.decode( contents )
end
if ( scoresTable == nil or #scoresTable == 0 ) then
scoresTable = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
end
end
Dissecting this function, we accomplish the following:
When working with files containing data, the first step is to confirm that the file exists. The first command inside this function, local file = io.open( filePath, "r" )scores.json in the system.DocumentsDirectory folder (remember that we set the variable filePath to point to that file and folder). In this command, also note the second parameter, "r". This tells Solar2D to open the file with read access only, but that’s sufficient here because we simply need to read the file contents.
In the conditional block following, if the file exists, its contents will be dumped into the local variable contents. Once we have its contents, we close the file with io.close( file )json.decode(), we decode contents and store the values in scoresTable — basically, json.decode() converts scores.json into a Lua table which can be used in our app.
In the final conditional block, just in case the scores.json file is empty or doesn’t exist, we assign scoresTable ten default values of 0 so that the scene has something to work with.
If you want to make things more interesting, start the game with ten default scores that the “computer” scored and challenge the player to beat them! For example:
scoresTable = { 10000, 7500, 5200, 4700, 3500, 3200, 1200, 1100, 800, 500 }
Saving data is just as easy as reading data. Following the previous function in the saveScores() function:
if ( scoresTable == nil or #scoresTable == 0 ) then
scoresTable = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
end
end
local function saveScores()
for i = #scoresTable, 11, -1 do
table.remove( scoresTable, i )
end
local file = io.open( filePath, "w" )
if file then
file:write( json.encode( scoresTable ) )
io.close( file )
end
end
This function saves high scores data as follows:
First, we clear out any unneeded scores from scoresTable. Because we only want to save the highest ten scores, anything beyond that can be discarded. Using a for loop, we step backwards through the table from its total count (#scoresTable) to 11, effectively removing all but ten scores.
Next, we open the scores.json file. Unlike our io.open() call within loadScores(), here we specify "w" as the second parameter. This tells Solar2D to create (write) a new file or overwrite the file if it already exists. It also tells Solar2D to open the file with write access which is important because, when saving score data, we need to write data to the file.
Once the file is successfully open, we call file:write() to write the scoresTable data to the file, converted into JSON via the json.encode() command. Finally, we close the file with io.close( file )
Before we show the scores to the player, we need to manipulate the scoresTable table slightly. Specifically, we need to add the most recent score to the table and then sort the table entries from highest to lowest.
All of the work for this scene can be done within scene:create(), so let’s focus our attention on that function:
loadScores() function:-- create()
function scene:create( event )
local sceneGroup = self.view
-- Code here runs when the scene is first created but has not yet appeared on screen
-- Load the previous scores
loadScores()
end
scoresTable table. Notice that we get its value by calling composer.getVariable() with a sole parameter of "finalScore". This exhibits the power of the composer.setVariable() game.lua —highscores.lua). Following that, we immediately reset its value to 0 since we won’t need further record of it. -- Load the previous scores
loadScores()
-- Insert the saved score from the last game into the table, then reset it
table.insert( scoresTable, composer.getVariable( "finalScore" ) )
composer.setVariable( "finalScore", 0 )
end
To sort the table, we use the Lua table.sort() command. For this to work, we must provide it with the table to sort and a reference to a comparison function (compare()) which determines if items need to swap places. Here we’ve coded the compare() function directly inside of scene:create() because it doesn’t need to be accessible elsewhere.
-- Insert the saved score from the last game into the table, then reset it
table.insert( scoresTable, composer.getVariable( "finalScore" ) )
composer.setVariable( "finalScore", 0 )
-- Sort the table entries from highest to lowest
local function compare( a, b )
return a > b
end
table.sort( scoresTable, compare )
end
The compare() function itself takes two values which table.sort() provides. Since we are sorting a table of numbers, the two parameters a and b will be numerical values. The function compares these two values as in a greater than b?”true and table.sort() knows that it needs to swap the values. Essentially, when the table.sort() process finishes, our scoresTable values will be sorted from highest to lowest.
scores.json by calling our saveScores() function: -- Sort the table entries from highest to lowest
local function compare( a, b )
return a > b
end
table.sort( scoresTable, compare )
-- Save the scores
saveScores()
end
-- Save the scores
saveScores()
local background = display.newImageRect( sceneGroup, "background.png", 800, 1400 )
background.x = display.contentCenterX
background.y = display.contentCenterY
local highScoresHeader = display.newText( sceneGroup, "High Scores", display.contentCenterX, 100, native.systemFont, 44 )
end
for loop from 1 to 10. For each score, we’ll first set the intended y position by calculating a local yPos variable. This will make the scores run down the screen, evenly spaced apart: local highScoresHeader = display.newText( sceneGroup, "High Scores", display.contentCenterX, 100, native.systemFont, 44 )
for i = 1, 10 do
if ( scoresTable[i] ) then
local yPos = 150 + ( i * 56 )
end
end
end
For each score line, we’ll display two text objects. On the left will be a rank number from 1) to 10). Directly to its right will be the actual score:
for i = 1, 10 do
if ( scoresTable[i] ) then
local yPos = 150 + ( i * 56 )
local rankNum = display.newText( sceneGroup, i .. ")", display.contentCenterX-50, yPos, native.systemFont, 36 )
rankNum:setFillColor( 0.8 )
rankNum.anchorX = 1
local thisScore = display.newText( sceneGroup, scoresTable[i], display.contentCenterX-30, yPos, native.systemFont, 36 )
thisScore.anchorX = 0
end
end
end
Most of the above code should be straightforward, but we’re introducing an important new concept in anchors. By default, Solar2D positions the center of any display object at the x and y coordinate given. However, sometimes you’ll need to align a series of objects along their edges — here, the list of scores will look best if each rank number is

To accomplish this, notice that we set the anchorX property of each object. This property typically ranges between 0 (left) and 1 (right), with a default of 0.5 (center). Since we want each rank number to be anchorX to 1, and for each score number, we set anchorX to 0 for left alignment.
Naturally, Solar2D also supports vertical anchor points with the anchorY property. Similar to its horizontal counterpart, this property typically ranges between 0 (top) and 1 (bottom), with a default of 0.5 (center). Anchors can even be set outside of the 0 to 1 range, although this usage is less common. Setting either anchorX or anchorY to values less than 0 or greater than 1 will place the anchor point conceptually somewhere in space outside of the object’s edge boundaries, which can be useful in some instances.
"tap" event listener will call a basic gotoMenu() function which we’ll write next. end
end
local menuButton = display.newText( sceneGroup, "Menu", display.contentCenterX, 810, native.systemFont, 44 )
menuButton:setFillColor( 0.75, 0.78, 1 )
menuButton:addEventListener( "tap", gotoMenu )
end
Back up in the "tap" event for the menuButton object. This is straightforward enough:
local function gotoMenu()
composer.gotoScene( "menu", { time=800, effect="crossFade" } )
end
-- -----------------------------------------------------------------------------------
-- Scene event functions
-- -----------------------------------------------------------------------------------
Similar to the game.lua scene, let’s remove the highscores.lua scene within its own scene:hide() function. This will remove the scene from memory when players return to the menu scene.
Inside the scene:hide() function, add the following highlighted line:
-- hide()
function scene:hide( event )
local sceneGroup = self.view
local phase = event.phase
if ( phase == "will" ) then
-- Code here runs when the scene is on screen (but is about to go off screen)
elseif ( phase == "did" ) then
-- Code here runs immediately after the scene goes entirely off screen
composer.removeScene( "highscores" )
end
end
This wraps up our high score scene! Save your modified highscores.lua and game.lua files and then relaunch the Simulator. Now you should be able to play the game and see your scores saved/sorted in the high scores scene.
This scene is relatively simple, but if your code isn’t working as expected, please compare it to the highscores.lua file bundled with this chapter’s source files.
We’ve covered several more important concepts in this chapter:
| Command/Property | Description |
|---|---|
| system.pathForFile() | Generates an absolute path using |
| io.open() | Opens a file for reading or writing. |
| io.close() | Closes an open file handle. |
| file:read() | Reads a file, according to the given formats which specify what to read. |
| file:write() | Writes the value of each of its arguments to the file. |
| json.decode() | Decodes a JSON-encoded data structure and returns a Lua table with the data. |
| json.encode() | Converts a Lua table into a |
| composer.setVariable() | Sets a variable declared in one scene to be accessible throughout the entire Composer app. |
| composer.getVariable() | Allows you to retrieve the value of any variable set via composer.setVariable(). |
| table.sort() | Sorts table elements in a given order. |
| object.anchorX | Property which allows you to control the alignment of a display object along the x direction. |
| object.anchorY | Property which allows you to control the alignment of a display object along the y direction. |