运行时文件加载和保存
参见
更多关于游戏进度的保存和读取的信息见《保存游戏》。
有时,当你希望玩家能够在项目中加载用户生成的内容时,导出包、补丁和模组并不理想。它要求用户通过 Godot 编辑器生成 PCK 或 ZIP 文件,其中包含由 Godot 导入的资源。
运行时文件加载和保存的示例用例包括:
加载为游戏设计的纹理包。
加载用户提供的音轨,在游戏内的电台中播放。
加载自定义关卡和 3D 模型,它们可以使用能够导出 glTF 的任意 3D DCC 设计(包括 Godot 在运行时保存的 glTF 场景)。
为菜单和 HUD 使用用户提供的字体。
保存/加载一种可以包含多个文件但仍可被其他应用程序轻松读取的文件格式(ZIP)。
加载由其他游戏或程序创建的文件,甚至是非 Godot 游戏的游戏数据文件。
运行时文件加载可以和 HTTP 请求结合起来,直接加载互联网上的资源。
警告
请勿用这种运行时加载的方法来加载属于项目本身的资源,因为它效率较低,且无法利用 Godot 的资源处理功能(例如翻译重定向)。详见《导入流程》。
参见
你可以使用运行时文件保存和加载(序列化)演示项目来查看保存和加载的实际工作方式。
纯文本文件和二进制文件
Godot 的 FileAccess 类提供了读写文件系统中文件的方法:
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
private void SaveFile(string content)
{
using var file = FileAccess.Open("/Path/To/File.txt", FileAccess.ModeFlags.Write);
file.StoreString(content);
}
private string LoadFile()
{
using var file = FileAccess.Open("/Path/To/File.txt", FileAccess.ModeFlags.Read);
string content = file.GetAsText();
return content;
}
为了处理自定义二进制格式(例如加载 Godot 不支持的文件格式),FileAccess 提供了几种方法来读取/写入整数、浮点数、字符串等。这些 FileAccess 方法的名称以 get_
和 store_
。
如果你需要对读取二进制文件进行更多控制,或者需要读取不属于文件的二进制流,PackedByteArray 提供了几种辅助方法,可以将一系列字节解码/编码为整数、浮点、字符串等数据类型。这些 PackedByteArray 方法的名称以 decode_
和 encode_
开头。另请参阅《二进制序列化 API》。
图像
Image 的 Image.load_from_file 静态方法处理各种事物,包括从基于文件扩展名的格式检测到从磁盘读取文件。
如果你需要错误处理或者更多的控制(例如更改加载 SVG 时的缩放),请根据文件格式使用以下方法:
Godot 还可以在运行时使用以下方法保存几种图像格式:
Image.save_exr 或 Image.save_exr_to_buffer (仅在编辑器版本中可用,无法在导出后的项目中使用)
带有 to_buffer
后缀的方法会将图像保存到 PackedByteArray 而不是文件系统。这有利于通过网络发送图像或将图像发生到 ZIP 存档,无需将其写入到文件系统。这可以通过降低 I/O 利用率来提高性能。
备注
如果在 3D 曲面上显示加载的图像,请确保调用 Image.generate_mipmaps,以便在远处观察时纹理看起来不会有颗粒感。在 2D 中,当遵循关于《减少降采样时的锯齿》的说明时也很有用。
加载图像并将其显示在 TextureRect 节点(需要转换为 ImageTexture)中的示例:
# Load an image of any format supported by Godot from the filesystem.
var image = Image.load_from_file(path)
# Optionally, generate mipmaps if displaying the texture on a 3D surface
# so that the texture doesn't look grainy when viewed at a distance.
#image.generate_mipmaps()
$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")
// Load an image of any format supported by Godot from the filesystem.
var image = Image.LoadFromFile(path);
// Optionally, generate mipmaps if displaying the texture on a 3D surface
// so that the texture doesn't look grainy when viewed at a distance.
// image.GenerateMipmaps();
GetNode<TextureRect>("TextureRect").Texture = ImageTexture.CreateFromImage(image);
// Save the loaded Image to a PNG image.
image.SavePng("/Path/To/File.png");
// Save the converted ImageTexture to a PNG image.
GetNode<TextureRect>("TextureRect").Texture.GetImage().SavePng("/Path/To/File.png");
音视频文件
Godot supports loading Ogg Vorbis, MP3, and WAV audio at runtime. Note that not all
files with an .ogg
extension are 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.
通过 AudioStreamPlayer 节点加载 Ogg Vorbis 音频文件的示例:
$AudioStreamPlayer.stream = AudioStreamOggVorbis.load_from_file(path)
GetNode<AudioStreamPlayer>("AudioStreamPlayer").Stream = AudioStreamOggVorbis.LoadFromFile(path);
通过 VideoStreamPlayer 节点加载 Ogg Theora 视频文件的示例:
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()
var videoStreamTheora = new VideoStreamTheora();
// File extension is ignored, so it is possible to load Ogg Theora videos
// that have an `.ogg` extension this way.
videoStreamTheora.File = "/Path/To/File.ogv";
GetNode<VideoStreamPlayer>("VideoStreamPlayer").Stream = videoStreamTheora;
// VideoStreamPlayer's Autoplay property won't work if the stream is empty
// before this property is set, so call `Play()` after setting `Stream`.
GetNode<VideoStreamPlayer>("VideoStreamPlayer").Play();
3D 场景
Godot 在编辑器和导出项目中都对 glTF 2.0 提供了一流的支持。结合使用 GLTFDocument 和 GLTFState,Godot 可以在导出的项目中加载和保存 glTF 文件,包括文本格式(.gltf
)和二进制格式(.glb
)。二进制格式应该优先考虑,因为它写入速度更快且体积更小,但文本格式更易于调试。
加载 glTF 场景并将其根节点附加到场景的示例:
# 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)
// 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 gltfDocumentLoad = new GltfDocument();
var gltfStateLoad = new GltfState();
var error = gltfDocumentLoad.AppendFromFile("/Path/To/File.gltf", gltfStateLoad);
if (error == Error.Ok)
{
var gltfSceneRootNode = gltfDocumentLoad.GenerateScene(gltfStateLoad);
AddChild(gltfSceneRootNode);
}
else
{
GD.PrintErr($"Couldn't load glTF scene (error code: {error}).");
}
// Save a new glTF scene.
var gltfDocumentSave = new GltfDocument();
var gltfStateSave = new GltfState();
gltfDocumentSave.AppendFromScene(gltfSceneRootNode, gltfStateSave);
// The file extension in the output `path` (`.gltf` or `.glb`) determines
// whether the output uses text or binary format.
// `GltfDocument.GenerateBuffer()` is also available for saving to memory.
gltfDocumentSave.WriteToFilesystem(gltfStateSave, path);
备注
加载 glTF 场景时,必须设置基础路径,以便可以正确加载纹理等外部资源。从文件加载时,基础路径会自动设置为包含该文件的文件夹。从缓冲区加载时,必须手动设置该基础路径,因为 Godot 无法推断该路径。
要设置基础路径,请在调用 GLTFDocument.append_from_buffer 或 GLTFDocument.append_from_file 之前,在 GLTFState 实例上设置 GLTFState.base_path。
字体
FontFile.load_dynamic_font 支持以下字体文件格式:TTF、OTF、WOFF、WOFF2、PFB、PFM
另一方面,FontFile.load_bitmap_font支持 BMFont 格式(.fnt
或 .font
)。
此外,可以使用 Godot 对《系统字体》的支持来加载系统上安装的任何字体。
根据文件扩展名自动加载字体文件,然后将其作为主题覆盖添加到 Label 节点的示例:
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)
string path = "/Path/To/Font.ttf";
var fontFile = new FontFile();
if (
path.EndsWith(".ttf", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith(".otf", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith(".woff", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith(".woff2", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith(".pfb", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith(".pfm", StringComparison.OrdinalIgnoreCase)
)
{
fontFile.LoadDynamicFont(path);
}
else if (path.EndsWith(".fnt", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".font", StringComparison.OrdinalIgnoreCase))
{
fontFile.LoadBitmapFont(path);
}
else
{
GD.PrintErr("Invalid font file format.");
}
if (!fontFile.Data.IsEmpty())
{
// If font was loaded successfully, add it as a theme override.
GetNode<Label>("Label").AddThemeFontOverride("font", fontFile);
}
ZIP 压缩包
Godot 支持使用 ZIPReader 和 ZIPPacker 类读取和写入 ZIP 档案。这支持任何 ZIP 文件,包括由 Godot 的“导出 PCK/ZIP”功能生成的文件(尽管这些文件将包含导入的 Godot 资源而不是原始项目文件)。
备注
使用 ProjectSettings.load_resource_pack 将 Godot 导出的 PCK 或 ZIP 文件加载为 附加数据包。这种方法是 DLC 的首选,因为它可以无缝地与附加数据包(虚拟文件系统)进行交互。
这种 ZIP 存档支持可与运行时图像、3D 场景和音频加载相结合,提供无缝的模组化体验,而无需用户通过 Godot 编辑器生成 PCK/ZIP 文件。
示例在 ItemList 节点中列出 ZIP 存档中的文件,然后将从中读取的内容写入新的 ZIP 存档(本质上是复制该存档):
# 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()
// Load an existing ZIP archive.
var zipReader = new ZipReader();
zipReader.Open(path);
string[] files = zipReader.GetFiles();
// The list of files isn't sorted by default. Sort it for more consistent processing.
Array.Sort(files);
foreach (string file in files)
{
GetNode<ItemList>("ItemList").AddItem(file);
// Make folders disabled in the list.
GetNode<ItemList>("ItemList").SetItemDisabled(-1, file.EndsWith('/'));
}
// Save a new ZIP archive.
var zipPacker = new ZipPacker();
var error = zipPacker.Open(path);
if (error != Error.Ok)
{
GD.PrintErr($"Couldn't open path for saving ZIP archive (error code: {error}).");
return;
}
// Reuse the above ZIPReader instance to read files from an existing ZIP archive.
foreach (string file in zipReader.GetFiles())
{
zipPacker.StartFile(file);
zipPacker.WriteFile(zipReader.ReadFile(file));
zipPacker.CloseFile();
}
zipPacker.Close();