[注意事項] Corona 已改名為 Solar2D。
在先前的範例中,每次專案重開時,程式又會回到初始狀態,這時因為我們沒有永久性地 (persistently) 儲存程式的狀態 (states)。在實務上,我們會儲存程式的狀態,像是使用者的偏好 (preferences)、遊戲的最高分數等;下次程式使用者重開程式時,又會回到先前的狀態。
在電腦程式中,這類議題和持久性 (persistence) 相關。在實務上,我們會透過硬碟 (hard disc) 這類非揮發性 (non-volatile) 儲存裝置將程式的狀態儲存起來。近年來流行的雲端儲存 (cloud storage) 其實仍是存在某個遠端的 (remote) 伺服器上的硬碟,故這個概念仍可通用。
在本文中,我們探討 Solar2D 在本地端的 (local) 持久性方案。一般來說,有兩種方式:
- 檔案 (flat files),像是 JSON 檔
- 資料庫 (databases),常見的是 SQLite 這類內嵌性資料庫
這兩者的差別在於需不需要資料庫索引的特性,JSON 像是一個簡單的鍵-值資料檔,而 SQLite 則是一個具體而微的資料庫。一般來說,使用者偏好設定會存在 JSON 檔案裡,而 TODO 清單這類資料就會存在 SQLite 資料庫中。
由於存取系統偏好設定是一個常見的功能,Corona 團隊將這個功能進行進一步地封裝,程式人只要用 Corona 提供的 API 來存取偏好,不用煩惱內部使用什麼方式來儲存 (可看這裡)。本範例即是用這項特性來儲存程式狀態;我們將完整的程式碼放在這裡,本文僅節錄部分內容。
本範例程式的示意圖如下:
{{< figure src="/img/corona-sdk/preferences.png" alt="Solar2D 的 segmented control 和 slider" class="phone" >}}
基本上,這個範例和先前的範例在功能上大抵雷同,只是加上持久性狀態的功能。
一開始,我們設置一些程式會用到的基本數值:
-- Set valid colors.
local color = {}
color.red = {255, 0, 0}
color.orange = {255, 165, 0}
color.yellow = {255, 255, 0}
color.green = {0, 255, 0}
color.blue = {0, 0, 255}
color.purple = {128, 0, 128}
-- Set valid sizes for different shapes.
local sizeCircle = {}
sizeCircle.small = 30
sizeCircle.medium = 40
sizeCircle.large = 50
local sizeRect = {}
sizeRect.small = {60, 36}
sizeRect.medium = {80, 48}
sizeRect.large = {100, 60}
local sizeTriangle = {}
sizeTriangle.small = {-20, -25, -20, 25, 30, 0}
sizeTriangle.medium = {-30, -35, -30, 35, 40, 0}
sizeTriangle.large = {-40, -45, -40, 45, 50, 0}
local size = {}
size.circle = sizeCircle
size.rectangle = sizeRect
size.triangle = sizeTriangle
-- Set location for our item.
local itemLocX = 0.5
local itemLocY = 0.2
-- Set the list of valid options.
local sizes = { "small", "medium", "large" }
local colors = { "red", "orange", "yellow", "green", "blue", "purple" }
local shapes = { "circle", "rectangle", "triangle" }
在這個範例中,我們將生成多邊形的函式獨立出來,因為我們在程式啟動及事件處理器中都會用到這個函式。程式碼參考如下:
local function getShape(options)
local item
if options.shape == "circle" then
item = display.newCircle(options.x, options.y, size[options.shape][options.size])
elseif options.shape == "rectangle" then
item = display.newRoundedRect(options.x, options.y,
size[options.shape][options.size][1], size[options.shape][options.size][2], 10)
elseif options.shape == "triangle" then
item = display.newPolygon(options.x, options.y,
{size[options.shape][options.size][1], size[options.shape][options.size][2],
size[options.shape][options.size][3], size[options.shape][options.size][4],
size[options.shape][options.size][5], size[options.shape][options.size][6]})
end
item:setFillColor(color[options.color][1] / 255, color[options.color][2] / 255, color[options.color][3] / 255)
return item
end
這個函式是我們要從 PickerWheel 中取得索引值用的:
local function getIndex(list, item)
for i = 1, #list do
if item == list[i] then
return i
end
end
end
在程式一開始時,我們讀取偏好值:
-- Get system prefs.
local mySize = system.getPreference("app", "mySize")
if mySize == nil then
mySize = "medium"
end
local myColor = system.getPreference("app", "myColor")
if myColor == nil then
myColor = "orange"
end
local myShape = system.getPreference("app", "myShape")
print("myShape: " .. myShape)
if myShape == nil then
myShape = "circle"
end
由於程式第一次啟動時,偏好值為空值,故我們在程式中要檢查這個情形,並塞入起始值。
在程式啟動時建立起始的 item
多邊形物件:
-- Init `item`.
local item = getShape({
x = display.contentWidth * itemLocX,
y = display.contentHeight * itemLocY,
size = mySize,
color = myColor,
shape = myShape,
})
建立 PickerWheel 物件:
-- Set initial columnData by system prefs.
local columnData = {
{
align = "left",
width = 100,
startIndex = getIndex(sizes, mySize),
labels = sizes,
},
{
align = "left",
width = 110,
startIndex = getIndex(colors, myColor),
labels = colors,
},
{
align = "left",
startIndex = getIndex(shapes, myShape),
labels = shapes,
}
}
-- Create a PickerWheel object.
這裡和先前的範例不同,我們不把起始值寫死,而是藉由讀取系統偏好來動態地調整 PickerWheel 的起始位置。
同樣地,我們用 timer
物件來監看 PickerWheel 的值,並動態地改變 item
物件:
-- The event listener for `pickerWheel`.
local onValueSelected = function ()
local values = pickerWheel:getValues()
local _size = values[1].value
local _color = values[2].value
local _shape = values[3].value
item:removeSelf()
item = getShape({
x = display.contentWidth * itemLocX,
y = display.contentHeight * itemLocY,
size = _size,
color = _color,
shape = _shape,
})
system.setPreferences("app", {
mySize = _size,
myColor = _color,
myShape = _shape,
})
end
-- Update `item` by current selection.
timer.performWithDelay(100, onValueSelected, -1)
在這段程式碼中,除了修改 item
外,我們也將程式狀態存在偏好中,下次啟動程式時就會從同樣的狀態回復。