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("檔案路徑")
Tags : godot