Збереження ігор

Вступ

Збереження гри може бути складним. Наприклад, може бути бажано зберігати інформацію з декількох об'єктів на декількох рівнях. Розширені системи збереження ігор повинні містити додаткову інформацію про довільну кількість об'єктів. Це дозволить функції збереження масштабуватися, оскільки гра стає все більш складною.

Примітка

Якщо ви хочете зберегти конфігурацію користувача, ви можете використовувати клас ConfigFile для цієї мети.

Визначення постійних об'єктів

Спершу, ми повинні визначити, які об'єкти ми хочемо зберігати між ігровими сесіями та яку інформацію від цих об'єктів ми хочемо зберегти. Для цього підручника ми будемо використовувати групи для позначення та обробки об'єктів, які потрібно зберегти, але, безумовно, можливі інші методи.

Почнемо з додавання об'єктів, які ми хочемо зберегти, до групи "Persist". Ми можемо зробити це за допомогою графічного інтерфейсу, або скрипту. Додамо відповідні вузли за допомогою графічного інтерфейсу:

../../_images/groups.png

Як тільки це буде зроблено, при потребі зберегти гру, ми зможемо отримати всі об'єкти, які необхідно зберегти, і зберегти їх за допомогою цього скрипту:

var save_nodes = get_tree().get_nodes_in_group("Persist")
for i in save_nodes:
    # Now, we can call our save function on each node.

Серіалізація

Наступним кроком є серіалізація даних. Це значно полегшує читання та зберігання на диску. У цьому випадку ми припускаємо, що кожен член групи Persist є вузлом екземпляра і, таким чином, має шлях. GDScript має допоміжні функції для цього, такі як to_json() і parse_json(), тому ми будемо використовувати словник. Наш вузол повинен містити функцію збереження, яка повертає ці дані. Функція збереження буде виглядати так:

func save():
    var save_dict = {
        "filename" : get_filename(),
        "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

Це дає нам словник в стилі { "назва_змінної":значення_змінної }, який буде корисний при завантаженні.

Збереження та читання даних

Як описано в підручнику Файлова система, нам потрібно буде відкрити файл, щоб ми могли записати в нього, або прочитати з нього. Тепер, коли у нас є спосіб викликати наші групи і отримати їх відповідні дані, давайте використовувати to_json() для перетворення їх у рядок, який легко зберегти і зберігання їх у файлі. При такому підході, кожен рядок є своїм власним об'єктом, тому у нас є простий спосіб витягнути дані з файлу.

# Note: This can be called from anywhere inside the tree. This function is
# path independent.
# Go through everything in the persist category and ask them to return a
# dict of relevant variables.
func save_game():
    var save_game = File.new()
    save_game.open("user://savegame.save", File.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.filename.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")

        # Store the save dictionary as a new line in the save file.
        save_game.store_line(to_json(node_data))
    save_game.close()

Гра збережена! Завантаження також досить просте. Для цього ми прочитаємо кожен рядок, за допомогою parse_json(), щоб прочитати словник, а потім проведемо ітерацію словника, щоб прочитати наші змінні. Але спочатку нам потрібно буде створити об'єкт, і для досягнення цього ми зможемо використовувати ім'я файлу та батьківські значення. Ось наша функція завантаження:

# Note: This can be called from anywhere inside the tree. This function
# is path independent.
func load_game():
    var save_game = File.new()
    if not save_game.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.
    save_game.open("user://savegame.save", File.READ)
    while save_game.get_position() < save_game.get_len():
        # Get the saved dictionary from the next line in the save file
        var node_data = parse_json(save_game.get_line())

        # Firstly, we need to create the object and add it to the tree and set its position.
        var new_object = load(node_data["filename"]).instance()
        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])

    save_game.close()

Тепер ми можемо зберегти і завантажити довільну кількість об'єктів практично в будь-якому місці по всьому дереву сцени! Кожен об'єкт може зберігати різні дані в залежності від того, що йому потрібно зберегти.

Деякі примітки

Ми приховали налаштування стану гри для завантаження. Зрештою, це залежить від творця проекту, де піде більша частина цієї логіки. Це часто ускладнюється і потрібно буде багато попрацювати, виходячи з потреб окремого проекту.

Крім того, наша реалізація передбачає, що жодні об'єкти Persist не є дочірніми елементами інших об'єктів Persist. В іншому випадку будуть створені неприпустимі шляхи. Щоб розмістити вкладені об'єкти Persist, спробуйте зберегти об'єкти поетапно. Спочатку завантажте батьківські об'єкти, щоб вони були доступні для виклику add_child() під час завантаження дочірніх об'єктів. Вам також знадобиться спосіб зв'язати дітей з батьками, оскільки NodePath (Шлях вузла), ймовірно, буде недійсним.