執行時檔案載入與儲存
也參考
關於儲存與載入遊戲進度,請參考 保存遊戲。
有時候,當你希望玩家能夠在專案中載入使用者自製內容時,匯出資源包、補丁和模組 就不太理想。這會要求使用者必須透過 Godot 編輯器產生 PCK 或 ZIP 檔案,內容為 Godot 匯入過的資源。
執行時檔案載入與儲存的常見應用情境包括:
載入為遊戲設計的材質包。
載入玩家提供的音樂檔並於遊戲內廣播電台播放。
載入可由任何能匯出 glTF 或 FBX 的 3D DCC 製作的自訂關卡或 3D 模型(包含於執行時由 Godot 儲存的 glTF 場景)。
在選單和 HUD 上使用玩家自備的字型。
儲存/載入一種能包含多個檔案,且能被其他應用程式輕鬆讀取的檔案格式(如 ZIP)。
載入其他遊戲或程式產生的檔案,甚至是非 Godot 製作的遊戲資料檔案。
執行時檔案載入也能搭配 HTTP 請求,直接從網路載入資源。
警告
請勿 用這種執行時載入方式來載入專案自身的資源,因為效率較低,且無法利用 Godot 資源管理(如翻譯重導等)功能。詳情請見 匯入流程。
也參考
你可以參考這個範例專案來了解執行時儲存與載入的實際運作:執行時檔案儲存與載入(序列化)Demo。
純文字與二進位檔案
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 提供了多種讀寫整數、浮點數、字串等的方法。這些方法名稱多以 get_ 或 store_ 開頭。
如果你需要更細緻地控制二進位檔案的讀取,或是要讀取不屬於檔案的二進位資料流,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 支援在執行時載入 Ogg Vorbis、MP3 與 WAV 音訊。請注意, 副檔名為 .ogg 的檔案並非 全 都是 Ogg Vorbis; 有些可能是 Ogg Theora 影片, 或是在 Ogg 容器中的 Opus 音訊。這些檔案在 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 a `.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 a `.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)格式。建議優先使用二進位格式,因為寫入速度較快且檔案較小,但文字格式較易除錯。
自 Godot 4.3 起,也可在執行時透過 FBXDocument 與 FBXState 載入(但無法儲存)FBX 場景。其程式碼與 glTF 相同,但需在以下範例中將所有 GLTFDocument 與 GLTFState 替換為 FBXDocument 與 FBXState。目前在執行時載入 FBX 仍有一些 已知問題,因此暫時建議優先使用 glTF。
以下範例說明如何載入 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();