Hour 12. File System
    What You’ll Learn in This Hour:
    Special Godot paths
    Creating and saving configuration files
    Saving general files, such as save games
    Using encrypted files
    Managing files and directories with scripting
    Godot has capabilities to deal with the file system of the player’s device, which is important when creating and managing configuration and
    save files. This hour will focus on the scripting API that helps you handle this in a portable, multi-platform way.
    Special Paths
    Apart from the standard file system paths available from the user’s operating system, Godot provides two special portable file paths: The
    Resources directory and the User directory.
    Resource Path
    The root folder of your project is the Resource Root. From there, all the resources are loaded to the game. It is identified with the res://
    prefix, which acts as a protocol in standard paths (such as file:// or http:// ), and can be used like a regular path in the functions.
    The Resource path is where your project lives. It is great for reading project resources in a portable way, since it’s the same for every platform.
    However, you should not try to write on it. You can write on the Resource path inside the editor, because it’s just your project folder, but it will
    likely be a read-only path when the game is exported. This is especially true for mobile, as the file system access is restricted. Even on
    desktop platforms, the game is exported in a single read-only package (a Godot-specific file, as we’ll see in the hour about exporting).
    TIP
    Case Sensitiveness
    The file system on Windows is not case-sensitive; therefore, it does no
    t
    matter if you change a capital letter to a lowercase correspondent. This
    is not true for other operating systems, however, so it’s important to keep the case of the file names in your code as they are in the system.
    A general rule of thumb is to name all files in lowercase.
    User Path
    In a similar manner, there’s a special path that can be accessed by the user:// prefix. This path is meant to store user-specific information,
    such as configurations and game saves. The actual location of this directory varies from one operating system to another, but it uses the
    common user-data location. It is a reliable place to write files in-game so they don’t get lost.
    NOTE
    User Path Location
    The user path has different locations for each platform to follow the standards of the user’s operating system. If you want to find the files, here’s
    a list of locations per OS:
    Windows: %APPDATA%/.godot/appuserdata/project_name
    Linux and MacOS: ~/.godot/app_userdata/project_name
    HTML5: The file system is mocked using the LocalStorage API.
    Android, iOS, and UWP: These platforms have restricted access to the application data.
    Note that you can remove the .godot/app_userdata from the path and just use project_name (which is a path-safe conversion of your
    project’s name). To do that, enable Use Shared User Dir on Project Settings under the Application/Config section.
    Game Configuration
    One very common thing in games is the options menu. The user can select the difficulty, video and audio settings, control configuration, etc.
    The player also expects the game to remember these options between sessions. To deal with that, you need to save a file in the player’s
    machine with the selected options so that the game can save and load the next time.
    Godot provides a special class called ConfigFile. This class has methods to add, edit, and retrieve options in a INI-like formatted file. It can
    deal with sections in the file to better organize the content, and is responsible for loading and saving the file in the disk. An example usage of
    the ConfigFile class is shown in Listing 12.1.
    LISTING12.1 Saving Configurations
    111111111
    22222222Click here to view code image
    extends Node
    func _ready():
    save_config()
    func save_config():
    var path = “user://config.ini” # The path to save the file
    var config = ConfigFile.new() # Create a new ConfigFile object
    config.set_value(“options”, “difficulty”, “hard”) # Save the game difficulty
    config.set_value(“audio”, “music_volume”, 42) # Save the music volume
    var err = config.save(path) # Save the configuration file to the disk
    if err != OK: # If there’s an error
    print(“Some error occurred”)
    In this sample, we add two values to the ConfigFile: the game difficulty and the music volume. The set_value() function receives three
    arguments: the section, the property name, and the property value. After setting the values, we need to call save() with the path to actually
    write the file to the disk. This function returns an Error value that can be checked against any of the ERR
    prefixed constants from the
    @Global Scope.
    Loading Configuration Files
    After you save the file to disk, only half the work is done. When the game starts again, you need to load the file from the disk and change the
    options to match the file. ConfigFile will also help you with that (see Listing 12.2).
    LISTING12.2 Loading Configurations
    Click here to view code image
    extends Node
    func ready():
    print(load_config()) # Should do something meaningful, but let’s print for test
    purposes
    func load_config():
    var path = “user://config.ini” # The path to load the file
    var config = ConfigFile.new() # Create a new ConfigFile object
    var default_options = { # Create a dictionary of defau
    l
    t options
    “difficulty”: “easy”,
    “music_volume”: 80
    }
    var err = config.load(path) # Load the file from the disk
    if err != OK: # If there’s an error return the default options
    return default_options
    var options = {} # Create a dictionary to store the loaded options
    # Get the values from the file or the predefined defaults if missing
    options.difficulty = config.get_value(“options”, “difficulty”, default_options.
    difficulty)
    options.music_volume = config.get_value(“audio”, “music_volume”, default
    _options.music_volume)
    return options # Return the loaded options
    The load_config() function starts by creating a dictionary of default options. Then it tries to load the file from the disk. If it fails (which may
    be the case in the first run of the game), it returns the default options. This ensures the output is always in a valid state. After that, it creates a
    new dictionary to store the loaded options.
    There are two calls to the get_value() function from the ConfigFile class. This function receives three arguments: the section, the name
    of the property, and the default value (the default is an option argument, and will be null if not provided). In the code, try to load the settings
    defined earlier. If for some reason they are not defined (maybe the player deleted them manually), load from the default options.
    After building the options dictionary, return it for the calling function so it can do the appropriate task such as changing the music volume or, in
    this case, printing the options to the output.
    TIP
    Avoiding Redundancies
    The ConfigFile class has methods for retrieving the sections and the properties in a section. You can use them to make a loop that fills the
    options dictionary based on the file instead of listing the properties manually once again, which might be troublesome if you forget to add or
    remove one option, or if you change the section of another.
    If you centralize the places where options are stored and retrieved, you can save work in the future and avoid the nasty hard-to-find bugs
    caused by the redundancy.
    111111111
    22222222Dealing with Files
    While the ConfigFile class is great for storing configuration, it is very limited in what it can do. It is not recommended to deal with more
    complex save games. Godot provides a general class to read and write to files in a general way. The File class can create files with any kind
    of content, including binary formats. It has options to store and retrieve any type of data, and is able to compress and encrypt files.
    Creating and Writing to Files
    The first thing you need to do to write to a file is create a new File object. Then you need to open a file from the disk. The class has functions
    to write Variant type to files, which may help you deal with the content. Listing 12.3 gives an example of how to write player data to a file.
    LISTING12.3 Writing Data to File
    Click here to view code image
    extends Node
    # Some variables to store
    var player_name = “Link”
    var player_score = 550
    func _ready():
    create_file()
    func create_file():
    var path = “user://save.dat”
    # Create a new File object
    var file = File.new()
    # Open the file for writing
    var err = file.open(path, File.WRITE)
    # Simple error checking
    if err != OK:
    print(“An error occurred”)
    return
    # Store the player data
    file.store_var(player_name)
    file.store_var(player_score)
    # Release the file handle
    file.close()
    The code opens a file for writing. Note that if the file doesn’t exist, it will be created; otherwise, it will be truncated (cleared). The
    store_var() function stores a Variant type with metadata (such as type and size) so that it can be retrieved later.
    TIP
    Open Modes
    The second argument of the File.open() method is the mode to open the files. There are four modes available, all of which are constants
    in the File class:
    READ: Open for reading. Will return an error if the file exists.
    WRITE: Open for writing. Create the file if it doesn’t exist and truncate if it exists.
    READ_WRITE: Open for reading and writing. Return an error if it doesn’t exist; don’t truncate if it exists.
    WRITE_READ: Open for reading and writing. Create the file if it doesn’t exist and truncate if it exists.
    These may be a bit confusing to remember, but you can always check the built-in help if needed.
    Reading from Files
    Writing to a file can only be useful if you can read the data later. The File class has similar methods for reading as for writing (e.g., if you use
    store_double(), you can later retrieve with get_double()). Let’s try to get back the player data from the file (see Listing 12.4).
    LISTING12.4 Retrieving Data fromthe File
    Click here to view code image
    extends Node
    func _ready():
    read_file()
    func read_file():
    var path = “user://save.dat”
    # Create a new File object
    var file = File.new()
    # Open the file for reading
    111111111
    22222222var err = file.open(path, File.READ)
    # Simple error checking
    if err != OK:
    print(“An error occurred”)
    return
    var read = {}
    read.player_name = file.get_var()
    read.player_score = file.get_var()
    file.close()
    return read
    This code is like that in Listing 12.3: it creates a File object and opens the file for reading. Note that an error will be returned if the file doesn’t
    exist. After it’s open, retrieve the variables and put them into a dictionary so the values can be returned.
    NOTE
    Data Order Matters
    If you are writing binary data to a file, be sure to use the same order when reading. If the order is changed during the development process, the
    old files may be wrongly read or simply unreadable. This may also be needed to update the game (add data or change a data type). Be sure to
    have a consistent format and versioning in your binary files to avoid losing data, especially when dealing with player data. No one likes to lose
    their game progress after an update.
    TIP
    Text Formats
    Instead of simply storing variables directly, you can save the data as text. Godot has support for the popular JSON format (JavaScript Object
    Notation), which can be used easily by other applications. It may not be suitable for save games if you don’t want the player to tamper with
    them, but it can be useful for configuration storage.
    You can use the to_json() and from_json() functions to convert from a dictionary to text and vice-versa. Then simply use
    File.store_string() and File.get_as_text() methods to store and retrieve the JSON from the file.
    Compressing and Encrypting Files
    The File class has special variations of the open() method to use c
    o
    mpression or encryption in your files. When you use them, the file will
    be automatically dealt with following the way it was opened. Note that encryption and compression needs a flush (the process that stores data
    to the disk). You need to call close() on the file object to flush the data; otherwise, you may lose it.
    The open_encrypted() and open_encrypted_with_pass()functions both open files with encrypted data. They can be used to open
    files for writing and reading the same way as the regular open() method. The difference between open_encrypted() and
    open_encrypted_with_pass() is that the first receives a binary key (as a PoolByteArray), and the other receives a string password
    that will be used to decrypt the file when opened in read mode.
    The same applies to the open_compressed() method. This method receives an optional argument to determine the compression mode.
    You can use the COMPRESSION
    prefixed constants from the file class. The default method is FastLZ, which has a nice compromise between
    compression ratio and speed.
    TIP
    Compressing and Encrypting the Same File
    Godot does not offer a direct API to compress and encrypt a file at the same time, but it is possible to work around that. If your data is in an
    array or in a string, you can convert it to a PoolByteArray. This kind of array has compression functions, so you can compress it and store
    the result as a buffer in the encrypted file, then follow the reverse steps to get back the data.
    This may be a bit convoluted, but if you really need to do it, it’s possible to abstract it away with a few functions.
    NOTE
    Encryption and Security
    It’s important to note that even if you encrypt save games, the key to decrypt must be somewhere in your application, and dedicated hackers
    could get their hands on it. Also, if the application uses the same key for all users to store save games, it won’t stop players from exchanging
    save files.
    The rule of thumb is to never trust the client. If your game relies on an online multiplayer experience that could be ruined by cheaters, be sure to
    enforce the rules on the server, where you have control.
    Dealing with Directories
    Besides the File class, Godot also contains a Directory class. This one is responsible for managing folders in the file system. It can list,
    delete, rename, and move files.
    Listing the Files in a Folder
    111111111
    22222222Listing the Files in a Folder
    A simple way to start is to list all the files in a certain directory. Godot has a few functions to make this behavior possible. This is a bit different
    from how it’s usually handled, so let’s look at an example, shown in Listing 12.5.
    LISTING12.5 Retrieving Data fromthe File
    Click here to view code image
    extends Node
    func _ready():
    # Create a new directory object
    var dir = Directory.new()
    var err = dir.open(“res://“)
    if err != OK:
    print(“An error occurred”)
    return
    # Start listing the directories
    dir.list_dir_begin()
    # Retrieve the first file or dir
    var name = dir.get_next()
    # Start a loop
    while name != “”:
    # Test if it’s a dir and print as appropriate
    if dir.current_is_dir():
    print(“dir : “, name)
    else:
    print(“file: “, name)
    name = dir.get_next()
    # End the listing
    dir.list_dir_end()
    The first step, as always, is to create a new Directory object. Try to open the resources directory, and if there’s an error, stop the function.
    Then call a method to start the listing of files and directories. Start a loop, and at each iteration, call get_next() to advance in the list.
    There’s a test for the current entry: if it’s a directory, prefix the print with “dir”; otherwise, use “file.” When the loop ends, call a function to stop
    the listing and close the stream.
    NOTE
    Special Directory Entries
    If you ran the code above, you may note that the first two entries are a dot (.) and a couple of dots (..). These are standard notations in file
    system paths. The single dot means the current directory, and the double dot means the directory above the current one. You can use them for
    relative navigation.
    If you don’t need them or want to skip them when listing the files, you can pass true as the first argument of the list_dir_begin() call.
    Managing Files and Directories
    There are a handful of methods in the Directory class that can act as full-fledged file explorers. In fact, these functions are in use by the
    engine internals in the file dialogs. Let’s walk through some possibilities with this powerful class.
    Creating Directories
    Two functions can create folders: make_dir() and make_dir_recursive(). The difference between the two is that the latter creates all
    the intermediate directories if they do not exist, while the former just returns an error if that happens.
    Deleting Files and Directories
    The remove() method of the Directory class acts as a delete function for both files and directories. Note that to remove folders, they must
    be empty. There’s no function to delete a directory recursively, but you can implement your own inGDScript.
    Moving, Renaming, and Copying Files
    The rename() method can be used for renaming and moving files. If the destination path is different from the source, the file will be moved
    there. This behavior is akin to the “move” command of Unix-like platforms.
    To copy files, you can use the copy() method. Its arguments are the same as the rename() function: the first is the source, and the second
    is the target. Both methods overwrite the destination if it already exists.
    Checking for Existence
    The Directory class has two methods for checking existence in the file system: dir_exists() and file_exists(). Both work with
    relative and absolute paths. Note that if you call dir_exists() with the path to a file, the function will return false, and vice-versa.
    TIP
    FileDialog
    111111111
    22222222If you need a graphical way for the user to select files and directories, you should use the FileDialog control. It has all the appropriate
    functions and interface to navigate within the file system and let the user select a file. This control is very similar to the file dialogs encountered
    in the Godot engine editor.
    Summary
    This hour covered the aspects of the file system and how Godot deals with it. You saw the special portable paths of the engine that can be
    used in any platform. You learned how to create configuration files and store them to the disk, followed by general files and how to deal with
    them. In the end, you saw how the Directory class can navigate and interact with the files in the user’s operating system.
    Q&A
    Q. Can Godot access any path in the file system?
    A. Yes. However, it follows the operating system’s permissions, so the game cannot change files to which the player doesn’t have access or
    permission to edit.
    Q. Do the file systemfunctions apply to any OS?
    A. Yes. Godot functions are truly portable. Some functions are OS-specific when there is a need (like access to the Windows drives) but in
    general, you can rely on the access to work in any operating system.
    Q. Does Godot deal with binary files from other applications?
    A. Godot can manage any kind of file. However, if you are trying to use files from other programs, you need to implement your own parser to
    analyze the file format.
    Workshop
    Here are a few questions to help you review the contents of this hour.
    Quiz
    1. Where’s the user path located?
    2. True or False: ConfigFile automatically detects your game configuration and saves it.
    3. Is it possible to load a file with File class if it was saved with ConfigFile?
    4. True or False: Godot games can create, modify, and delete files and directories.
    Answers
    1. The user path resides in the common application data for the user’s operating system.
    2. False. ConfigFile can be used to save the configuration, but you need to provide the information via code.
    3. Yes. The File class can open any file, including those created with ConfigFile.
    4. True. Games have full access to the user’s file system (if the user has permissions). These functions should be used wisely.
    Exercises
    Take some time to solve the following exercises. They may require some coding experience, but solving them will improve your understanding
    of the file system functions.
    1. Modify the directory listing to show the subdirectories. This requires a recursive function. Be careful to not fall into an infinite loop by
    going into the special relative paths (“.” and “..”).
    2. Abstract the configuration saving and loading to functions that receive and return a dictionary. Pass the file path as an argument.
    3. Try to implement a configuration saver and loader using JSON.
    4. Make a system of save slots. Each slot can be a file, and the player may be able to delete them.
    5. Advanced: Implement the abstraction so that you can compress and encrypt the save game at the same time.