Recursos

Nodos y recursos

Hasta ahora, nos hemos centrado en la clase Node de Godot , ya que es la que usarás para programar comportamientos y de la que dependen la mayoría de las características del motor. Hay otro tipo de datos que es igualmente importante: Resource.

Los Nodes te dan funcionalidad: Dibujan sprites, modelos 3D, simulan físicas, organizan interfaces gráficas, etc. Los Resources son data containers. Ellos no hacen nada por su cuenta: en su lugar, los nodos usan los datos contenidos en los Resources.

Cualquier cosa que Godot grabe o cargue del disco es un Resource. Puede ser una escena (archivo .scn o .scn), una imagen, un script. Estos son unos ejemplos de Resource Texture, Script, Mesh, Animation, AudioStream, Font, Translation.

Cuando el motor carga un recurso desde el disco, siempre se carga una sola vez. Esto significa que, si hay una copia de ese recurso ya cargado en la memoria, intentar cargar el recurso de nuevo devolverá la misma copia una y otra vez. Esto se corresponde con el hecho de que los recursos son sólo contenedores de datos, por lo que no es necesario duplicarlos.

Cada objeto, sea Node o Resource, puede exportar propiedades. Existen muchos tipos de proiedades como un String, integer, Vector2, etc., y cualquiera de esos tipos puede ser un recurso. Esto significa que tanto los Node como los Resources pueden contener Resources como propiedades:

../../_images/nodes_resources.png

Externo vs built-in

Hay dos formas de grabar Resources. Estas pueden ser:

  1. External a una escena, guardada en el disco como archivos individuales.
  2. Built-in, guardado dentro del archivo *.tscn o *.scn al que están adjuntos.

Para ser más específico, aquí tenemos Texture en un nodo Sprite:

../../_images/spriteprop.png

Haz clic en la vista previa del recurso para ver y editar sus propiedades.

../../_images/resourcerobi.png

La propiedad «path» nos dice de donde proviene el recurso. En este caso, proviene de una imagen PNG llamada robi.png. Cuando el recurso proviene de un archivo como este, es un recurso externo. Si borra la ruta o esta ruta está vacía, se convierte en un recurso incorporado.

El cambio entre recursos incorporados y externos se produce al guardar la escena. En el ejemplo anterior, si borra la ruta `»res://robi.png»` y guardas, Godot guardará la imagen dentro del archivo de escena .tscn.

Nota

Incluso si guardas un recurso incorporado, cuando hagas múltiples instancias de una escena, el motor sólo cargará una copia del recurso.

Cargando recursos desde código

Hay dos maneras de cargar recursos desde el código. La primera es usando la función load() en cualquier momento:

func _ready():
        var res = load("res://robi.png") # Godot loads the Resource when it reads the line.
        get_node("sprite").texture = res
public override void _Ready()
{
    var texture = (Texture)GD.Load("res://robi.png"); // Godot loads the Resource when it reads the line.
    var sprite = (Sprite)GetNode("sprite");
    sprite.Texture = texture;
}

También puedes preload recursos. A diferencia de `load, esta función leerá el archivo desde el disco y lo cargará en tiempo de compilación. Como resultado, no se puede llamar a preload con una ruta variable: es necesario utilizar una cadena constante.

func _ready():
        var res = preload("res://robi.png") # Godot loads the resource at compile-time
        get_node("sprite").texture = res
// 'preload()' is unavailable in C Sharp.

Cargando escenas

Las escenas también son Resources, pero hay un detalle. Las escenas guardadas en el disco son Resources del tipo PackedScene. Esto significa que la escena está empaquetada dentro de un Resource.

Para obtener una instancia de la escena, se debe usar el método PackedScene.instance().

func _on_shoot():
        var bullet = preload("res://bullet.tscn").instance()
        add_child(bullet)
private PackedScene _bulletScene = (PackedScene)GD.Load("res://bullet.tscn");

public void OnShoot()
{
    Node bullet = _bulletScene.Instance();
    AddChild(bullet);
}

Este método crea nodos en la estructura de la escena, los configura (asigna las propiedades) y retorna el nodo raíz de la escena, el cual puede ser agregado como hijo a cualquier otro nodo.

Este enfoque tiene muchas ventajas. Como la función PackedScene.instance() es bastante rápida, se pueden crear nuevos enemigos, balas, efectos, etc. sin tener que cargarlos del disco continuamente. Es importante recordar que, como siempre, imágenes, mallas, etc. son compartidas entre las instancias de escenas.

Liberando recursos

Cuando un Resource no se encuentra más en uso, este se libera de memoria a si mismo automáticamente. Como en la mayoría de los casos los Resources están contenidos en Nodes, cuando liberas (queue_free) un Node, el motor también libera de memoria todos los recursos que posee si ningún otro objeto los utiliza.

Creando tus propios Resources

Como cualquier objeto en Godot, los usuarios también pueden programar scripts de Recursos. Los scripts de recursos heredan la habilidad de poder traducir libremente entre propiedades de objeto y texto o datos binarios serializados (/.tres, /.res). También heredan el manejo de memoria por conteo de referencias del tipo Referencia.

Esto trae muchas ventajas distintivas por sobre estructuras de datos alternativas, como JSON, CSV, o archivos TXT personalizados. Los usuarios sólo pueden importar estos recursos como un Dictionary (JSON) o como un File para analizar. Lo que separa a los Recursos son sus características de Object, Reference y Resource:

  • Pueden definir constantes, así que no se necesitan constantes de otros campos de datos u objetos.
  • Pueden definir métodos, incluidos los métodos de setter/getter de propiedades. Esto permite la abstracción y la encapsulación de los datos subyacentes. Si la estructura del script de recurso necesita cambiar, el juego que usa el recurso no necesita cambiar también.
  • Pueden definir señales, por lo que los recursos pueden desencadenar respuestas a los cambios en los datos que administran.
  • Tienen propiedades definidas, por lo que los usuarios saben al 100% que sus datos existirán.
  • La auto-serialización y deserialización de recursos es una característica incorporada del motor de Godot. Los usuarios no necesitan implementar una lógica personalizada para importar / exportar los datos de un archivo de recursos.
  • Los recursos pueden incluso serializar sub-recursos de forma recursiva, lo que significa que los usuarios pueden diseñar estructuras de datos aún más sofisticadas.
  • Los usuarios pueden guardar Recursos como archivos de texto amigables con el control de versiones (*.res). Al exportar un juego, Godot serializa los archivos de recursos como archivos binarios (*.res) para aumentar la velocidad y la compresión.
  • El inspector de Godot procesa y edita los archivos de recursos de forma inmediata. Como tal, los usuarios a menudo no necesitan implementar una lógica personalizada para ver o editar sus datos. Para hacerlo, haga doble clic en el archivo de recursos en el panel FileSystem o haga clic en el icono de carpeta en el Inspector y abra el archivo en el cuadro de diálogo.
  • Pueden extender ** otros ** tipos de recursos además del recurso base.

Advertencia

Tanto los Recursos como los Diccionarios se pasan por referencia, pero sólo los Recursos se cuentan por referencia. Esto significa que si se pasa un Diccionario entre objetos y se borra el primer objeto, las referencias de todos los demás objetos al diccionario se invalidarán. Por el contrario, los Recursos no serán liberados de la memoria hasta que todos los objetos sean borrados.

extends Node

class MyObject:
    extends Object
    var dict = {}

func _ready():
    var obj1 = MyObject.new()
    var obj2 = MyObject.new()
    obj1.dict.greeting = "hello"
    obj2.dict = obj1.dict             # 'obj2.dict' now references 'obj1's Dictionary.
    obj1.free()                       # 'obj1' is freed and the Dictionary too!
    print(obj2.dict.greeting)         # Error! 'greeting' index accessed on null instance!

    # To avoid this, we must manually duplicate the Dictionary.
    obj1 = MyObject.new()
    obj1.dict.greeting = "hello"
    obj2.dict = obj1.dict.duplicate() # Now we are passing a copy, not a reference.
    obj1.free()                       # obj2's Dictionary still exists.
    print(obj2.dict.greeting)         # Prints 'hello'.

Godot facilita la creación de recursos personalizados en el Inspector.

  1. Cree un objeto de recurso simple en el inspector. Este puede ser incluso un tipo que derive el recurso, siempre y cuando su script esté extendiendo ese tipo.
  2. Establece la propiedad script en el Inspector para que sea tu script.

El inspector ahora mostrará las propiedades personalizadas de su script de recursos. Si uno edita esos valores y guarda el recurso, ¡el Inspector serializa las propiedades personalizadas también! Para guardar un recurso del Inspector, haga clic en el menú de herramientas del Inspector (arriba a la derecha) y seleccione «Save» o «Save As…».

Si el lenguaje del script admite script classes, entonces se agiliza el proceso. Definir un nombre para su script solo lo agregará al cuadro de diálogo de creación del Inspector. Esto agregará automáticamente su script al objeto de recurso que cree.

Veamos algunos ejemplos.

# bot_stats.gd
extends Resource
export(int) var health
export(Resource) var sub_resource
export(Array, String) var strings

func _init(p_health = 0, p_sub_resource = null, p_strings = []):
    health = p_health
    sub_resource = p_sub_resource
    strings = p_strings

# bot.gd
extends KinematicBody

export(Resource) var stats

func _ready():
    # Uses an implicit, duck-typed interface for any 'health'-compatible resources.
    if stats:
        print(stats.health) # Prints '10'.
// BotStats.cs
using System;
using Godot;

namespace ExampleProject {
    public class BotStats : Resource
    {
        [Export]
        public int Health { get; set; }

        [Export]
        public Resource SubResource { get; set; }

        [Export]
        public String[] Strings { get; set; }

        public BotStats(int health = 0, Resource subResource = null, String[] strings = null)
        {
            Health = health;
            SubResource = subResource;
            Strings = strings ?? new String[0];
        }
    }
}

// Bot.cs
using System;
using Godot;

namespace ExampleProject {
    public class Bot : KinematicBody
    {
        [Export]
        public Resource Stats;

        public override void _Ready()
        {
            if (Stats != null && Stats is BotStats botStats) {
                GD.Print(botStats.Health); // Prints '10'.
            }
        }
    }
}

Nota

Los scripts de recursos son similares a los ScriptableObjects de Unity. El Inspector proporciona soporte integrado para recursos personalizados. Si así lo desean, los usuarios pueden incluso diseñar sus propios scripts de herramientas basadas en Control y combinarlos con un EditorPlugin para crear visualizaciones y editores personalizados para sus datos.

Las DataTables y CurveTables de Unreal Engine 4 también son fáciles de recrear con los scripts de recursos. Los DataTables son una cadena asignada a una estructura personalizada, similar a un diccionario que asigna una cadena a un script de recursos personalizado secundario.

# bot_stats_table.gd
extends Resource

const BotStats = preload("bot_stats.gd")

var data = {
    "GodotBot": BotStats.new(10), # Creates instance with 10 health.
    "DifferentBot": BotStats.new(20) # A different one with 20 health.
}

func _init():
    print(data)
using System;
using Godot;

public class BotStatsTable : Resource
{
    private Godot.Dictionary<String, BotStats> _stats = new Godot.Dictionary<String, BotStats>();

    public BotStatsTable()
    {
        _stats["GodotBot"] = new BotStats(10); // Creates instance with 10 health.
        _stats["DifferentBot"] = new BotStats(20); // A different one with 20 health.
        GD.Print(_stats);
    }
}

En lugar de simplemente alinear los valores del Diccionario, también se podría, alternativamente …

  1. Importar una tabla de valores de una hoja de cálculo y generar estos pares clave-valor, o …
  2. Diseñar una visualización en el editor y crear un simple complemento que la añada al inspector cuando se abra este tipo de recursos.

Las CurveTables son la misma cosa, excepto que se asignan a una matriz de valores flotantes o a un objeto de recurso Curve/Curve2D.

Advertencia

Tenga en cuenta que los archivos de recursos (*.tres/*.res) almacenarán la ruta del script que usan en el archivo. Cuando se carguen, obtendrán y cargarán este script como una extensión de su tipo. Esto significa que tratar de asignar una subclase, es decir, una clase interna de un script (como usar la palabra clave class en GDScript) no funcionará. Godot no serializará correctamente las propiedades personalizadas en la subclase del script.

En el siguiente ejemplo, Godot cargaría el script Node, vería que no extiende Resource, y luego determinaría que el script no se pudo cargar para el objeto Resource ya que los tipos son incompatibles.

extends Node

class MyResource:
    extends Resource
    export var value = 5

func _ready():
    var my_res = MyResource.new()

    # This will NOT serialize the 'value' property.
    ResourceSaver.save("res://my_res.tres", my_res)
using Godot;

public class MyNode : Node
{
    public class MyResource : Resource
    {
        [Export]
        public int Value { get; set; } = 5;
    }

    public override void _Ready()
    {
        var res = new MyResource();

        // This will NOT serialize the 'Value' property.
        ResourceSaver.Save("res://MyRes.tres", res);
    }
}