godot存檔讀檔
紀錄一下godot4中存檔和讀檔的方式,使用的程式碼都來自於參考資料。
參考資料
文件:https://docs.godotengine.org/en/stable/tutorials/io/saving_games.html
參考範例1:Different save load methods demo - Godot Asset Library (godotengine.org)
參考範例2(官方範例):GitHub - godotengine/godot-demo-projects at 4.0
其中在官方範例中loading/serialization為存讀檔案的示範,展示了JSON和ConfigFile的方式。
而別人寫的範例中多示範了兩種方式,readme中有另外備註了不同類型的比較,其中還有備註了PackedScene(保存場景)的方式時間最久、而且檔案與更新的遊戲可能不相容。
此表格來自範例1備註
儲存與讀取類型 | Text | Binary | Standard format | Stores all data types |
---|---|---|---|---|
JSON | ✅ | 🟥 | ✅ | 🟥 |
ConfigFile | ✅ | 🟥 | ✅ | ✅ |
Custom resource | ✅ | ✅ | 🟥 | ✅ |
PackedScene | ✅ | ✅ | 🟥 | ✅ |
下方範例json的部分抓官方文件範例加上簡單註解,其餘則是範例中簡單截出的語法。
json
文件中推薦建立虛列化格式來方便處理存檔,在這邊處理存檔資料。
func save():
var save_dict = {
"filename" : get_scene_file_path(),
"parent" : get_parent().get_path(),
"pos_x" : position.x, # Vector2 is not supported by JSON
"pos_y" : position.y,
"attack" : attack,
"defense" : defense,
"current_health" : current_health,
"max_health" : max_health,
"damage" : damage,
"regen" : regen,
"experience" : experience,
"tnl" : tnl,
"level" : level,
"attack_growth" : attack_growth,
"defense_growth" : defense_growth,
"health_growth" : health_growth,
"is_alive" : is_alive,
"last_attack" : last_attack
}
return save_dict
存檔,用FileAccess開啟/建立檔案後開始抓資料寫入(store_line)。
func save_game():
var save_game = FileAccess.open("user://savegame.save", FileAccess.WRITE)
var save_nodes = get_tree().get_nodes_in_group("Persist")
for node in save_nodes:
# Check the node is an instanced scene so it can be instanced again during load.
if node.scene_file_path.is_empty():
print("persistent node '%s' is not an instanced scene, skipped" % node.name)
continue
# #####重點在這區 檢查設定的格式是否存在並使用
# Check the node has a save function.
if !node.has_method("save"):
print("persistent node '%s' is missing a save() function, skipped" % node.name)
continue
# Call the node's save function.
var node_data = node.call("save")
# JSON provides a static method to serialized JSON string.
var json_string = JSON.stringify(node_data)
# Store the save dictionary as a new line in the save file.
save_game.store_line(json_string)
讀檔,先確認檔案是否存在,存在的話讀取並塞回遊戲中。
func load_game():
if not FileAccess.file_exists("user://savegame.save"):
return # Error! We don't have a save to load.
# We need to revert the game state so we're not cloning objects
# during loading. This will vary wildly depending on the needs of a
# project, so take care with this step.
# For our example, we will accomplish this by deleting saveable objects.
var save_nodes = get_tree().get_nodes_in_group("Persist")
for i in save_nodes:
i.queue_free()
# Load the file line by line and process that dictionary to restore
# the object it represents.
var save_game = FileAccess.open("user://savegame.save", FileAccess.READ)
while save_game.get_position() < save_game.get_length():
var json_string = save_game.get_line()
# Creates the helper class to interact with JSON
var json = JSON.new()
# Check if there is any error while parsing the JSON string, skip in case of failure
var parse_result = json.parse(json_string)
if not parse_result == OK:
print("JSON Parse Error: ", json.get_error_message(), " in ", json_string, " at line ", json.get_error_line())
continue
# Get the data from the JSON object
var node_data = json.get_data()
# Firstly, we need to create the object and add it to the tree and set its position.
var new_object = load(node_data["filename"]).instantiate()
get_node(node_data["parent"]).add_child(new_object)
new_object.position = Vector2(node_data["pos_x"], node_data["pos_y"])
# Now we set the remaining variables.
for i in node_data.keys():
if i == "filename" or i == "parent" or i == "pos_x" or i == "pos_y":
continue
new_object.set(i, node_data[i])
config file
存檔
var config_file := ConfigFile.new()
for actor in get_tree().get_nodes_in_group("actors"):
config_file.set_value("actors", actor.name, actor.serialize_config_file())
config_file.save(SAVE_PATH_CONFIG_FILE)
讀檔
if not FileAccess.file_exists(SAVE_PATH_CONFIG_FILE):
$UILayer/UI/LoadConfigFile.disabled = true
return
clear_actors()
var config_file := ConfigFile.new()
config_file.load(SAVE_PATH_CONFIG_FILE)
for key in config_file.get_section_keys("actors"):
var actor := Actor.instantiate()
actor.init_config_file(config_file.get_value("actors", key))
$Actors.add_child(actor)
actor.owner = $Actors
custom resource
存檔
var actor_data: Array[ActorData] = []
for actor in get_tree().get_nodes_in_group("actors"):
actor_data.append(actor.serialize_custom_resource())
var save_resource := SaveResource.new(actor_data)
ResourceSaver.save(save_resource, SAVE_PATH_CUSTOM_RESOURCE)
讀檔
if not FileAccess.file_exists(SAVE_PATH_CUSTOM_RESOURCE):
$UILayer/UI/LoadCustomResource.disabled = true
return
clear_actors()
var save_resource := ResourceLoader.load(SAVE_PATH_CUSTOM_RESOURCE, "SaveResource")
for actor_data in save_resource.actor_data:
var actor := Actor.instantiate()
actor.init_custom_resource(actor_data)
$Actors.add_child(actor)
actor.owner = $Actors
packed scene
存檔
var scene := PackedScene.new()
scene.pack($Actors)
ResourceSaver.save(scene, SAVE_PATH_PACKED_SCENE)
讀檔
if not FileAccess.file_exists(SAVE_PATH_PACKED_SCENE):
$UILayer/UI/LoadPackedScene.disabled = true
return
$Actors.free()
var scene := ResourceLoader.load(SAVE_PATH_PACKED_SCENE, "PackedScene")
add_child(scene.instantiate())
刪除檔案
DirAccess.remove_absolute("檔案路徑")