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

Введение

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

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

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

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

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

  • Всегда загружены, независимо от того, какая сцена в данный момент запущена
  • Может хранить глобальные переменные, такие как информация об игроке
  • Возможность переключения между сценами и переходами от одной сцены к другой
  • Действуйте как одиночка, так как GDScript не поддерживает глобальные переменные по дизайну

Autoloading nodes and scripts can give us these characteristics.

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

Вы можете использовать Автозагрузку для загрузки сцены или скрипта, унаследованного от Node. Примечание: при автозагрузке скрипта будет создан узел и к нему будет прикреплен скрипт. Этот узел будет добавлен в корневое окно(Viewport) перед загрузкой любых других сцен.

../../_images/singleton.png

Чтобы автоматически загрузить сцену или скрипт, выберите в меню Проект -> Настройки проекта и перейдите на вкладку «Автозагрузка».

../../_images/autoload_tab.png

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

../../_images/autoload_example.png

Это означает, что любой узел может получить доступ к синглтону с именем «PlayerVariables»:

var player_vars = get_node("/root/PlayerVariables")
player_vars.health -= 10
var playerVariables = (PlayerVariables)GetNode("/root/PlayerVariables");
playerVariables.Health -= 10; // Instance field.

Если колонка «Включить» отмечена галочкой (по умолчанию true), то Синглтон можно просто получить напрямую:

PlayerVariables.health -= 10
// Static members can be accessed by using the class name.
PlayerVariables.Health -= 10;

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

../../_images/autoload_runtime.png

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

This tutorial will demonstrate building a scene switcher using autoload. For basic scene switching, you can use the SceneTree.change_scene() method (see Дерево сцены for details). However, if you need more complex behavior when changing scenes, this method provides more functionality.

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

Проект содержит две сцены: Scene1.tscn and Scene2.tscn. Каждая сцена содержит Label, отображающий название сцены и кнопку с подключенным сигналом pressed(). Когда вы запускаете проект, он начинается в Scene1.tscn. Однако, нажатие кнопки ничего не даст.

Global.gd

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

../../_images/autoload_script.png

Следующим шагом будет добавление этого скрипта в список автозагрузки. Откройте Проект > Настройки проекта, перейдите на вкладку «Автозагрузка» и выберите скрипт, нажав на кнопку .. или напечатав его путь: res://Global.gd. Нажмите «Добавить», чтобы добавить его в список автозагрузки:

../../_images/autoload_tutorial1.png

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

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

extends Node

var current_scene = null

func _ready():
    var root = get_tree().get_root()
    current_scene = root.get_child(root.get_child_count() - 1)
using Godot;
using System;

public class Global : Godot.Node
{
    public Node CurrentScene { get; set; }

    public override void _Ready()
    {
        Viewport root = GetTree().GetRoot();
        CurrentScene = root.GetChild(root.GetChildCount() - 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:

    call_deferred("_deferred_goto_scene", 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.instance()

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

    # Optionally, to make it compatible with the SceneTree.change_scene() API.
    get_tree().set_current_scene(current_scene)
public void GotoScene(string path)
{
    // This function will usually be called from a signal callback,
    // or some other function from 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:

    CallDeferred(nameof(DeferredGotoScene), path);
}

public void DeferredGotoScene(string path)
{
    // It is now safe to remove the current scene
    CurrentScene.Free();

    // Load a new scene.
    var nextScene = (PackedScene)GD.Load(path);

    // Instance the new scene.
    CurrentScene = nextScene.Instance();

    // Add it to the active scene, as child of root.
    GetTree().GetRoot().AddChild(CurrentScene);

    // Optionally, to make it compatible with the SceneTree.change_scene() API.
    GetTree().SetCurrentScene(CurrentScene);
}

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

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

# Add to 'Scene1.gd'.

func _on_Button_pressed():
    Global.goto_scene("res://Scene2.tscn")
// Add to 'Scene1.cs'.

public void OnButtonPressed()
{
    var global = (Global)GetNode("/root/Global");
    global.GotoScene("res://Scene2.tscn");
}

и

# Add to 'Scene2.gd'.

func _on_Button_pressed():
    Global.goto_scene("res://Scene1.tscn")
// Add to 'Scene2.cs'.

public void OnButtonPressed()
{
    var global = (Global)GetNode("/root/Global");
    global.GotoScene("res://Scene1.tscn");
}

Run the project and test that you can switch between scenes by pressing the button.

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