Cuándo usar escenas y cuándo scripts

Ya hemos cubierto la diferencia entre escenas y scripts. Los scripts definen una extensión de clases del motor con código imperativo, las escenas lo hacen con código declarativo.

Como resultado, las capacidades de cada sistema son diferentes. Las escenas pueden definir cómo se inicializa una clase extendida, pero no cuál es su comportamiento. Las escenas también suelen ser usadas en conjunto con scripts, así que las escenas actúan como extensión del código declarativo del script.

Tipos anónimos

Es posible definir escenas completamente usando sólo scripts. Esto es en escencia lo que hace el editor de Godot, sólo que lo hace en el constructor C++ de sus objetos.

El dilema puede surgir al tener que elegir cuál usar. Crear instancias de un script es idéntico a crear clases en el motor, por lo que manipular escenas requiere un cambio en el API:

const MyNode = preload("my_node.gd")
const MyScene = preload("my_scene.tscn")
var node = Node.new()
var my_node = MyNode.new() # Same method call
var my_scene = MyScene.instance() # Different method call
var my_inherited_scene = MyScene.instance(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene
using System;
using Godot;

public class Game : Node
{
    public readonly Script MyNodeScr = (Script)ResourceLoader.Load("MyNode.cs");
    public readonly PackedScene MySceneScn = (PackedScene)ResourceLoader.load("MyScene.tscn");
    public Node ANode;
    public Node MyNode;
    public Node MyScene;
    public Node MyInheritedScene;

    public Game()
    {
        ANode = new Node();
        MyNode = new MyNode(); // Same syntax
        MyScene = MySceneScn.Instance(); // Different. Instantiated from a PackedScene
        MyInheritedScene = MySceneScn.Instance(PackedScene.GenEditState.Main); // Create scene inheriting from MyScene
    }
}

Además, los scripts funcionan un poco más lentos que las escenas debido a la diferencia de velocidad entre el motor y el código del script. Mientras más grande sea, más complejo es el nodo, lo que nos da una mayor razón para construirlo como escena.

Tipos con nombre

En algunos casos, un usuario puede registrar un script como un nuevo tipo de nodo en sí mismo. Esto muestra el tipo nuevo de nodo o recurso en el diálogo de creación con un icono opcional. En esos casos, la habilidad del usuario para usar scripts es mucho más simplificada. En lugar de tener que…

  1. Conocer el tipo base del script que quieran usar.
  2. Crear una instancia de un tipo base.
  3. Agrega el script al nodo.
    1. (Método de arrastrar y soltar)
      1. Encuentra el script en el panel de Sistema de Archivos.
      2. Arrastre el script hasta el nodo en el panel de escenas.
    2. (Método de propiedad)
      1. Baja al final del inspector para encontrar la propiedad script y selecciónala.
      2. Selecciona Cargar de la lista desplegable.
      3. Selecciona el script en el diálogo de selección de archivo.

Con un script registrado, el tipo del script se convierte en una opción de creación como los demás Node y Resource en el sistema. No es necesario hacer nada de lo mostrado anteriormente, el diálogo de creación además tiene una barra de búsqueda donde se puede buscar el tipo por nombre.

Existen dos modos de registrar tipos…

  • Tipos Personalizados

    • Solo para el editor. Los tipos con nombre no son accesibles en tiempo de ejecución.
    • No soporta tipos personalizados heredados.
    • Una herramienta inicializadora. Crea el nodo con el script, nada más.
    • El editor no está pendiente del tipo del script o su relación con otros tipos del engine o scripts.
    • Permite al usuario definir un icono.
    • Funciona para todos los lenguajes de scripting porque funciona con los recursos Script de manera abstracta.
    • Se configura usando EditorPlugin.add_custom_type.
  • Script Classes

    • Accesible en el editor y en tiempo de ejecución.
    • Muestra las relaciones de herencia de manera completa.
    • Crea el nodo con el script, pero también cambia el tipo o extiende el tipo desde el editor.
    • El editor está pendiente de la relación de herencia entre scripts, clases de script y clases C++ del motor.
    • Permite al usuario definir un icono.
    • Los desarrolladores que trabajan sobre el motor deben agregar soporta para lenguajes manualmente (tanto la exposición del nombre como la accesibilidad en tiempo de ejecución).
    • Sólo en Godot 3.1+.
    • El editor escanea las carpetas del proyecto y registra cualquier nombre expuesto para todos los lenguajes de scripting. Cada lenguaje de scripting debe incorporar su propio soporte para exponer esta información.

Ambas metodologías agregan nombres al diálogo de creación, pero para clases de script en particular, también permite a los usuarios acceder por el nombre del tipo sin necesidad de cargar el recurso de script. La creación de instancias y acceso a constantes o métodos estáticos es viable desde cualquier parte.

Con características como esta, puede preferirse que un tipo sea un script sin una escena debido a la facilidad de uso que le da a los usuarios. Quienes estén desarrollando plugins o creando herramientas propias para diseñadores, encontrarán mucho más sencillo hacer las cosas de este modo.

Un aspecto negativo de esto es que implica tener que usar mucha programación imperativa.

Conclusión

El mejor enfoque consiste en considerar lo siguiente:

  • Si se desea crear una herramienta básica que será reutilizada en distintos proyectos y donde gente de todo tipo de nivel la usará (incluyendo aquellos que no se llaman a si mismos «programadores»), entonces hay chances de que deba ser un script, probablemente uno con un nombre/icono personalizado.

  • Si se desea crear un concepto que es particular para el juego, entonces debe ser una escena. Las escenas son más fáciles de revisar/editar y proveen más seguridad que los scripts.

  • Si uno desea darle un nombre a una escena, también se puede hacer en 3.1 declarando una clase script y dándole la escena como constante, de este modo el script se vuelve un espacio de nombres:

    # game.gd
    extends Reference
    class_name Game # extends Reference, so it won't show up in the node creation dialog
    const MyScene = preload("my_scene.tscn")
    
    # main.gd
    extends Node
    func _ready():
        add_child(Game.MyScene.instance())