From 3ebfc5684362f906f9d8cebb0045dad5ea192ff5 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Tue, 31 Oct 2023 09:43:28 +0100 Subject: [PATCH] Add a page on runtime file loading and saving This accompanies a new demo project. --- about/list_of_features.rst | 3 +- tutorials/io/index.rst | 1 + .../io/runtime_file_loading_and_saving.rst | 280 ++++++++++++++++++ tutorials/ui/gui_using_fonts.rst | 7 +- 4 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 tutorials/io/runtime_file_loading_and_saving.rst diff --git a/about/list_of_features.rst b/about/list_of_features.rst index f0acbb478dd..939a415172c 100644 --- a/about/list_of_features.rst +++ b/about/list_of_features.rst @@ -724,7 +724,8 @@ File formats - Can (de)serialize any Godot datatype, including Vector2/3, Color, ... - Read XML files using :ref:`class_XMLParser`. -- Read and write ZIP files using :ref:`class_ZIPReader` and :ref:`class_ZIPPacker`. +- :ref:`Load and save images, audio/video, fonts and ZIP archives ` + in an exported project without having to go through Godot's import system. - Pack game data into a PCK file (custom format optimized for fast seeking), into a ZIP archive, or directly into the executable for single-file distribution. - :ref:`Export additional PCK files` that can be read diff --git a/tutorials/io/index.rst b/tutorials/io/index.rst index 5c84489b202..9c7f5bb14aa 100644 --- a/tutorials/io/index.rst +++ b/tutorials/io/index.rst @@ -8,4 +8,5 @@ File and data I/O background_loading data_paths saving_games + runtime_file_loading_and_saving binary_serialization_api diff --git a/tutorials/io/runtime_file_loading_and_saving.rst b/tutorials/io/runtime_file_loading_and_saving.rst new file mode 100644 index 00000000000..4256aedd98a --- /dev/null +++ b/tutorials/io/runtime_file_loading_and_saving.rst @@ -0,0 +1,280 @@ +.. _doc_runtime_loading_and_saving: + +Runtime file loading and saving +=============================== + +.. seealso:: + + See :ref:`doc_saving_games` for information on saving and loading game progression. + +Sometimes, :ref:`doc_exporting_pcks` is not ideal when you want players to be +able to load user-generated content in your project. It requires users to +generate a PCK or ZIP file through the Godot editor, which contains resources +imported by Godot. + +Example use cases for runtime file loading and saving include: + +- Loading texture packs designed for the game. +- Loading user-provided audio tracks and playing them back in an in-game radio station. +- Loading custom levels or 3D models that can be designed with any 3D DCC that + can export to glTF (including glTF scenes saved by Godot at runtime). +- Using user-provided fonts for menus and HUD. +- Saving/loading a file format that can contain multiple files but can still + easily be read by other applications (ZIP). +- Loading files created by another game or program, or even game data files from + another game not made with Godot. + +.. warning:: + + Do **not** use this runtime loading approach to load resources that are part + of the project, as it's less efficient and doesn't allow benefiting from + Godot's resource handling functionality (such as translation remaps). See + :ref:`doc_import_process` for details. + +.. seealso:: + + You can see how saving and loading works in action using the + `Run-time File Saving and Loading (Serialization) demo project `__. + +Plain text and binary files +--------------------------- + +Godot's :ref:`class_FileAccess` class provides methods to access files on the +filesystem for reading and writing: + +:: + + func save_file(content): + var file = FileAccess.open("/path/to/file.txt", FileAccess.WRITE) + file.store_string(content) + + func load_file(): + var file = FileAccess.open("/path/to/file.txt", FileAccess.READ) + var content = file.get_as_text() + return content + +To handle custom binary formats (such as loading file formats not supported by +Godot), :ref:`class_FileAccess` provides several methods to read/write integers, +floats, strings and more. These FileAccess methods have names that start with +``get_`` and ``store_``. + +If you need more control over reading binary files or need to read binary +streams that are not part of a file, :ref:`class_PackedByteArray` provides +several helper methods to decode/encode series of bytes to integers, floats, +strings and more. These PackedByteArray methods have names that start with +``decode_`` and ``encode_``. See also :ref:`doc_binary_serialization_api`. + +Images +------ + +Image's :ref:`load_from_file` static method +handles everything, from format detection based on file extension to reading the +file from disk. + +If you need error handling or more control (such as changing the scale a SVG is +loaded at), use one of the following methods depending on the file format: + +- :ref:`load_jpg_from_buffer` +- :ref:`load_ktx_from_buffer` +- :ref:`load_png_from_buffer` +- :ref:`load_svg_from_buffer` + or :ref:`load_svg_from_string` +- :ref:`load_tga_from_buffer` +- :ref:`load_webp_from_buffer` + +Several image formats can also be saved by Godot at runtime using the following +methods: + +- :ref:`save_png` + or :ref:`save_png_to_buffer` +- :ref:`save_webp` + or :ref:`save_webp_to_buffer` +- :ref:`save_jpg` + or :ref:`save_jpg_to_buffer` +- :ref:`save_exr` + or :ref:`save_exr_to_buffer` + *(only available in editor builds, cannot be used in exported projects)* + +The methods with the ``to_buffer`` suffix save the image to a PackedByteArray +instead of the filesystem. This is useful to send the image over the network or +into a ZIP archive without having to write it on the filesystem. This can +increase performance by reducing I/O utilization. + +Example of loading an image and displaying it in a :ref:`class_TextureRect` node +(which requires conversion to :ref:`class_ImageTexture`): + +:: + + # Load an image of any format supported by Godot from the filesystem. + var image = Image.load_from_file(path) + $TextureRect.texture = ImageTexture.create_from_image(image) + + # Save the loaded Image to a PNG image. + image.save_png("/path/to/file.png") + + # Save the converted ImageTexture to a PNG image. + $TextureRect.texture.get_image().save_png("/path/to/file.png") + +Audio/video files +----------------- + +Godot supports loading Ogg Vorbis audio at runtime. Note that not *all* files +with an ``.ogg`` extension may be Ogg Vorbis files. Some may be Ogg Theora +videos, or contain Opus audio within an Ogg container. These files will **not** +load correctly as audio files in Godot. + +Example of loading an Ogg Vorbis audio file in an :ref:`class_AudioStreamPlayer` node: + +:: + + $AudioStreamPlayer.stream = AudioStreamOggVorbis.load_from_file(path) + +Example of loading an Ogg Theora video file in a :ref:`class_VideoStreamPlayer` node: + +:: + + var video_stream_theora = VideoStreamTheora.new() + # File extension is ignored, so it is possible to load Ogg Theora videos + # that have an `.ogg` extension this way. + video_stream_theora.file = "/path/to/file.ogv" + $VideoStreamPlayer.stream = video_stream_theora + + # VideoStreamPlayer's Autoplay property won't work if the stream is empty + # before this property is set, so call `play()` after setting `stream`. + $VideoStreamPlayer.play() + +.. note:: + + Godot doesn't support runtime loading of MP3 or WAV files yet. Until this is + implemented, it's feasible to implement runtime WAV loading using a script + since :ref:`class_AudioStreamWAV`'s ``data`` property is exposed to + scripting. + + It's still possible to *save* WAV files using + :ref:`save_to_wav`, which is useful + for procedurally generated audio or microphone recordings. + +3D scenes +--------- + +Godot has first-class support for glTF 2.0, both in the editor and exported +projects. Using :ref:`class_gltfdocument` and :ref:`class_gltfstate` together, +Godot can load and save glTF files in exported projects, in both text +(``.gltf``) and binary (``.glb``) formats. The binary format should be preferred +as it's faster to write and smaller, but the text format is easier to debug. + +Example of loading a glTF scene and appending its root node to the scene: + +:: + + # Load an existing glTF scene. + # GLTFState is used by GLTFDocument to store the loaded scene's state. + # GLTFDocument is the class that handles actually loading glTF data into a Godot node tree, + # which means it supports glTF features such as lights and cameras. + var gltf_document_load = GLTFDocument.new() + var gltf_state_load = GLTFState.new() + var error = gltf_document_load.append_from_file("/path/to/file.gltf", gltf_state_load) + if error == OK: + var gltf_scene_root_node = gltf_document_load.generate_scene(gltf_state_load) + add_child(gltf_scene_root_node) + else: + show_error("Couldn't load glTF scene (error code: %s)." % error_string(error)) + + # Save a new glTF scene. + var gltf_document_save := GLTFDocument.new() + var gltf_state_save := GLTFState.new() + gltf_document_save.append_from_scene(gltf_scene_root_node, gltf_state_save) + # The file extension in the output `path` (`.gltf` or `.glb`) determines + # whether the output uses text or binary format. + # `GLTFDocument.generate_buffer()` is also available for saving to memory. + gltf_document_save.write_to_filesystem(gltf_state_save, path) + +.. _doc_runtime_file_loading_and_saving_fonts: + +Fonts +----- + +:ref:`load_dynamic_font` supports the following +font file formats: TTF, OTF, WOFF, WOFF2, PFB, PFM + +On the other hand, :ref:`load_bitmap_font` supports +the `BMFont `__ format (``.fnt`` or ``.font``). + +Additionally, it is possible to load any font that is installed on the system using +Godot's support for :ref:`doc_using_fonts_system_fonts`. + +Example of loading a font file automatically according to its file extension, +then adding it as a theme override to a :ref:`class_Label` node: + +:: + + var path = "/path/to/font.ttf" + var path_lower = path.to_lower() + var font_file = FontFile.new() + if ( + path_lower.ends_with(".ttf") + or path_lower.ends_with(".otf") + or path_lower.ends_with(".woff") + or path_lower.ends_with(".woff2") + or path_lower.ends_with(".pfb") + or path_lower.ends_with(".pfm") + ): + font_file.load_dynamic_font(path) + elif path_lower.ends_with(".fnt") or path_lower.ends_with(".font"): + font_file.load_bitmap_font(path) + else: + push_error("Invalid font file format.") + + if not font_file.data.is_empty(): + # If font was loaded successfully, add it as a theme override. + $Label.add_theme_font_override("font", font_file) + +ZIP archives +------------ + +Godot supports reading and writing ZIP archives using the :ref:`class_zipreader` +and :ref:`class_zippacker` classes. This supports any ZIP file, including files +generated by Godot's "Export PCK/ZIP" functionality (although these will contain +imported Godot resources rather than the original project files). + +.. note:: + + Use :ref:`load_resource_pack` + to load PCK or ZIP files exported by Godot as + :ref:`additional data packs `. That approach is preferred + for DLCs, as it makes interacting with additional data packs seamless (virtual filesystem). + +This ZIP archive support can be combined with runtime image, 3D scene and audio +loading to provide a seamless modding experience without requiring users to go +through the Godot editor to generate PCK/ZIP files. + +Example that lists files in a ZIP archive in an :ref:`class_ItemList` node, +then writes contents read from it to a new ZIP archive (essentially duplicating the archive): + +:: + + # Load an existing ZIP archive. + var zip_reader = ZIPReader.new() + zip_reader.open(path) + var files = zip_reader.get_files() + # The list of files isn't sorted by default. Sort it for more consistent processing. + files.sort() + for file in files: + $ItemList.add_item(file, null) + # Make folders disabled in the list. + $ItemList.set_item_disabled(-1, file.ends_with("/")) + + # Save a new ZIP archive. + var zip_packer = ZIPPacker.new() + var error = zip_packer.open(path) + if error != OK: + push_error("Couldn't open path for saving ZIP archive (error code: %s)." % error_string(error)) + return + + # Reuse the above ZIPReader instance to read files from an existing ZIP archive. + for file in zip_reader.get_files(): + zip_packer.start_file(file) + zip_packer.write_file(zip_reader.read_file(file)) + zip_packer.close_file() + + zip_packer.close() diff --git a/tutorials/ui/gui_using_fonts.rst b/tutorials/ui/gui_using_fonts.rst index 5c78dbfeab3..44d5c6acf78 100644 --- a/tutorials/ui/gui_using_fonts.rst +++ b/tutorials/ui/gui_using_fonts.rst @@ -665,7 +665,8 @@ possible to display CJK characters and emoji without having to load a custom font. There are some restrictions that apply though, as mentioned in the :ref:`Using emoji ` section. -Create a SystemFont resource in the location where you desire to use the system font: +Create a :ref:`class_SystemFont` resource in the location where you desire to +use the system font: .. figure:: img/using_fonts_system_font_create.webp :align: center @@ -720,6 +721,10 @@ that labels can extend further if needed. distributions, different fonts may be displayed for a given system font name or alias. +It is also possible to load fonts at runtime even if they aren't installed on the system. +See :ref:`Runtime loading and saving ` +for details. + .. _doc_using_fonts_font_prerendering: Font prerendering