For player data vs game data, it's important to note that LOVE itself has only a single location it can read and write to. If you need to load data not in a LUA module from the game directory for editing (for instance, a map editor), you'll need to write your own functions using LUA's file system libraries. This is not much more difficult to do than using LOVE, but you have to be more careful, as you're reading/writing to a location that you may not have write access to.
A typical game is likely going to need at least 2 files: a profile for the user, and their savegame data, assuming you persist state. Note that persisting state itself can be very tricky, depending on the method you use. I use the commonly-available "persistence" module, but also go to great lengths to ensure I'm not using upvalues (you generally CANNOT persist upvalues to a file), as well as trawl the data to ensure I'm not writing data that is identical to a vanilla game state.
It's also important to ensure you are guarding against failures, since the file might not be there, might not be writeable, might have invalid data, et cetera. So what I do is pack a read function with a separate validation function that is ensuring 1) I'm only reading the data I expect to be in there 2) That data conforms to a particular type. Here's an example from a game I have in development:
Code: Select all
-- load saved profile if it exists
love.filesystem.setIdentity( "EndlessDark", false )
local loadprofile = false -- default to doing nothing
local f = "profile.txt"
local d = love.filesystem.getSaveDirectory()
if (d) then
local file
local errstr
local s
local err
local pf = d .. "/" .. f
f_ed_log("SYSTEM: Determined profile path to be " .. d .. "/" .. f, "debug")
local pft = love.filesystem.getInfo( f )
if (pft) then
f_ed_log("SYSTEM: Profile " .. pf .. " exists, attempting to load.", "debug")
local load = f_ed_load_profile(f)
else
f_ed_log("SYSTEM: Profile " .. pf .. " does not exist, attempting to create.", "debug")
local made = f_ed_create_profile(f)
if (made) then
f_ed_log("SYSTEM: Profile " .. pf .. " has been created.", "debug")
else
f_ed_log("SYSTEM: Profile " .. pf .. " could not be created.", "debug")
end
end
else
f_ed_log("SYSTEM: Failed to determine save directory, cannot load profile.txt", "debug")
end
There are many reasons those things COULD fail, so make sure you guard against that. Then after all of that, for every key I am going to read OR write from that file, I individually vet it - there's one function to validate strings, and another to validate bools, and in both cases there's an extra step to ensure the loaded value matches the ranges I require, otherwise it's discarded and a new profile value written.
Code: Select all
function f_ed_validate_profile_key_as_string_to_num(id)
if not(id) then return false end
if not(type(id) == "string") then return false end
-- list of profile keys we take as strings and convert to a number
local t = {
memory_core_stage = true,
num_victory_annie = true,
num_victory_robbie = true,
num_victory_frank = true,
num_attempt_annie = true,
num_attempt_robbie = true,
num_attempt_frank = true,
stress_breaks_annie = true,
stress_breaks_robbie = true,
stress_breaks_frank = true,
went_crazy_annie = true,
went_crazy_robbie = true,
went_crazy_frank = true,
winstreak_total = true,
repairs_total = true,
sabotage_total = true,
npcs_died_annie = true,
npcs_died_robbie = true,
npcs_died_frank = true,
musicvol = true,
}
if (t[id]) then return true else return false end