Scripting (continuación)

Procesamiento

Varias acciones en Godot se activan por llamadas de retorno o funciones virtuales, así que no hay necesidad de escribir código que se ejecute todo el tiempo.

Sin embargo, todavía es común necesitar un script para que se procese en cada frame. Hay dos tipos de procesamiento: procesamiento en tiempo de espera y procesamiento físico.

El procesamiento en tiempo de espera (idle) se activa cuando el método Node._process() se encuentra en un script. Se puede apagar y encender con la función Node.set_process().

Este método se llamará cada vez que se dibuje un frame:

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

Es importante tener en cuenta que la frecuencia con la que se llamará a _process() depende del número de cuadros por segundo (FPS) a los que se esté ejecutando la aplicación. Esta frecuencia puede variar con el tiempo y los dispositivos.

Para ayudar a manejar esta variabilidad el parámetro delta contiene el tiempo transcurrido en segundos, como punto flotante, desde la llamada anterior a _process().

Este parámetro se puede utilizar para asegurarse de que las cosas siempre tardan la misma cantidad de tiempo, independientemente de los FPS del juego.

Por ejemplo, el movimiento a menudo se multiplica por un tiempo delta para que la velocidad de movimiento sea constante e independiente de la velocidad de fotogramas.

El procesamiento de la física con _physics_process() es similar, pero se debería usar para procesos que deben ocurrir antes de cada paso de la física, como el control de un personaje. Siempre se ejecuta antes de un paso de física y se llama en intervalos de tiempo fijos: 60 veces por segundo por defecto. Puedes cambiar el intervalo desde la Configuración del proyecto, en Physics -> Common -> Physics Fps.

La función _process(), sin embargo, no está sincronizada con la física. Su frecuencia de imagen no es constante y depende de la optimización del hardware y del juego. Su ejecución se realiza después del paso de la física en los juegos de un solo subproceso.

Una forma sencilla de ver la función _process() en funcionamiento es crear una escena con un único nodo Label, con el siguiente script:

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

Lo que mostrará un contador aumentando cada fotograma.

Grupos

Los grupos en Godot trabajan como las etiquetas que puedes encontrar en otro software. Se puede añadir un nodo a tantos grupos como se desees. Esta es una función útil para organizar escenas de gran tamaño. Hay dos maneras de añadir nodos a los grupos. La primera es desde la UI, usando el botón Grupos bajo el panel de Nodos:

../../_images/groups_in_nodes.png

Y el segundo modo es desde código. El siguiente script agregará el nodo actual al grupo enemies ni bien aparece en el árbol de escenas.

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

    AddToGroup("enemies");
}

De esta manera, si se descubre al jugador entrando a escondidas en una base secreta, se puede avisar a todos los enemigos de que está sonando la alarma 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");
}

El código anterior llama a la función player_was_discovered en cada miembro del grupo enemies.

También es posible obtener la lista completa de nodos enemies llamando a SceneTree.get_nodes_in_group():

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

La clase SceneTree proporciona muchos métodos útiles, como la interacción con escenas, su jerarquía de nodos y grupos de nodos. Te permite cambiar escenas o recargarlas fácilmente, salir del juego o pausar y despausar el juego. Incluso viene con señales interesantes !Compruébalo si tienes tiempo!

Notificaciones

Godot tiene un sistema de notificaciones. Por lo general, no son necesarios para la creación de scripts, ya que es de muy bajo nivel y se proporcionan funciones virtuales para la mayoría de estas. Pero es bueno saber que existen. Por ejemplo, puedes añadir una función Object._notification() en tu script:

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

La documentación de cada clase en la Referencia de Clases muestra las notificaciones que puedes recibir. Sin embargo, en la mayoría de los casos, GDScript proporciona funciones anulables más simples.

Funciones anulables

Estas funciones, que se describen a continuación, se pueden aplicar a los nodos:

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 se mencionó anteriormente, es mejor utilizar estas funciones que el sistema de notificación.

Creación de nodos

Para crear un nodo desde código, se debe llamar al método .new(), como en cualquier otro tipo de dato basado en una clase. Por ejemplo:

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 eliminar un nodo, ya sea dentro o fuera de la escena, se debe 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.
}

Cuando se elimina un nodo, también se eliminan todos los nodos hijos. Debido a esto, eliminar manualmente los nodos es mucho más sencillo de lo que parece. Elimina el nodo base y todo lo demás en el subárbol desaparecerá con él.

Puede ocurrir una situación en la que queramos borrar un nodo que esté actualmente «bloqueado», porque esté emitiendo una señal o llamando a una función. Esto interrumpirá el juego. Ejecutar Godot con el depurador a menudo detectará este caso y te advertirá al respecto.

La forma más segura de eliminar un nodo es usando Node.queue_free(). Esto borra el nodo de forma segura mientras está inactivo.

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

Instanciación de escenas

La instanciación de una escena a partir de código se realiza en dos pasos. El primero es cargar la escena desde el disco duro:

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.

La precarga puede ser más conveniente, ya que ocurre en tiempo de análisis (sólo GDScript):

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

Pero scene todavía no es un nodo. Está empaquetado en un recurso especial llamado PackedScene. Para crear el nodo se debe llamar a la función PackedScene.instance(). Esto retornará el árbol de nodos que se puede añadir a la escena activa:

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

La ventaja de este proceso de dos pasos es que una escena empaquetada se puede mantener cargada y lista para usar para que puedas crear tantas instancias como desees. Esto es especialmente útil para identificar rápidamente varios enemigos, balas y otras entidades en la escena activa.

Registrar scripts como clases

Godot tiene una funcionalidad de «Script Class» para registrar codigos individuales con el Editor. Por defecto, solo puedes accesar codigos sin nombre cargando el archivo directamente.

Puedes nombrar un script y registrarlo como un tipo en el editor con la palabra clave class_name seguida por el nombre de la clase. Puedes agregar una coma y una dirección opcional a una imagen para usarla como ícono. De esta forma encontrarás un nuevo tipo en la ventana Node o Resource creation.

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

Advertencia

En Godot 3.1:

  • Únicamente GDScript o NativeScript, en otras palabras, C++ y otros lenguajes basandos en GDNative, pueden registrar scripts.
  • Únicamente GDScript crea variables globales para cada script nombrado.