執行時檔案載入與儲存

也參考

關於儲存與載入遊戲進度,請參考 保存遊戲

有時候,當你希望玩家能夠在專案中載入使用者自製內容時,匯出資源包、補丁和模組 就不太理想。這會要求使用者必須透過 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

若需處理自訂二進位格式(例如載入 Godot 不支援的檔案格式),FileAccess 提供了多種讀寫整數、浮點數、字串等的方法。這些方法名稱多以 get_store_ 開頭。

如果你需要更細緻地控制二進位檔案的讀取,或是要讀取不屬於檔案的二進位資料流,PackedByteArray 提供多種輔助方法,可以將位元組序列解碼/編碼為整數、浮點數、字串等。這些方法名稱通常以 decode_encode_ 開頭。詳情請參考 二進位序列化 API

影像

Image 的靜態方法 Image.load_from_file 能自動處理檔案格式偵測(依副檔名判斷)與從磁碟讀取檔案。

如果你需要錯誤處理或更細緻的控制(例如調整載入 SVG 時的縮放比例),請根據檔案格式改用下列方法:

Godot 也能於執行時將多種圖片格式儲存,請使用以下方法:

帶有 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")

音訊/影片檔案

Godot 支援在執行時載入 Ogg Vorbis、MP3 與 WAV 音訊。請注意, 副檔名為 .ogg 的檔案並非 都是 Ogg Vorbis; 有些可能是 Ogg Theora 影片, 或是在 Ogg 容器中的 Opus 音訊。這些檔案在 Godot 中 不會 被正確載入為音訊。

以下是在 AudioStreamPlayer 節點中載入 Ogg Vorbis 音訊檔案的範例:

$AudioStreamPlayer.stream = AudioStreamOggVorbis.load_from_file(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()

3D 場景

Godot 在編輯器及匯出專案中皆完整支援 glTF 2.0。搭配使用 GLTFDocumentGLTFState,Godot 可於匯出專案中載入與儲存 glTF 檔案,支援文字(.gltf)與二進位(.glb)格式。建議優先使用二進位格式,因為寫入速度較快且檔案較小,但文字格式較易除錯。

自 Godot 4.3 起,也可在執行時透過 FBXDocumentFBXState 載入(但無法儲存)FBX 場景。其程式碼與 glTF 相同,但需在以下範例中將所有 GLTFDocumentGLTFState 替換為 FBXDocumentFBXState。目前在執行時載入 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)

備註

於載入 glTF 場景時,必須設定*基底路徑*,才能正確載入外部資源(如貼圖)。若從檔案載入,基底路徑會自動設為檔案所在資料夾;若從緩衝區載入,則需手動設定此基底路徑,因為 Godot 無法自動推斷。

設定基底路徑時,請於呼叫 GLTFDocument.append_from_bufferGLTFDocument.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)

ZIP 壓縮檔案

Godot 支援使用 ZIPReaderZIPPacker 來讀寫 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()