Lưu trò chơi
Lưu và tải

Lưu
Vì lý do nào đó mà tôi không muốn bắt tay vào phần này ngay. Thật ra tôi còn sợ nó sẽ khó. Nhưng vì đã làm vài cảnh thử nghiệm, nên có thể lưu trò chơi sẽ tiện hơn. Và dĩ nhiên là tải lại nữa.
Để làm việc đó, tôi đã tạo trong engine3 một lớp tên là serialization.lua, là một lớp bao quanh binser.
Nhờ vậy có thể lưu dữ liệu đối tượng một cách chính xác.
Sau đó trong trò chơi, tôi tạo lớp state.lua trông đại khái như thế này.
function State:saveEntities()
for _, entity in pairs(entityManager.entities) do
if entity.saveEntity then
entity:saveEntity()
end
end
end
---@param file string Where to save the data
function State:save(file, force)
-- Check if it is possible to save right now
if action:isProcessWorking() and force ~= true then
print("saving is not possible now, a action is still working")
return false
end
local data = {}
-- Create a save state for all entities that are needed
self:saveEntities()
-- We don't need all session data.
-- @warning keep this up to date if you want to add something
local sessionData = session:getData()
local validSessionKeyNamePatterns = {
"^actor-.*",
"^item-.*",
"^animation-.*",
"^scene-.*",
"action",
"^entity_delete",
"^entity_properties",
"^currentSceneUkey"
}
for sessionKeyName, sessionValue in pairs(sessionData) do
-- I don't know how to create multi patterns ... [x|y] don't work
local check = false
for _, pattern in pairs(validSessionKeyNamePatterns) do
if string.match(sessionKeyName, pattern) ~= nil then
if self.config.log.state then
print("[SESSION.SET." .. sessionKeyName .. "]")
end
data[sessionKeyName] = sessionValue
check = true
break
end
end
if check == false and self.config.log.state then
print("[SESSION.INFO." .. sessionKeyName .. "] don't match any save pattern")
end
end
local success, message = love.filesystem.write(file, Serialization.serialize(data))
if success == false then
error("Cannot save data, " .. message)
end
end
---@param file string What file data to load
function State:load(file)
local data = love.filesystem.read(file)
if data then
local results, len = Serialization.deserialize(data)
-- at the moment we only use one object in each file
if len == 1 then
-- set the session data
session:setData(results[1])
end
end
end
Cách dùng
Giờ trong mỗi lớp entity có thể tạo (tùy chọn) hai hàm được dùng khi lưu và tải trò chơi. Bên dưới là ví dụ ngắn gọn từ entity của nhân vật. Ưu điểm của cách này là sau này bạn có thể phát triển theo mọi hướng, và các tệp cũng rất nhỏ.
-- special function that will execute when saving the game
function Actor:saveEntity()
-- maybe a extend class did create some data
local data = session:get("actor-" .. self.ukey) or {}
data.sceneUkey = self.sceneUkey
data.lastMove = self.lastMove
data.movableByUserInput = self.movableByUserInput
if self.activeFrameset then
data.startPositionX = self.activeFrameset:getX()
data.startPositionY = self.activeFrameset:getY()
end
-- maybe the actor did change the frameset (using a hat)
data.frameset = {}
for index, frameset in pairs(self.frameset) do
data.frameset[index] = frameset.name
end
session:set("actor-" .. self.ukey, data)
end
function Actor:loadEntity()
local restoreData = session:get("actor-" .. self.ukey)
if restoreData == nil then
return
end
if restoreData.sceneUkey ~= nil then
self:setSceneUkey(restoreData.sceneUkey)
end
if restoreData.movableByUserInput ~= nil then
self:setMovableByUserInput(restoreData.movableByUserInput)
end
-- maybe the actor did change the frameset (using a hat)
if restoreData.frameset then
for index, framesetName in pairs(restoreData.frameset) do
self:setFrameset(index, framesetName)
end
end
if restoreData.startPositionX and restoreData.startPositionY then
self.movable.vPosition.x = restoreData.startPositionX
self.movable.vPosition.y = restoreData.startPositionY
self:setXY(self.movable.vPosition.x, self.movable.vPosition.y)
end
if restoreData.lastMove then
-- what will be the new activeFrameset (index)
self:setActiveFrameset(restoreData.lastMove)
end
-- when load scene is used, we don't set the player position
-- @warning we do this after setXY, because that function will show the player
local currentSceneUkey = session:get("currentSceneUkey")
if currentSceneUkey and self.sceneUkey ~= currentSceneUkey then
self:hide()
end
end
Lời kết
Như bạn thấy, bạn chỉ cần đảm bảo chỉ lưu những dữ liệu thật sự cần. Mọi dữ liệu khác đều thừa và khiến việc gỡ lỗi gần như không thể. Đừng chờ quá lâu mới làm một hệ thống như vậy, vì đằng nào bạn cũng phải làm.
Đoạn mã trên hiện được kích hoạt bằng một nút, kèm ảnh chụp màn hình trò chơi, và nhân vật được chọn sẽ nói rằng đã lưu thành công.
self.buttonSave.callbackAfterMousePressedEvent["btnSave"] = function(button)
local currentTime = os.time()
local saveGameName = "savegame" .. currentTime .. ".data"
local saveGameScreenshot = "screenshot" .. currentTime .. ".png"
-- create a screenshot without the state menu (write it to the save directory (love default))
love.graphics.captureScreenshot(saveGameScreenshot)
-- save the game
State():save(saveGameName)
-- say that the save the game was correct
local activeActor = entityManager:find({ movableByUserInput = true }, "Player")
if #activeActor == 1 then
local actionSay = ActionSay()
actionSay:execute(
{
action = action.SAY,
initiator = activeActor[1]
},
{
text = i18n:s("Save game done!")
},
nil,
{},
{},
nil
)
end
self.buttonLoad:setIsVisible(true)
return { preventDefault = true }
end