Making games

Files

File reads and writes files from a script, for what Save and Settings are not for: a level a player made, a replay, a text log, or data your game ships with, such as a list of levels. Every call is listed in the Cog API reference.

Two places, and nothing else

A file is named by an address that starts with one of these:

AddressWhat it is
user://...This game’s folder on the player’s computer. Read and write.
assets://...The game’s own files, the ones in your project’s assets folder. Read only.

That is all a script can reach. A path like C:/notes.txt is an error that says what is allowed, and .. cannot climb out of either folder. People will run each other’s games, and a game that can only touch its own folder cannot read or change another game’s files, or anything else on the computer.

user:// is the same folder Save and Settings use, named for your game (see Where it is kept). Folders are made as you write, so there is no call to make one.

Writing and reading

fn write_notes(text) {
    File.write_all_text("user://notes/today.txt", text)
}

fn read_notes() {
    var text = File.read_all_text("user://notes/today.txt")
    if text == null {
        return "No notes yet"
    }
    return text
}

Reading a file that is not there gives null, not an error: a missing file is an ordinary answer. A write that fails (the disk is full, or the folder is not allowed) stops the script with an error that names the file and the reason. Every write is the safe kind described under A crash cannot cut a save off.

To carry on after a failed write, catch it:

fn keep_notes(text) {
    try {
        File.write_all_text("user://notes/today.txt", text)
    } catch (e) {
        log.error(e)
    }
}

JSON

File.read_json and File.write_json read and write a value as JSON, which other programs can read too:

fn load_level(name) {
    var level = File.read_json("assets://levels/{name}.json")
    if level == null {
        log.warn("No level called {name}")
    }
    return level
}

fn save_level(name, level) {
    File.write_json("user://levels/{name}.json", level)
}

JSON has no Vec3, so one is written as an object of its numbers, and reads back as an object, not a Vec3 (the reference lists how each type is written). For data your own game reads back as it was, use Save.

Listing a folder

File.list gives the names in a folder, sorted, with a / after each folder’s name:

fn level_names() {
    var names = []
    for name in File.list("user://levels") {
        if name.ends_with(".json") {
            names.push(name)
        }
    }
    return names
}

A folder that is not there is an empty list. On assets:// the list comes from your project’s asset list, so it gives the same names in the editor and in the built game.