Autoloads y nodos internos

Otros motores suelen incentivar el uso de crear clases «manager» para organizar las funcionalidades en una entidad accesible globalmente. Godot soporta modos que permiten reducir el tamaño y cantidad de dichos objetos. En su lugar divide el contenido en nodos individuales cuanto sea posible.

Por ejemplo, ¿qué puede hacer un desarrollador está creando un juego de plataformas y quiere que al recolectar monedas se reproduzca un efecto de sonido? Existe un nodo para hacer eso, AudioStreamPlayer. Pero notará durante las pryebas que si reproduce un «AudioStreamPlayer» mientras está ejecutando un sonido, el nuevo sonido interrumpirá el que se está reproduciendo, terminándolo antes de que se pueda reproducir.

Los usuarios tienden a creer que la mejor solución es crear un sistema más práctico haciendo un nodo SoundManager (administrador de sonidos) en modo autoload. Este genera un pool de AudioStreamPlayers que administra cada requerimiento de un nuevo efecto de sonido. Entonces hacen de este SoundManager un autoload para que pueda accederse desde cualquier parte con como SFX.play(«coin_pickup.ogg»). Poco saben que han agregado mucha complejidad a su código.

  • estado global: Un objeto es ahora responsable de todos los datos del objeto. Si SFX tiene errores o no tiene un AudioStreamPlayer disponible, todo se romperá.
  • acceso global: Ahora que cualquier objeto puede llamar a SFX.play(sound_path) desde cualquier parte, no habrá un modo sencillo de rastrear un error relacionado al SFX.
  • asignación global de recursos: Si todos los datos de un objeto y su procesamiento es centralizado desde el principio, entonces uno debe…
    1. arriesgarse a sub-asignar recursos que podrían provocar un comportamiento erróneo.
      • Ej: Tener muy pocos AudioStreamPlayers en el grupo de objetos. El sonido no se reproduce o interrumpe otro sonido.
    2. sobre-asignar recursos y usar más memoria/procesamiento de lo necesario.
      • Ej: Tener un número arbitrariamente grande de AudioStreamPlayers, muchos de ellos inactivos y sin hacer nada.
    3. Cada objeto que necesita un AudioStreamPlayer registra exactamente cuántos necesita y para qué sonidos. Esto anula el propósito de usar un tercero; ahora está acoplado a cada objeto, tal como lo estaría un nodo hijo. Uno ha añadido un intermediario innecesario a la ecuación.

Contrasta esto con cada escena manteniendo tantos nodos de AudioStreamPlayer como sean necesarios dentro de sí mismo y todos estos problemas desaparecerán.

  • Cada escena gestiona su propia información de estado. Si hay un problema con los datos, únicamente causará problemas en esa escena.
  • Cada escena accede sólo a sus propios nodos. Ahora, si hay un error, rastrear qué nodo es el responsable (probablemente el nodo raíz de la escena), y en qué parte del código está haciendo la llamada problemática (localizar dónde el código hace referencia al nodo en cuestión) va a ser mucho más fácil.
  • Cada escena sabe exactamente cuántos recursos necesita para la tarea que realiza. No hay pérdida de memoria o procesamiento por falta de información.

Las justificaciones típicas para el Autoload incluyen, «Tengo X’s comunes que involucran muchos nodos a través de muchas escenas, y quiero que cada escena tenga X.»

Si X es una función, entonces la solución es crear un nuevo tipo de Nodo que se ocupe de proporcionar esa característica para una escena individual o subárbol de un nodo.

Si X es dato, entonces la solución es, 1) crear un nuevo tipo de Resource para compartir datos, o 2) guardar los datos en un objeto al cual cada nodo tenga acceso (nodos dentro de una escena pueden utilizar get_owner() para obtener la raíz de su escena).

Entonces, ¿cuándo debería utilizarse un autoload?

  • Datos estáticos: si necesitas datos estáticos, por ejemplo, datos que deben ser asociados a una clase (así hay sólo una única copia de los datos), entonces los autoloads son una buena oportunidad para eso. Los datos estáticos no existen en la API de scripting de Godot, así que los autoload singletons son lo mejor disponible. Si uno crea una clase como autoload y nunca crea otra copia de esa clase dentro de una escena, entonces esta funcionará en como un singleton.
  • Convenience: los nodos en modo autoload tienen una variable global generada para GDScript. Esto puede ser conveniente para definir objetos que deben existir siempre pero que necesitan información de instancia de objeto. La alternativa es crear un script de espacio de nombres, un script cuyo único propósito es cargar y crear constantes para acceder a otros recursos Script o PackedScene, resultando en algo como MyAutoload.MyNode.new().
    • Nota que la introducción de classes de script en Godot 3.1 cuestiona la validez de esta lógica. Con ellas, se pueden acceder scripts usando un nombre explícito desde GDScript. Usando un autoload para crear un espacio de nombres se vuelve innecesario, por ejemplo: MyScriptClass.MyPreloadedScript.new().

Si el singleton mantiene su propia información y no invade los datos de otros objetos, entonces es un buen modo de crear una clase «system» que maneje tareas de ámbito amplio. Por ejemplo, un sistema de rastreo de blancos, de misiones o diálogo, pueden ser casos de uso de implementación de singletons.