Roteirizando (continuação)

Processamentos

Muitas ações no Godot são disparadas por funções virtuais ou de retorno, então não há necessidade de escrever um código que execute o tempo todo.

Entretanto, ainda é comum precisar que um roteiro seja processado a cada quadro. Existem dois tipos de processamento: o ocioso e o físico.

Processamento ocioso é ativado quando o método Node._process() é encontrado em um roteiro. Ele pode ser desativado e reativado com a função Node.set_process().

Esse método será chamado toda vez que um quadro é desenhado:

func _process(delta):
    # Do something...
    pass
public override void _Process(float delta)
{
    // Do something...
}

É importante ter em mente que a frequência em que a função _process() será chamada depende de quantos quadros por segundo (FPS) sua aplicação está rodando. Essa taxa pode variar com o tempo e dispositivos.

Para ajudar a gerenciar essa variabilidade o parâmetro delta contém o tempo decorrido em segundos, sendo um número decimal, desde a chamada anterior de _process().

Este parâmetro pode ser usado para garantir que as coisas sempre levem a mesma quantidade de tempo, independentemente do taxa de quadros (FPS) do jogo.

Por exemplo, é frequente multiplicar o movimento pelo delta para tornar a velocidade de movimento constante independente da taxa de quadros.

Processamento físico com _physics_process() funciona similar, mas deveria ser usado para processo que tenham que acontecer antes de cada passo da física, tais como controlar um personagem. Ele sempre é executado antes de um passo da física e é chamado a intervalos fixos de tempo: 60 vezes por segundo por padrão. Você pode alterar o intervalo nas Configurações do Projeto, em Physics -> Common -> Physics Fps (Física -> Comum -> Fps Física).

A função _process(), contudo, não é sincronizado com a física. Sua taxa de quadros não é constante e depende do hardware e da otimização do jogo. Sua execução é feita depois do passo da física em jogos de thread única.

Uma maneira simples de ver a função _process() funcionando é criar uma cena com um único nó Label, com o seguinte roteiro:

extends Label

var accum = 0

func _process(delta):
    accum += delta
    text = str(accum) # 'text' is a built-in label property.
public class CustomLabel : Label
{
    private float _accum;

    public override void _Process(float delta)
    {
        _accum += delta;
        Text = _accum.ToString(); // 'Text' is a built-in label property.
    }
}

Que vai mostrar um contador aumentando a cada quadro.

Grupos

Grupos no Godot funcionam como tags que você pode encontrar em outros softwares. ‘Nós’, podem ser adicionados a grupos, a quantidade que desejar por nó, e isso é uma funcionalidade útil para organizar cenas grandes. Há duas formas de fazer isso. A primeira é pela interface gráfica, a partir do botão Grupos dentro do painel Nó:

../../_images/groups_in_nodes.png

E a segunda maneira é por código. O sequinte script adicionará o nó atual ao grupo enemies assim que aparecesse na árvore de cena.

func _ready():
    add_to_group("enemies")
public override void _Ready()
{
    base._Ready();

    AddToGroup("enemies");
}

Desta maneira, se o jogador é descoberto se esgueirando pela base secreta, todos os inimigos podem ser notificados por alarme sonoro usando SceneTree.call_group():

func _on_discovered(): # This is a purely illustrative function.
    get_tree().call_group("enemies", "player_was_discovered")
public void _OnDiscovered() // This is a purely illustrative function.
{
    GetTree().CallGroup("enemies", "player_was_discovered");
}

O código acima chama a função player_was_discovered (“jogador_foi_descoberto”) de cada membro do grupo enemies` (“inimigos”).

É também possível conseguir a lista completa de nós inimigos chamando SceneTree.get_nodes_in_group():

var enemies = get_tree().get_nodes_in_group("enemies")
var enemies = GetTree().GetNodesInGroup("enemies");

A classe SceneTree fornece muitos métodos úteis, como interagir com cenas, suas hierarquias e grupos de nós. Ela lhe permite facilmente trocar ou recarregar cenas, sair do jogo ou pausa-lo ou retomá-lo. Ela até vem com sinais interessantes. Então dê uma olhada nela se tiver um tempo!

Notificações

O Godot tem um sistema de notificações. Elas não costumam ser necessárias para roteirizar, já que são programação de baixo nível e se provê funções virtuais para a maioria delas. Mas é bom saber que elas existem. Por exemplo, você pode adicionar uma função Object._notification() ao seu roteiro:

func _notification(what):
    match what:
        NOTIFICATION_READY:
            print("This is the same as overriding _ready()...")
        NOTIFICATION_PROCESS:
            print("This is the same as overriding _process()...")
public override void _Notification(int what)
{
    base._Notification(what);

    switch (what)
    {
        case NotificationReady:
            GD.Print("This is the same as overriding _Ready()...");
            break;
        case NotificationProcess:
            var delta = GetProcessDeltaTime();
            GD.Print("This is the same as overriding _Process()...");
            break;
    }
}

A documentação de cada classe na Referência das Classes mostra as notificações que ela pode receber. No entanto, na maior parte dos casos, a GDScript fornece funções substituíveis mais simples.

Funções substituíveis

Tais funções substituíveis, como descritas no código, podem ser aplicadas a nós:

func _enter_tree():
    # When the node enters the Scene Tree, it becomes active
    # and  this function is called. Children nodes have not entered
    # the active scene yet. In general, it's better to use _ready()
    # for most cases.
    pass

func _ready():
    # This function is called after _enter_tree, but it ensures
    # that all children nodes have also entered the Scene Tree,
    # and became active.
    pass

func _exit_tree():
    # When the node exits the Scene Tree, this function is called.
    # Children nodes have all exited the Scene Tree at this point
    # and all became inactive.
    pass

func _process(delta):
    # This function is called every frame.
    pass

func _physics_process(delta):
    # This is called every physics frame.
    pass
public override void _EnterTree()
{
    // When the node enters the Scene Tree, it becomes active
    // and  this function is called. Children nodes have not entered
    // the active scene yet. In general, it's better to use _ready()
    // for most cases.
    base._EnterTree();
}

public override void _Ready()
{
    // This function is called after _enter_tree, but it ensures
    // that all children nodes have also entered the Scene Tree,
    // and became active.
    base._Ready();
}

public override void _ExitTree()
{
    // When the node exits the Scene Tree, this function is called.
    // Children nodes have all exited the Scene Tree at this point
    // and all became inactive.
    base._ExitTree();
}

public override void _Process(float delta)
{
    // This function is called every frame.
    base._Process(delta);
}

public override void _PhysicsProcess(float delta)
{
    // This is called every physics frame.
    base._PhysicsProcess(delta);
}

Como já mencionado, é melhor usar essas funções ao invés do sistema de notificações.

Criando nós

Para criar um nó a partir do código, chame o método .new(), assim como faria para qualquer tipo de dado baseado em classe. Por exemplo:

var s
func _ready():
    s = Sprite.new() # Create a new sprite!
    add_child(s) # Add it as a child of this node.
private Sprite _sprite;

public override void _Ready()
{
    base._Ready();

    _sprite = new Sprite(); // Create a new sprite!
    AddChild(_sprite); // Add it as a child of this node.
}

Para excluir um nó, esteja ele dentro ou fora da cena, deve-se usar free():

func _someaction():
    s.free() # Immediately removes the node from the scene and frees it.
public void _SomeAction()
{
    _sprite.Free(); // Immediately removes the node from the scene and frees it.
}

Quando um nó é liberado da memória, ele também libera todos os seus nós filhos. Por isso, excluir nós manualmente é mais simples do que parece. Libere o nó base e todo o resto vai embora junto com ele.

Uma situação pode acontecer quando queremos excluir um nó que está “bloqueado” atualmente, por estar emitindo um sinal ou chamando uma função. Isso quebra o jogo. Executar o Godot com o depurador irá pegar esse caso frequentemente e lhe alertar sobre isso.

A forma mais segura de excluir um nó é usando Node.queue_free(). Isso apaga o nó com segurança durante o tempo ocioso.

func _someaction():
    s.queue_free() # Removes the node from the scene and frees it when it becomes safe to do so.
public void _SomeAction()
{
    _sprite.QueueFree(); // Removes the node from the scene and frees it when it becomes safe to do so.
}

Criando instâncias de cenas

Criar uma instância de cena a partir do código é feito em duas estapas. A primeira é carregar a cena do seu disco rígido:

var scene = load("res://myscene.tscn") # Will load when the script is instanced.
var scene = GD.Load<PackedScene>("res://myscene.tscn"); // Will load when the script is instanced.

Pré-carregamento pode ser mais conveniente, já que acontece durante o tempo de análise (apenas para GDScript):

var scene = preload("res://myscene.tscn") # Will load when parsing the script.

Mas cena ainda não é um nó. Ela está empacotada em um recurso especial chamado PackedScene. Para criar um nó de fato, é preciso chamar a função PackedScene.instance(). Ela vai retornar a árvore de nós que pode ser adicionada à cena ativa:

var node = scene.instance()
add_child(node)
var node = scene.Instance();
AddChild(node);

A vantagem deste processo de duas etapas é que uma cena empacotada pode ser mantida carregada e pronta para uso, de forma que você possa gerar quantas instâncias desejar. É útil especialmente para, rapidamente, criar instâncias de vários inimigos, balas e outras entidades na cena ativa.

Registre scripts como classes

Godot tem uma função “Classe Script” para registrar scripts individuais com o editor. Por padrão, você só pode acessar scripts sem nome ao carregar o arquivo diretamente.

Você pode nomear um script e o registrar como um tipo no editor com a palavra chave class_name seguido pelo nome da classe. Você pode adicionar uma vírgula e um caminho opcional a uma imagem para usar como ícone. Então, você encontrará seu novo tipo na janela de criação de Nodes e recursos.

extends Node

# Declare the class name here
class_name ScriptName, "res://path/to/optional/icon.svg"

func _ready():
    var this = ScriptName           # reference to the script
    var cppNode = MyCppNode.new()   # new instance of a class named MyCppNode

    cppNode.queue_free()
../../_images/script_class_nativescript_example.png

Aviso

Em Godot 3.1:

  • Apenas GDScript e NativeScript (ex: C++) e outras linguagens baseadas em GDNative, podem registrar scripts.
  • Apenas GDScript pode criar variáveis globais para cada script nomeado.