Chapter 6 — 实现高分(Implementing High Scores)
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.
- Composer-Accessible Data
- High Scores Scene
- Loading Data
- Saving Data
- Displaying High Scores
- Leaving the Scene
- Scene Cleanup
- Chapter Concepts
Composer-Accessible Data
For the most part, when you use Composer, scenes remain self-contained, meaning that variables, functions, and objects which you create within a given scene are isolated to that specific scene. For example, the 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) inside our new 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-structured app.composer.getVariable()— Allows you to retrieve the value of any variable previously set viacomposer.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 ), creates a Composer-accessible variable named 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.
- Save your modified
game.luafile.
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.
High Scores Scene
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.lua file, included with this chapter’s source files. Rename this copy to highscores.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 scene-accessible code area:
-- ------------------------------------------------------------------------------------- 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 variableslocal 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 built-injson.encode() and json.decode() commands. Respectively, these commands let you take a Lua table, store (encode) its contents in a well-understood format, and later translate (decode) that data back into a Lua table.
-
The next command, local scoresTable = {}, simply creates an empty table which will eventually contain the retrieved scores, or an updated list of scores to save.
-
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.
Important
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.
Loading Data
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 scene-accessible code area, add the following loadScores() function:
local filePath = system.pathForFile( "scores.json", system.DocumentsDirectory )local function loadScores()local file = io.open( filePath, "r" )if file thenlocal contents = file:read( "*a" )io.close( file )scoresTable = json.decode( contents )endif ( scoresTable == nil or #scoresTable == 0 ) thenscoresTable = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }endend
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" ), attempts to open the file 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 ). Then, using the command 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
Saving data is just as easy as reading data. Following the previous function in the scene-accessible code area, add a saveScores() function:
if ( scoresTable == nil or #scoresTable == 0 ) thenscoresTable = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }endendlocal function saveScores()for i = #scoresTable, 11, -1 dotable.remove( scoresTable, i )endlocal file = io.open( filePath, "w" )if file thenfile:write( json.encode( scoresTable ) )io.close( file )endend
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 ).
Displaying High Scores
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:
Before we can do anything with score data, we need to load the existing scores. Let’s do so by calling the
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 scoresloadScores()end
Next, let’s insert the player’s most recent score into the
scoresTabletable. Notice that we get its value by callingcomposer.getVariable()with a sole parameter of"finalScore". This exhibits the power of thecomposer.setVariable()command — added togame.lua— for setting a Composer-accessible variable which we can now access in this scene (highscores.lua). Following that, we immediately reset its value to0since we won’t need further record of it.-- Load the previous scoresloadScores()-- Insert the saved score from the last game into the table, then reset ittable.insert( scoresTable, composer.getVariable( "finalScore" ) )composer.setVariable( "finalScore", 0 )end
At this point, we don’t know where our score ranks among the existing high scores. It might not even beat the lowest score among the ten highest! To check this, let’s sort the table’s values from highest to lowest. This will automatically place our score in the correct order. If it’s not high enough to make the top ten, it will remain in the 11th slot, but since we’ll only show ten scores, you won’t see it on the screen.
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 ittable.insert( scoresTable, composer.getVariable( "finalScore" ) )composer.setVariable( "finalScore", 0 )-- Sort the table entries from highest to lowestlocal function compare( a, b )return a > bendtable.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 “is a greater than b?” If true, it returns 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.
With the table now sorted, let’s save the data back out to
scores.jsonby calling oursaveScores()function:-- Sort the table entries from highest to lowestlocal function compare( a, b )return a > bendtable.sort( scoresTable, compare )-- Save the scoressaveScores()end
Following this, let’s create the scene background and a text object — nothing special here; by now you’re an expert at this!
-- Save the scoressaveScores()local background = display.newImageRect( sceneGroup, "background.png", 800, 1400 )background.x = display.contentCenterXbackground.y = display.contentCenterYlocal highScoresHeader = display.newText( sceneGroup, "High Scores", display.contentCenterX, 100, native.systemFont, 44 )end
Next, let’s display the scores using a simple
forloop from1to10. For each score, we’ll first set the intended y position by calculating a localyPosvariable. 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 doif ( scoresTable[i] ) thenlocal yPos = 150 + ( i * 56 )endendend
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 doif ( scoresTable[i] ) thenlocal yPos = 150 + ( i * 56 )local rankNum = display.newText( sceneGroup, i .. ")", display.contentCenterX-50, yPos, native.systemFont, 36 )rankNum:setFillColor( 0.8 )rankNum.anchorX = 1local thisScore = display.newText( sceneGroup, scoresTable[i], display.contentCenterX-30, yPos, native.systemFont, 36 )thisScore.anchorX = 0endendend
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 right-aligned and each score is left-aligned.

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 right-aligned with the others, we set 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.
Finally, let’s create a button (text object) to go back to the menu. This is just like the text buttons you created in the menu scene, and its
"tap"event listener will call a basicgotoMenu()function which we’ll write next.endendlocal menuButton = display.newText( sceneGroup, "Menu", display.contentCenterX, 810, native.systemFont, 44 )menuButton:setFillColor( 0.75, 0.78, 1 )menuButton:addEventListener( "tap", gotoMenu )end
Leaving the Scene
Back up in the scene-accessible code area, we need to add the function which handles 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-- -----------------------------------------------------------------------------------
Scene Cleanup
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.viewlocal phase = event.phaseif ( 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 screencomposer.removeScene( "highscores" )endend
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.
Chapter Concepts
We’ve covered several more important concepts in this chapter:
| Command/Property | Description |
|---|---|
| system.pathForFile() | Generates an absolute path using system-defined directories as the base. |
| 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 JSON-encoded string. |
| 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. |
#
⟨Chapter 5 — Converting the Game to Composer|Chapter 7 — Sounds and Music⟩
© 2020-2026 Solar2D. All Rights Reserved.
Help us help you! If you notice a problem with this page, please report it.
Report an Issue
