Making games
Saving data
Two things keep a player’s data between plays, and each has one job:
Savekeeps progress, in slots: where the player is, what they have.Settingskeeps what the player prefers: volume, controls. It is separate from saves, so deleting a save never resets it.
For files of your own, such as a level a player made, see Files. Every call is listed in the Cog API reference.
Save
Save.write(slot, value) keeps a value in a slot, and Save.read(slot) gives
it back. The value can be anything you can hold in a variable: numbers, text,
true/false, a list, an object, a Vec2, Vec3, Rotation or Color. It comes
back as what it was: a Vec3 is a Vec3, and a whole number stays whole.
var coins = 0
var best = 0
fn on_ready() {
var data = Save.read("progress")
if data != null {
coins = data.coins
best = data.best
}
}
fn finished(score) {
if score > best {
best = score
}
Save.write("progress", { coins: coins, best: best })
}
There is no hidden store: what you write to a slot is what is saved, and
nothing is saved until you call Save.write. A slot nobody has written reads as
null, so check for it before you use what you read.
Several save files
A slot is a name, so a game with three save files uses three slots. Save.list()
gives the slots that exist, newest first, for a load screen.
fn save_to(file) {
var data = { level: 3, position: self.position, facing: self.rotation }
Save.write("slot{file}", data)
}
fn load_from(file) {
var data = Save.read("slot{file}")
if data == null {
log.info("Save file {file} is empty")
return
}
self.position = data.position
self.rotation = data.facing
}
fn show_slots() {
for slot in Save.list() {
log.info(slot.name)
}
}
Keep what you need to rebuild the game’s state (a position, a count, the name of a level), not the objects themselves: a value a file cannot hold, such as an object in the world, is an error that names where in the value it is.
Settings
Settings keeps what the player prefers. There is nothing to save: a setting is
written to disk by the end of the frame it changed in, and again when the game
ends.
fn on_ready() {
var volume = Settings.get("volume", 1.0)
log.info("Volume is {volume}")
}
fn volume_changed(value) {
Settings.set("volume", value)
}
get takes the value to use when the player has not chosen one yet. A list or
object you get is a copy: change it, then set it again.
A crash cannot cut a save off
Every write goes to a temporary file first, and only replaces the old file when
it is complete. A crash or a full disk leaves the previous save as it was. The
file a write replaces is also kept beside it as slot1.json.bak; if a file is
ever damaged, the game reads the backup instead and says so in the Console.
Where it is kept
Saves, settings and files are in a folder the computer keeps for each user and each game, not in the game’s own folder, so they survive the game being updated or reinstalled. The folder is named for your game, so two games never share one, and the editor and the built game use the same folder.
By default the name is the project’s name. To pin it, so renaming the project does not lose anyone’s saves, set Save folder in File → Project Settings → General.
Two entries in the File menu help while you work:
- Open Save Folder opens that folder, to look at a save or copy it.
- Clear Saves deletes the saves of the open project, after asking. Settings and the files your game wrote are kept.
Playing in the editor uses the game’s real folder, so a save made while testing is there for the next play, and Clear Saves is how you start over.
Moving from the old Save
Older versions of Save kept a set of values with Save.set, then wrote them
all with Save.write. Those calls still work for now, and the Console warns
where one is used and says what replaces it:
| Before | After |
|---|---|
Save.set("gold", 5) then Save.write("slot1") | Save.write("slot1", { gold: 5 }) |
Save.load("slot1") then Save.get("gold") | var data = Save.read("slot1") then data.gold, after checking data != null |
Save.has("gold") | data.gold != null |
Save.remove("gold") | Write the object again without gold. |
A save made by the old calls reads with Save.read as an object of its values.
Saves are now in a folder named for your game rather than Otter, and saves
made by older builds are not moved there.