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.
O padrão Singleton é uma ferramenta útil para se usar quando é necessário armazenar informações entre cenas. No nosso caso é possível reusar a mesma cena ou classe para múltiplos singletons, contanto que eles tenham diferentes nomes.
Usando esse conceito, você pode criar objetos que:
São sempre carregados, independentemente da cena em execução.
Pode armazenar variáveis globais, como informações do jogador.
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.
Carregamento automático de nós e scripts atendem a essa necessidade.
Nota
Godot não tornará AutoLoad um singleton "true" de acordo com o design pattern. Ele ainda pode ser instanciado mais de uma vez pelo usuário, se desejado.
AutoLoad¶
Você pode criar um Autoload para carregar a cena ou um script que herda Node.
Nota
Ao carregar automaticamente um script, um: ref: class_Node será criado e o script será anexado a ele. Este nó será adicionado à viewport raiz antes que qualquer outra cena seja carregada.

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

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. Like regular scenes, the engine will read these nodes
in top-to-bottom order.

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 = GetNode<PlayerVariables>("/root/PlayerVariables");
playerVariables.Health -= 10; // Instance field.
Se a coluna "Enable" estiver marcada (que é o padrão), então o singleton pode ser acessado diretamente sem a necessidade de get_node()
:
PlayerVariables.health -= 10
// Static members can be accessed by using the class name.
PlayerVariables.Health -= 10;
Note que objetos carregados automaticamente (scripts e/ou cenas) são acessadas como qualquer outro nó na árvore de cenas. Se você olhar numa cena executando, você verá os nós carregados automaticamente aparecerem:

Comutador de cena personalizado¶
Este tutorial explicará como fazer um trocador de cena usando o autoload. Para troca de cena simples, o método SceneTree.change_scene() é suficiente (descrito em Usando Árvore de cena). Mas se você precisar de um comportamento mais complexo ao mudar de cenas, esse método fornece mais funcionalidades.
Primeiro faça o download do modelo aqui: autoload.zip
e abra-o com 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 Node
:

O próximo passo é adicionar este script à lista de autoLoad. Clique em Projeto > Configurações de Projeto no menu, mude para a aba "Carregamento Automático" (AutoLoad) e selecione o seu script clicando no botão de procurar ou digitando seu caminho: res://Global.gd
. Clique em "Adicionar" para adicioná-lo à lista de autoload:

Agora, todas as vezes que você executar qualquer uma das suas cenas, o script sempre será carregado.
Voltando ao nosso script, a cena atual precisa ser usada na função _ready(). Tanto a cena atual (aquela com o botão) quanto Global.gd
são filhos do nó raiz, mas os nós carregados automaticamente são sempre os primeiros. Isto significa que o último filho da raiz é sempre a cena carregada.
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);
}
}
Em seguida vem a função para trocar de cena. Esta função libera a cena atual e a substitui pela cena requisitada.
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);
}
Usando Object.call_deferred(), a segunda função irá somente rodar uma vez que todo o código dessa cena has completed. Thus, the current scene will not be removed while it is still being used (i.e. its code is still running).
Finalmente, tudo que resta é completar as funções vazias nas duas cenas:
# Add to 'Scene1.gd'.
func _on_Button_pressed():
Global.goto_scene("res://Scene2.tscn")
// Add to 'Scene1.cs'.
public void OnButtonPressed()
{
var global = GetNode<Global>("/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 = GetNode<Global>("/root/Global");
global.GotoScene("res://Scene1.tscn");
}
Rode o projeto e veja se você consegue trocar de cena apertando o botão.
Nota
Quando as cenas são pequenas, a transição é instantânea. No entanto, se suas cenas forem mais complexas, elas podem demorar um pouco para aparecer. Para aprender como lidar com isso, veja o próximo tutorial: Carregamento em segundo plano.
Alternativamente, se o tempo de carregamento é relativamente curto (menos que 3 segundos ou por aí), você pode exibir uma "placa de carregamento" mostrando algum tipo de elemento 2D logo antes de trocar a cena. Você pode, então, escondê-lo logo após a cena ser trocada. Isto pode ser usado para indicar ao jogador que uma cena está sendo carregada.