Синглтоны (автозагрузка)

Введение

Система сцен Godot, будучи мощной и гибкой, имеет недостаток: отсутствует способ хранения информации (например, счет игрока или инвентарь), которая необходима для нескольких сцен.

Можно решить эту проблему с помощью некоторых обходных путей, но у них есть свои ограничения:

  • Вы можете использовать "мастер-сцену", которая загружает и выгружает другие сцены в качестве своих детей. Однако это означает, что вы больше не можете запускать эти сцены по отдельности и ожидать от них корректной работы.

  • Информация может храниться на диске в user://, а затем загружаться сценами, которые требуют этого, но увы сохранение и загрузка данных часто является громоздкой и может быть медленной.

Шаблон Singleton - полезный инструмент для решения распространенного варианта использования, когда вам необходимо хранить постоянную информацию между сценами. В нашем случае можно повторно использовать одну и ту же сцену или класс для нескольких синглтонов, если они имеют разные имена.

Используя эту концепцию, вы можете создавать объекты, которые:

  • Всегда загружаются, независимо от того, какая сцена в данный момент запущена.

  • Могут хранить глобальные переменные, такие как информация об игроке.

  • Могут обрабатывать переключения между сценами и переходы между сценами.

  • Действуют как синглтон, так как GDScript не поддерживает глобальные переменные по дизайну.

Узлы и скрипты автоматической загрузки могут дать нам эти характеристики.

Примечание

Godot не сделает Autoload "true" синглтоном, как того требует шаблон проектирования синглтонов. При желании пользователь может создать экземпляр несколько раз.

Совет

Если вы создаете автозагрузку как часть плагина редактора, рассмотрите возможность ее автоматической регистрации в настройках проекта при включении плагина.

Автозагрузка

Вы можете создать автозагрузку для загрузки сцены или скрипта, наследующего от Node.

Примечание

При автозагрузке скрипта будет создан Node, и скрипт будет прикреплен к нему. Этот узел будет добавлен к корневому окну просмотра перед загрузкой любых других сцен.

../../_images/singleton.webp

Чтобы автоматически загрузить сцену или сценарий, откройте меню и перейдите в раздел Project > Project Settings > Globals > Autoload.

../../_images/autoload_tab.webp

Здесь вы можете добавить любое количество сцен или скриптов. Каждой записи в списке требуется имя, которое назначается свойством name узла. Порядок добавления записей в глобальное дерево сцен можно изменять с помощью клавиш со стрелками вверх/вниз. Как и в случае с обычными сценами, движок будет считывать эти узлы сверху вниз.

../../_images/autoload_example.webp

Если отмечен флажок Enable (что установлено по умолчанию), то к синглтону можно будет получить доступ напрямую в GDScript:

PlayerVariables.health -= 10

Столбец Enable не имеет никакого эффекта в коде C#. Однако, если синглтон представляет собой скрипт C#, аналогичного эффекта можно добиться, включив статическое свойство Instance и назначив его в _Ready():

public partial class PlayerVariables : Node
{
    public static PlayerVariables Instance { get; private set; }

    public int Health { get; set; }

    public override void _Ready()
    {
        Instance = this;
    }
}

Это позволяет получить доступ к синглтону из кода C# без GetNode() и без приведения типа:

PlayerVariables.Instance.Health -= 10;

Обратите внимание, что доступ к объектам автозагрузки (скриптам и/или сценам) осуществляется так же, как и к любому другому узлу дерева сцен. На самом деле, если вы посмотрите на дерево выполняющихся сцен, вы увидите, что появились узлы автозагрузки:

../../_images/autoload_runtime.webp

Предупреждение

Автозагрузки не должны удаляться с помощью free() или queue_free() во время выполнения, иначе движок выйдет из строя.

Пользовательский переключатель сцены

В этом руководстве мы покажем, как создать переключатель сцен с использованием автозагрузок. Для базового переключения сцен можно использовать метод SceneTree.change_scene_to_file() (подробнее см. в Дерево сцены). Однако, если вам требуется более сложное поведение при смене сцен, этот метод предоставляет больше функциональности.

Для начала скачайте шаблон отсюда: singleton_autoload_starter.zip и откройте его в Godot.

Может появиться окно с уведомлением о том, что проект в последний раз открывался в более старой версии Godot, это не проблема. Нажмите Ok, чтобы открыть проект.

Проект содержит две сцены: scene_1.tscn и scene_2.tscn. Каждая сцена содержит метку с названием сцены и кнопку с подключенным сигналом pressed(). При запуске проекта он запускается в сцене scene_1.tscn. Однако нажатие кнопки ничего не делает.

Создание скрипта

Откройте окно Script и создайте новый скрипт с именем global.gd. Убедитесь, что он наследует Node:

../../_images/autoload_script.webp

The next step is to add this script to the autoload list. Starting from the menu, open Project > Project Settings > Globals > Autoload and select the script by clicking the browse button or typing its path: res://global.gd. Press Add to add it to the autoload list and name it "Global", which is required for scripts to access it by the name "Global":

../../_images/autoload_tutorial1.webp

Теперь всякий раз, когда мы запускаем какую-либо сцену в проекте, этот скрипт всегда будет загружен.

Возвращаясь к скрипту, он должен получить текущую сцену в функции _ready(). И текущая сцена (с кнопкой), и global.gd являются дочерними элементами корневого элемента, но автоматически загружаемые узлы всегда идут первыми. Это означает, что последний дочерний элемент корневого элемента всегда является загруженной сценой.

extends Node

var current_scene = null

func _ready():
    var root = get_tree().root
    # Using a negative index counts from the end, so this gets the last child node of `root`.
    current_scene = root.get_child(-1)

Теперь нам нужна функция для смены сцены. Эта функция должна освободить (очистить) текущую сцену и заменить ее на нужную нам.

func goto_scene(path):
    # This function will usually be called from a signal callback,
    # or some other function in the current scene.
    # Deleting the current scene at this point is
    # a bad idea, because it may still be executing code.
    # This will result in a crash or unexpected behavior.

    # The solution is to defer the load to a later time, when
    # we can be sure that no code from the current scene is running:

    _deferred_goto_scene.call_deferred(path)


func _deferred_goto_scene(path):
    # It is now safe to remove the current scene.
    current_scene.free()

    # Load the new scene.
    var s = ResourceLoader.load(path)

    # Instance the new scene.
    current_scene = s.instantiate()

    # Add it to the active scene, as child of root.
    get_tree().root.add_child(current_scene)

    # Optionally, to make it compatible with the SceneTree.change_scene_to_file() API.
    get_tree().current_scene = current_scene

Используя Object.call_deferred(), вторая функция будет запускаться только после завершения всего кода из текущей сцены. Таким образом, текущая сцена не будет удалена, пока она все еще используется (то есть ее код все еще выполняется).

Наконец, нам нужно заполнить пустые функции обратного вызова в двух сценах:

# Add to 'scene_1.gd'.

func _on_button_pressed():
    Global.goto_scene("res://scene_2.tscn")

и

# Add to 'scene_2.gd'.

func _on_button_pressed():
    Global.goto_scene("res://scene_1.tscn")

Запустите проект и проверьте, что вы можете переключаться между сценами, нажав кнопку.

Примечание

Когда сцены маленькие, переход происходит мгновенно. Однако, если ваши сцены более сложные, для их отображения может потребоваться значительное время. Чтобы узнать, как с этим справиться, см. следующий учебник: Фоновая загрузка.

В качестве альтернативы, если время загрузки относительно невелико (менее 3 секунд или около того), вы можете отобразить "табличку загрузки", показав какой-либо 2D-элемент непосредственно перед изменением сцены. Затем вы можете скрыть его сразу после изменения сцены. Это можно использовать, чтобы указать игроку, что сцена загружается.