Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

執行時檔案載入與儲存

也參考

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

有時候,當你希望玩家能夠在專案中載入使用者自製內容時,匯出資源包、補丁和模組 就不太理想。這會要求使用者必須透過 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)格式。建議優先使用二進位格式,因為寫入速度較快且檔案較小,但文字格式較易除錯。

Since Godot 4.3, FBX scenes can also be loaded (but not saved) at runtime using the FBXDocument and FBXState classes. The code to do so is the same as glTF, but you will need to replace all instances of GLTFDocument and GLTFState with FBXDocument and FBXState in the code samples below.

以下範例說明如何載入 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()