What is your advice for saving game state?

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
User avatar
togFox
Party member
Posts: 828
Joined: Sat Jan 30, 2021 9:46 am
Location: Brisbane, Oztralia

What is your advice for saving game state?

Post by togFox »

I have an isometric tile based dungeon crawling RPG and need to provide a "save/load" game function. I'm thinking about the best way to do that.

I suppose I can save as a single text file or mulitple text files. I can use a DB database (done that before). The game is in prototyping stage so the it will change. My chosen technique will need to be scalable and flexible without being overkill.

I'm thinking a DB falls into the overkill category. I like the neatness of different text files. My 'data' will be like any other rpg:

- map (tiles - 2D array
- character data (health, position in the world) (array)
- inventory (2d array)
- object state within the world (2d array)

I'm thinking of just dumping each array/list/table to a file when saving and then just read them back in during load. The map file will likely be large but the others would be small.

Is there something else I should be considering?
Last project:
https://togfox.itch.io/hwarang
A card game that brings sword fighting to life.
Current project:
Turn-based PBEM horse stable (racing) management sim: https://togfox.itch.io/horse-stable-manager
https://discord.gg/HeHgwE5nsZ
User avatar
darkfrei
Party member
Posts: 1197
Joined: Sat Feb 08, 2020 11:09 pm

Re: What is your advice for saving game state?

Post by darkfrei »

I save some table data as table:

savefile.lua:

Code: Select all

return {
	player = {name = "Hero", health = 10, position = {x=4, y=8}},
	enemies = {
		{health = 8, position = {x=10, y=18}},
		{health = 12, position = {x=12, y=20}, hidden = true},
	}
}
And the

Code: Select all

save = require ('savefile')
to load it.
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
Gunroar:Cannon()
Party member
Posts: 1141
Joined: Thu Dec 10, 2020 1:57 am

Re: What is your advice for saving game state?

Post by Gunroar:Cannon() »

Maybe you could serialize all the classes that hold data into different filrs(kind if like your last suggestion), leaving out the class functions, then to load it you would have a class/function(for each?) like MapLoader, that makes a new map class, for example, then takes all the variables for that deserialized data and replace them. Or you could put in all the functions that the data doesn't have, that's how I do it.
But for a case where classes are inside classes (like Tile classes inside Map) I give each class instance a _class variable that holds the name of the class they belong to, and I store all the classes in tables.
The risk I took was calculated,
but man, am I bad at math.

-How to be saved and born again :huh:
User avatar
Xii
Party member
Posts: 137
Joined: Thu Aug 13, 2020 9:09 pm
Contact:

Re: What is your advice for saving game state?

Post by Xii »

I use moonblob. Makes it easy to save/load any table from disk.

Code: Select all

local writer, reader = require("BlobWriter"), require("BlobReader")

local filesystem_identity = "68841bcfb1b0c875"
state = {}

function save_state()
	love.filesystem.setIdentity(filesystem_identity)
	local blob = writer()
	blob:write(state)
	assert(love.filesystem.write("state", blob:tostring()))
end

function maybe_load_state()
	love.filesystem.setIdentity(filesystem_identity)
	local fileinfo = love.filesystem.getInfo("state")
	if not fileinfo then
		return false
	end
	
	local data = assert(love.filesystem.read("state"))
	local blob = reader(data)
	state = blob:read()
	return true
end
DrNefario
Prole
Posts: 6
Joined: Thu Apr 08, 2021 7:44 pm

Re: What is your advice for saving game state?

Post by DrNefario »

I put all my saveable data into one table, and used a library called table_save to save and load it. It works fine, but could easily be swapped out for some other system if I wanted more security or whatever.

My inventory and level state data are in sub-tables inside the main savedata table.

The other thing I do, which I thought helped during development, is create a fresh savegame and then copy the loaded table over the top of it. That way if I add a new field (at some point there are going to be player stats, like STR, for instance), it will be added to the loaded file without me having to start my game from scratch. I also (belatedly) added a version number so I can update or invalidate out-of-date saves.

I also have a slot-based system so i don't need to worry about filenames.

Code: Select all

require("lib/table_save")

-- globals
selectedSlot = 1
-- save data
player = {}

function loadGame(slot)
	slot = slot or selectedSlot -- allow quickload from current slot
	
	if (slotIsFull(slot)) then
		-- do some magic stuff
		local fileName = "slot" .. slot .. ".sav"
		local loaded = table.load(fileName)
		-- clone the table into player (this means we keep whatever is in player now
		-- that doesn't exist in loaded, so it will have new fields we added since the
		-- save, as long as they aren't subfields)
		player = newSaveData()
		for k, v in pairs(loaded) do
			player[k] = v
		end
		
		-- make sure flags and automap are created
		if (player.levelflags[player.area] == nil) then player.levelflags[player.area] = {} end
		if (player.levelmaps[player.area] == nil) then player.levelmaps[player.area] = {} end
		
		selectedSlot = slot

		level.loadLevel(player.area)
		return true
	end
	return false
end

function saveGame(slot)
	slot = slot or selectedSlot -- allow us to call with no slot (bit hacky)
	
	local fileName = "slot" .. slot .. ".sav"
	table.save(player, fileName)
end

function newSaveData()
	local saveData = {}
	
	saveData.version = 1
	saveData.x = 1
	saveData.y = 1
	saveData.facing = 2
	saveData.area = 1
	saveData.hp = 100
	saveData.maxhp = saveData.hp
	saveData.xp = 0
	saveData.level = 1
	
	-- event tracking
	saveData.globalflags = {}
	saveData.levelflags = {}
	-- automaps
	saveData.levelmaps = {}
	-- inventory
	saveData.inventory = {
		{ "fist", -1 },
		{ "knife", 10 },
		{ "heal", 5 },
	}
	
	-- make sure flags and automap are created
	if (saveData.levelflags[saveData.area] == nil) then saveData.levelflags[saveData.area] = {} end
	if (saveData.levelmaps[saveData.area] == nil) then saveData.levelmaps[saveData.area] = {} end
	
	return saveData
end
Edit: Just noticed my comment "Do some magic stuff". That was a placeholder from before I implemented it :)
User avatar
togFox
Party member
Posts: 828
Joined: Sat Jan 30, 2021 9:46 am
Location: Brisbane, Oztralia

Re: What is your advice for saving game state?

Post by togFox »

Thanks for all the replies. I eventually found this on github:

https://github.com/BroccoliRaab/SaveData

It is very simple and pretty much does what everyone here is saying but it is neatly encapsulated so you just need savedata.save and savedata.load. I thought that was worth posting here.
Last project:
https://togfox.itch.io/hwarang
A card game that brings sword fighting to life.
Current project:
Turn-based PBEM horse stable (racing) management sim: https://togfox.itch.io/horse-stable-manager
https://discord.gg/HeHgwE5nsZ
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: What is your advice for saving game state?

Post by pgimeno »

Maybe not hundreds, but there are probably dozens of serialization libraries. For T2R I decided to use JSON; today I would have chosen Smallfolk, but if I wanted the files to be compact and not necessarily human-readable, bitser. I don't trust serialization libraries that generate Lua code.
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Bing [Bot], Google [Bot] and 2 guests