Singletons (Carregamento Automático)

Introdução

O sistema de cenas de Godot, embora poderoso e flexível, tem uma desvantagem: não existe um método para armazenar informações (por exemplo, pontuação ou inventário de um jogador) que seja necessário em mais de uma cena.

É possível resolver isso com algumas soluções alternativas, mas elas vêm com suas próprias limitações:

  • Embora seja possível que uma cena carregue e descarregue outras cenas como seus filhos para armazenar informações comuns a essas cenas filhas, não será mais possível executar essas cenas sozinhas e esperar que elas funcionem corretamente.
  • Embora a informação possa ser armazenada no disco em ``user: // `` e esta informação possa ser carregada por cenas que a exijam, salvar e carregar continuamente esses dados ao alterar cenas é trabalhoso e pode ser lento.

The Singleton Pattern is a useful tool for solving the common use case where you need to store persistent information between scenes. In our case it is possible re-use the same scene or class for multiple singletons, so long as they have different names.

Usando esse conceito, você pode criar objetos que:

  • São sempre carregados, não importa qual cena seja aberta no editor
  • Pode armazenar variáveis globais, como informações do jogador, itens, dinheiro etc. e compartilhar informações entre cenas
  • Pode lidar com troca de cenas e transições entre cenas
  • Atua como um singleton, já que o GDScript não suporta variáveis globais por projeto

Autoloading nodes and scripts can give us these characteristics.

AutoLoad

Você pode usar o AutoLoad para carregar uma cena ou um script que herda da Classe Node. Nota: ao carregar automaticamente um script, um nó será criado e o script pode referenciá-lo. Este nó será adicionado à viewport raiz antes que qualquer outra cena seja carregada.

../../_images/singleton.png

Para carregar automaticamente uma cena ou script, selecione no menu Projeto -> Configurações do Projeto e mude para a aba “AutoLoad”.

../../_images/autoload_tab.png

Here you can add any number of scenes or scripts. Each entry in the list requires a name, which is assigned as the node’s name property. The order of the entries as they are added to the global scene tree can be manipulated using the up/down arrow keys.

../../_images/autoload_example.png

Isso significa que qualquer nó pode acessar um singleton chamado “PlayerVariables” com:

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

Se a coluna “Enable” estiver marcada (default true), então o singleton pode simplesmente ser acessado diretamente:

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

Note that autoload objects (scripts and/or scenes) are accessed just like any other node in the scene tree. In fact, if you look at the running scene tree, you’ll see the autoloaded nodes appear:

../../_images/autoload_runtime.png

Comutador de cena personalizado

This tutorial will demonstrate building a scene switcher using autoload. For basic scene switching, you can use the SceneTree.change_scene() method (see Árvore de cena for details). However, if you need more complex behavior when changing scenes, this method provides more functionality.

To begin, download the template from here: autoload.zip and open it in Godot.

O projeto contém duas cenas: Scene1.tscn e Scene2.tscn. Cada cena contém um Label exibindo o nome da cena e um botão com seu sinal pressed() conectado. Quando você executar o projeto, ele começará em Scene1.tscn. No entanto, ao pressionar o botão, nada acontecerá.

Global.gd

Mude para a aba “Script” e crie um novo script chamado Global.gd. Tenha certeza que ele herda de :

../../_images/autoload_script.png

The next step is to add this script to the autoLoad list. Open Project > Project Settings from the menu, switch to the “AutoLoad” tab and select the script by clicking the .. button or typing its path: res://Global.gd. Press “Add” to add it to the autoload list:

../../_images/autoload_tutorial1.png

Now whenever we run any scene in the project, this script will always be loaded.

Returning to the script, it needs to fetch the current scene in the _ready() function. Both the current scene (the one with the button) and global.gd are children of root, but autoloaded nodes are always first. This means that the last child of root is always the loaded scene.

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);
    }
}

Now we need a function for changing the scene. This function needs to free the current scene and replace it with the requested one.

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);
}

Using Object.call_deferred(), the second function will only run once all code from the current scene has completed. Thus, the current scene will not be removed while it is still being used (i.e. its code is still running).

Finally, we need to fill the empty callback functions in the two scenes:

# 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");
}

e

# 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.

Nota: Quando cenas são pequenas, a transição será instantânea. No entanto, se as cenas são mais complexas, elas levarão um tempo significativo para aparecer. Para aprender a gerenciar isto, veja o próximo tutorial: Background loading