在本章中,我们将创建一个场景来显示游戏中的十个最高分。我们还将探讨如何将这些分数保存到持久化存储位置。
在大多数情况下,当您使用 Composer 时,场景保持
这通常有利于保持应用程序的整洁和有序,但有时您需要从本质上不知道其存在的不同场景访问一个场景中的变量/对象。例如,在本章中,我们需要访问玩家的分数
Lua 本身提供了多种在模块之间传递和访问数据的方法,但 Composer 通过以下命令使其更加容易
掌握了这些命令后,让我们更新 `endGame()` 函数
打开您的 `game.lua` 文件。
找到 `endGame()` 函数并将其内容**替换**为以下两个命令
local function endGame() composer.setVariable( "finalScore", score ) composer.gotoScene( "highscores", { time=800, effect="crossFade" } ) end
第一个新命令,`composer.setVariable( "finalScore", score )`
第二个命令只是将应用程序重定向到 `highscores.lua` 场景,而不是菜单场景。
您可以传递任何标准 Lua 变量类型(包括表)作为 `composer.setVariable()` 的值。您甚至可以使用它使一个场景中的局部函数在另一个场景中可访问。这种灵活性使 `composer.setVariable()` 和 `composer.getVariable()` 成为 Composer 工具集中最有用的两个 API。
我们的高分场景将主要具有存储和检索分数、确定十个最高分并显示它们的功能。
首先,复制标准的`scene-template.lua`
像往常一样,我们将从初始化一些变量开始。将以下代码放在
-- ----------------------------------------------------------------------------------- -- 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 )
让我们简要地检查一下这些命令
使用第一个命令,我们为我们的应用程序加载一个新的资源:JSON 库。JSON 是一种处理数据的便捷格式。如果您不熟悉 JSON,请不要担心 - 就此应用程序而言,您只需要学习如何使用
下一个命令,`local scoresTable = {}`
最后一行生成 JSON 文件(`scores.json`)的绝对路径,我们将使用该文件保存十个最高分。不用担心该文件实际上尚不存在 - 此命令将创建该文件并在变量 `filePath` 下启动指向它的链接。
您可能想知道为什么我们要创建一个**文件**来存储高分数据。为什么不直接将它们存储在 Lua 表中?原因是应用程序开发的基础。基本上,如果应用程序退出/关闭,则应用程序本地内存中存在的变量将被销毁!
本质上,任何需要在应用程序退出/关闭后某个时间点访问的数据都应存储在**持久**状态,而存储持久数据的最简单方法是将其保存到设备上的文件中。此外,此文件必须存储在**持久位置**。
为了确保将 `scores.json` 文件放置在持久位置,我们将 `system.DocumentsDirectory` 指定为刚刚输入的命令的第二个参数。这告诉 Solar2D 在应用程序内部的“documents”目录中创建 `scores.json` 文件。虽然我们可以将文件放在其他位置,但我们在这里不会详细介绍它们 - 只需记住,由常量 `system.DocumentsDirectory` 引用的 documents 目录是唯一可以为从应用程序内部创建的文件提供真正持久存储的地方。换句话说,即使玩家退出了应用程序并且一个月后才再次打开它,`scores.json` 文件仍然存在。
现在我们已经初始化了用于存储分数的文件,让我们编写一个函数来检查先前保存的任何分数。当然,目前还没有任何分数,但我们最终将需要此函数。
紧跟您已经添加到
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
剖析此函数,我们完成了以下操作
使用包含数据的文件时,第一步是确认该文件是否存在。此函数中的第一个命令,`local file = io.open( filePath, "r" )`
在后面的条件块中,如果文件存在,则其内容将被转储到局部变量 `contents` 中。获得其内容后,我们使用`io.close( file )`
在最后的条件块中,以防 `scores.json` 文件为空或不存在,我们为 `scoresTable` 分配十个默认值 `0`,以便场景可以使用一些数据。
如果您想让事情更有趣,请以“计算机”获得的十个默认分数开始游戏,并挑战玩家击败它们!例如
scoresTable = { 10000, 7500, 5200, 4700, 3500, 3200, 1200, 1100, 800, 500 }
保存数据与读取数据一样简单。在之前的函数之后
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
此函数如下保存高分数据
首先,我们从 `scoresTable` 中清除任何不需要的分数。因为我们只想保存最高的十个分数,所以超出此范围的任何分数都可以丢弃。使用 `for` 循环,我们从表的总数(`#scoresTable`)到 `11` 向后遍历表,有效地删除了除十个分数之外的所有分数。
接下来,我们打开 `scores.json` 文件。与我们在 `loadScores()` 中的 `io.open()` 调用不同,这里我们将 `“w”` 指定为第二个参数。这告诉 Solar2D 创建(写入)一个新文件或覆盖该文件(如果它已经存在)。它还告诉 Solar2D 以**写入**访问权限打开文件,这很重要,因为在保存分数数据时,我们需要将数据写入文件。
文件成功打开后,我们调用 `file:write()` 将 `scoresTable` 数据写入文件,并通过 `json.encode()` 命令将其转换为 JSON。最后,我们使用`io.close( file )`
在向玩家显示分数之前,我们需要稍微操作一下 `scoresTable` 表。具体来说,我们需要将最新的分数添加到表中,然后将表条目从高到低排序。
此场景的所有工作都可以在 `scene:create()` 中完成,因此让我们将注意力集中在该函数上
-- 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
-- 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
要对表格进行排序,我们使用 Lua 的 `table.sort()` 命令。为此,我们必须为它提供要排序的表格和一个比较函数 (`compare()`) 的引用,该函数确定项目是否需要交换位置。这里我们将 `compare()` 函数直接编写在 `scene:create()` 内部,因为它不需要在其他地方访问。
-- 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
`compare()` 函数本身接受 `table.sort()` 提供的两个值。由于我们正在对数字表格进行排序,因此两个参数 `a` 和 `b` 将是数值。该函数比较这两个值,如下所示
-- 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
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
对于每个分数行,我们将显示**两个**文本对象。左边将是一个从 **1)** 到 **10)** 的排名数字。它的右边将是实际分数。
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
以上大多数代码应该很简单,但我们在**锚点**中引入了一个重要的新概念。默认情况下,Solar2D 将任何显示对象的**中心**放置在给定的 **x** 和 **y** 坐标处。但是,有时您需要沿其边缘对齐一系列对象 — 在这里,如果每个排名数字都
为了实现这一点,请注意我们设置了每个对象的 `anchorX` 属性。此属性通常介于 `0`(左)和 `1`(右)之间,默认为 `0.5`(中心)。由于我们希望每个排名数字都
当然,Solar2D 也支持使用 `anchorY` 属性的垂直锚点。与其水平对应物类似,此属性通常介于 `0`(顶部)和 `1`(底部)之间,默认为 `0.5`(中心)。锚点甚至可以设置在 `0` 到 `1` 范围之外,尽管这种用法不太常见。将 `anchorX` 或 `anchorY` 设置为小于 `0` 或大于 `1` 的值会将锚点概念上放置在对象边缘边界之外的空间中的某个位置,这在某些情况下可能很有用。
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
回到
local function gotoMenu() composer.gotoScene( "menu", { time=800, effect="crossFade" } ) end -- ----------------------------------------------------------------------------------- -- Scene event functions -- -----------------------------------------------------------------------------------
与 `game.lua` 场景类似,让我们在其自身的 `scene:hide()` 函数中移除 `highscores.lua` 场景。当玩家返回菜单场景时,这将从内存中移除该场景。
在 `scene:hide()` 函数中,添加以下突出显示的行
-- 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
我们的高分场景到此结束!保存您修改后的 `highscores.lua` 和 `game.lua` 文件,然后重新启动模拟器。现在您应该能够玩游戏并看到您的分数保存在高分场景中并进行了排序。
此场景相对简单,但如果您的代码未按预期工作,请将其与本章源文件中捆绑的 `highscores.lua` 文件进行比较。
我们在本章中介绍了几个更重要的概念
命令/属性 | 描述 |
---|---|
system.pathForFile() | 使用系统定义的目录作为基准生成绝对路径。 |
io.open() | 打开文件以进行读取或写入。 |
io.close() | 关闭打开的文件句柄。 |
file:read() | 根据给定的指定读取内容的格式读取文件。 |
file:write() | 将其每个参数的值写入文件。 |
json.decode() | 解码 JSON 编码的数据结构并返回一个包含数据的 Lua 表格。 |
json.encode() | 将 Lua 表格转换为 |
composer.setVariable() | 设置在一个场景中声明的变量,使其在整个 Composer 应用程序中都可访问。 |
composer.getVariable() | 允许您检索通过 composer.setVariable() 设置的任何变量的值。 |
table.sort() | 按给定顺序对表格元素进行排序。 |
object.anchorX | 允许您控制显示对象沿 **x** 方向对齐的属性。 |
object.anchorY | 允许您控制显示对象沿 **y** 方向对齐的属性。 |