Notificações em Godot¶
Cada objeto no Godot implementa um método _notification. Sua finalidade é permitir que o Objeto responda a uma variedade de chamadas de retorno em nível de motor que possam estar relacionadas a ele. Por exemplo, se o motor diz a um CanvasItem para "desenhar", ele irá chamar _notification(NOTIFICATION_DRAW)
.
Algumas destas notificações, como desenhar, são úteis para sobrepor em scripts. Tanto que o Godot expões muitas delas com funções dedicadas:
_ready()
: NOTIFICATION_READY_enter_tree()
: NOTIFICATION_ENTER_TREE_exit_tree()
: NOTIFICATION_EXIT_TREE_process(delta)
: NOTIFICATION_PROCESS_physics_process(delta)
: NOTIFICATION_PHYSICS_PROCESS_draw()
: NOTIFICATION_DRAW
O que os usuários podem não perceber é que existem notificações para outros tipos além do Node sozinho:
Object::NOTIFICATION_POSTINITIALIZE: uma chamada de retorno que aciona durante a inicialização do objeto. Não acessível a scripts.
Object::NOTIFICATION_PREDELETE: uma chamada de retorno que aciona antes do motor deletar um Objeto, ou seja, um 'destruidor'.
MainLoop::NOTIFICATION_WM_MOUSE_ENTER: uma chamada de retorno que aciona quando o mouse entra na janela no sistema operacional que exibe o conteúdo do jogo.
E muitas das chamadas de retorno que existem em Nós não têm métodos dedicados mas ainda são bastante úteis.
Node::NOTIFICATION_PARENTED: uma chamada de retorno que aciona sempre que se adiciona um nó filho a outro nó.
Node::NOTIFICATION_UNPARENTED: uma chamada de retorno que aciona sempre que se remove um nó filho de outro nó.
Popup::NOTIFICATION_POST_POPUP: uma chamada de retorno que aciona após um nó Popup completar qualquer método
Popup*
. Observe a diferença do seu sinalabout_to_show
que aciona antes de sua aparência.
Pode-se acessar todas essas notificações personalizadas a partir do método universal _notification
.
Nota
Os métodos na documentação rotulados como "virtual" também são destinados a serem substituídos por scripts.
Um exemplo clássico é o método _init em Object. Embora ele não tenha uma NOTIFICAÇÃO_*
equivalente, o motor ainda chama o método. A maioria das linguagens (exceto C#) dependem dele como um construtor.
Então, em que situação se deve usar cada uma destas notificações ou funções virtuais?
_process vs. _physics_process vs. *_input¶
Use _process`
quando precisar de um deltatime dependente de framerate entre frames. Se o código que atualiza os dados do objeto precisa ser atualizado o mais frequentemente possível, este é o lugar certo. As verificações lógicas recorrentes e o armazenamento em cache de dados costumam ser executados aqui, mas se resume à frequência na qual se precisa das avaliações para atualizar. Se eles não precisarem executar cada frame, então implementar um loop Timer-yield-timeout é outra opção.
# Infinitely loop, but only execute whenever the Timer fires.
# Allows for recurring operations that don't trigger script logic
# every frame (or even every fixed frame).
while true:
my_method()
$Timer.start()
yield($Timer, "timeout")
Use _physics_process
quando precisar de um deltatime entre frames independente de framerate. Se o código precisa de atualizações consistentes ao longo do tempo, independentemente de quão rápido ou lento o tempo avança, este é o lugar certo. As operações recorrentes de cinemática e transformação de objetos devem ser executadas aqui.
Embora seja possível, para obter o melhor desempenho, deve-se evitar fazer verificações de entrada durante estas chamadas de retorno. _process
e _physics_process
acionarão em todas as oportunidades (eles não "descançam" por padrão). Em contraste, as chamadas de retorno *_input
acionarão apenas nos quadros nos quais o motor realmente detectou a entrada.
É possível verificar se há ações de entrada dentro das chamadas de retorno de entrada da mesma forma. Se alguém quiser usar o tempo delta, pode-se buscá-lo a partir dos métodos deltatime relacionados conforme necessário.
# Called every frame, even when the engine detects no input.
func _process(delta):
if Input.is_action_just_pressed("ui_select"):
print(delta)
# Called during every input event.
func _unhandled_input(event):
match event.get_class():
"InputEventKey":
if Input.is_action_just_pressed("ui_accept"):
print(get_process_delta_time())
public class MyNode : Node
{
// Called every frame, even when the engine detects no input.
public void _Process(float delta)
{
if (Input.IsActionJustPressed("ui_select"))
GD.Print(delta);
}
// Called during every input event. Equally true for _input().
public void _UnhandledInput(InputEvent event)
{
switch (event)
{
case InputEventKey keyEvent:
if (Input.IsActionJustPressed("ui_accept"))
GD.Print(GetProcessDeltaTime());
break;
default:
break;
}
}
}
_init vs. initialization vs. export¶
Se o script inicializa sua própria sub-árvore de nó, sem uma cena, esse código deve ser executado aqui. Outras propriedades ou inicializações independentes da SceneTree-independent também devem ser executadas aqui. Isto aciona antes de _ready
ou _enter_tree
, mas depois que um script cria e inicializa suas propriedades.
Scripts têm três tipos de atribuições de propriedade que podem ocorrer durante a instanciação:
# "one" is an "initialized value". These DO NOT trigger the setter.
# If someone set the value as "two" from the Inspector, this would be an
# "exported value". These DO trigger the setter.
export(String) var test = "one" setget set_test
func _init():
# "three" is an "init assignment value".
# These DO NOT trigger the setter, but...
test = "three"
# These DO trigger the setter. Note the `self` prefix.
self.test = "three"
func set_test(value):
test = value
print("Setting: ", test)
public class MyNode : Node
{
private string _test = "one";
// Changing the value from the inspector does trigger the setter in C#.
[Export]
public string Test
{
get { return _test; }
set
{
_test = value;
GD.Print("Setting: " + _test);
}
}
public MyNode()
{
// Triggers the setter as well
Test = "three";
}
}
Ao instanciar uma cena, os valores das propriedades serão configurados de acordo com a seguinte sequência:
Atribuição de valor inicial: a instanciação atribuirá o valor de inicialização ou o valor de atribuição inicial. As atribuições de inicialização têm prioridade sobre os valores de inicialização.
Atribuição de valor exportado: Se criar uma instância de uma cena em vez de um script, o Godot atribuirá o valor exportado para substituir o valor inicial definido no script.
Como resultado, instanciar um script versus uma cena afetará tanto a inicialização como o número de vezes que o mecanismo chama o setter.
_ready vs. _enter_tree vs. NOTIFICATION_PARENTED¶
Ao instanciar uma cena conectada à primeira cena executada, o Godot irá instanciar nós abaixo da árvore (fazendo chamadas _init
) e construir a árvore descendo a partir da raiz. Isto faz com que as chamadas _enter_tree
desçam em cascata pela árvore. Quando a árvore estiver completa, os nós da folha chamam _ready
. Um nó chamará este método assim que todos os nós filhos tenham terminado de chamar o seu. Isto então causa uma cascata inversa que volta à raiz da árvore.
Ao instanciar um script ou uma cena isolada, nós não são adicionados à SceneTree após a criação, então nenhuma chamada de retorno _enter_tree
aciona. Em vez disso, apenas as chamadas _init
e depois _ready
ocorrem.
Se for necessário disparar o comportamento que ocorre como nós pais de outro, independentemente de ocorrer como parte da cena principal/ativa ou não, pode-se usar a notificação PARENTED. Por exemplo, aqui está um trecho que conecta o método de um nó a um sinal personalizado no nó pai sem falhar. Útil em nós centrados em dados que podem ser criados durante a execução.
extends Node
var parent_cache
func connection_check():
return parent.has_user_signal("interacted_with")
func _notification(what):
match what:
NOTIFICATION_PARENTED:
parent_cache = get_parent()
if connection_check():
parent_cache.connect("interacted_with", self, "_on_parent_interacted_with")
NOTIFICATION_UNPARENTED:
if connection_check():
parent_cache.disconnect("interacted_with", self, "_on_parent_interacted_with")
func _on_parent_interacted_with():
print("I'm reacting to my parent's interaction!")
public class MyNode : Node
{
public Node ParentCache = null;
public void ConnectionCheck()
{
return ParentCache.HasUserSignal("InteractedWith");
}
public void _Notification(int what)
{
switch (what)
{
case NOTIFICATION_PARENTED:
ParentCache = GetParent();
if (ConnectionCheck())
ParentCache.Connect("InteractedWith", this, "OnParentInteractedWith");
break;
case NOTIFICATION_UNPARENTED:
if (ConnectionCheck())
ParentCache.Disconnect("InteractedWith", this, "OnParentInteractedWith");
break;
}
}
public void OnParentInteractedWith()
{
GD.Print("I'm reacting to my parent's interaction!");
}
}