19 julio 2025

Los Resources en Godot y el riesgo de compartirlos

En Godot, los Resources (recursos) son un tipo de objeto que se utiliza para almacenar y gestionar datos reutilizables en un proyecto. Son componentes modulares que pueden ser creados, guardados, cargados y compartidos entre diferentes nodos o escenas dentro del motor. Los recursos son esenciales para organizar y estructurar proyectos de manera eficiente.

Están pensados para almacenar información específica, como configuraciones, propiedades o datos personalizados, que pueden ser accedidos o modificados por nodos u otros sistemas. Los recursos pueden guardarse como archivos (generalmente con extensión .tres para recursos textuales o .res para binarios) y cargarse en el proyecto usando ResourceLoader. Esto permite compartir recursos entre escenas o incluso entre proyectos, al no estar ligados una escena específica, sino que pueden ser referenciados por cualquier nodo o script.

Las ventajas de usar recursos son varias:

  • Modularidad: Facilita la reutilización de datos sin duplicarlos.
  • Organización: Ayuda a mantener los datos separados del código o las escenas.
  • Flexibilidad: Puedes modificar un recurso y los cambios se reflejarán en todos los nodos que lo usen.

Godot ya trae muchos predefinidos, pero nosotros podemos crear los nuestros extendiendo la clase Resource. Por ejemplo, para un proyecto que estoy desarrollando en Godot C# he creado el siguiente recurso:

Ejemplo de recurso personalizado en Godot C#
Ejemplo de recurso personalizado en Godot C#

Como verás, no tiene mucho misterio. Basta con heredar de Resource y definir los campos/propiedades que queramos que tenga el recurso. Lo que sí merece mención aparte son los atributos. 

Como en cualquier otro script de Godot, el atributo [Export] sirve para que el campo al que decora sea editable en el inspector cuando se use el recurso. 

En cuanto al atributo [GlobalClass] de la línea 7, se necesita para que tu recurso personalizado esté disponible en el buscador del editor, cuando quieras usar el recurso. 

El cuanto al atributo [Tool], sólo es necesario si vas a necesitar acceder al contenido de tus recursos desde código que se ejecute en el editor (y no sólo en el juego).

Este recurso en concreto lo uso para poder crear un array de ellos en otro nodo y almacenar en cada uno de ellos enlaces a otros nodos presentes en la escena (ese enlace es precisamente el campo NodePath). Podría haber añadido métodos al recurso, nada te lo impide, por ejemplo, para validar los datos que se guarden en el recurso, pero en mi caso no me ha hecho falta.

Ejemplo de uso de mi recurso personalizado

Cuando pinches en cada recurso, lo desplegarás y podrás ver su contenido, así como editarlo.

Edición del contenido de los recursos
Edición del contenido de los recursos

Si vienes del mundo de Unity, los recursos son el equivalente a los Scriptable Object dado que también permiten guardar sus instancias como un asset más del proyecto. 

También cubren el hueco de las clases serializables de Unity. Aunque Godot C# tiene acceso al atributo [Serializable] de C#, a diferencia de Unity, Godot no es capaz de mostrarlas en el inspector. Por eso, la alternativa es crear una clase heredera de Resource con los campos que en Unity le habríamos puesto a la clase serializada. Tengo una versión en Unity de todo el ejemplo anterior y allí lo he resuelto haciendo de WeightedBehavior una clase serializable.

Los recursos son ubicuos en Godot, te los encuentras en todas partes. Por ejemplo, cada vez quieras usar un nodo Area2D, para crear un área de detección, dicho nodo te exigirá colgarle otro de tipo CollisionShape2D, con el que definir los límites del área de detección.

Un nodo Area2D utiliza a otro nodo CollisionShape2D para definir su área
Un nodo Area2D utiliza a otro nodo CollisionShape2D para definir su área

Lo interesante es que no podremos definir la forma del CollisionShape2D hasta que creemos un nuevo recurso en su campo Shape.

Un nodo CollisionShape2D con un recurso RectangleShape2D asociado
Un nodo CollisionShape2D con un recurso RectangleShape2D asociado

Esto responde a la obsesión de Godot por separar responsabilidades. En este caso, el nodo CollisionShape2D se limita a implementar una funcionalidad en un área, pero la forma de ese área se guarda en un recurso (en este caso, RectangleShape2D). 

Si extendemos el combo veremos que RectangleShape2D no es el único recurso que podremos asociar a ese campo.

Los distintos recursos que se pueden asociar al campo Shape
Los distintos recursos que se pueden asociar al campo Shape

Si conoces los ScriptableObjects de Unity, intuirás la utilidad de los recursos de Unity y si no, ya te irás dando cuenta conforme vayas usando el engine. Al final, lo más probable es que modeles los recursos en tu cabeza como "cajitas con datos".

Una de las utilidades de estas "cajitas" es que múltiples nodos pueda leer datos de la misma cajita, en vez de repetir dichos datos en cada nodo. Se trata de una medida de eficiencia que Godot sigue por defecto de manera transparente al usuario. 

Generalmente no pasa nada, pero a veces nos puede dar sorpresas si no somos conscientes de este comportamientos. Te pongo un ejemplo que acabo de sufrir "en mis propias carnes". 

En mi proyecto he desarrollado un sensor de cono. A todos los efectos es un cono de visión. La escena que compone el sensor cuenta con los siguientes nodos:

Nodos que componen mi sensor
Nodos que componen mi sensor

Las capturas de antes del CollisionShape2D están sacadas precisamente de esta escena.

El funcionamiento general es que la definición del parámetro de alcance y apertura del cono (definidos desde otro nodo) hacen que el nodo BoxRangeManager redimensione el CollisionShape2D para que sólo se detecten los objetos que estén dentro de su área. Luego, se hace un filtrado adicional más fino por ángulo y por distancia, pero el primer filtrado "grueso" es por si el objeto detectado entra en la caja.

Un agente de mi proyecto al que le he agregado mi sensor
Un agente de mi proyecto al que le he agregado mi sensor 

En la captura anterior, la caja azul es precisamente el área del RectangleShape2D.

Con un sólo agente todo funcionaba estupendamente. El problema empezó cuando añadí un segundo agente con otro sensor de cono.

Un ejemplo de problema al instanciar escenas con recursos
Un ejemplo de problema al instanciar escenas con recursos

Para mi sorpresa, los respectivos RectangleShape2D (ojo a la cursiva) se empeñaban en tener el mismo tamaño. Cuando cambiaba las dimensiones en uno de los agentes, ambas áreas tenían las dimensiones fijadas en ese agente, y cuando pasaba a cambiarlas en el otro agente entonces ambas áreas pasaban a configurarse con esas últimas dimensiones.

Tardé un buen rato en darme cuenta del problema. La cuestión era que no había dos RectangleShape2D independientes. Como los RectangleShape2D son recursos, todas las instancias del sensor usaban, y modificaban, el mismo recurso. De ahí la cursiva de antes, no había "respectivos" RectangleShape2D sino uno sólo compartido por los sensores de ambos agentes.

¿Cómo se soluciona esto? ¿Estamos condenados a usar una única instancia de nuestro sensor? Obviamente no, pero la solución no es tan evidente.

La clave estar en volver a la configuración del nodo CollisionShape2D y pinchar en el recurso RectangleShape2D para configurarlo por dentro.

Configuración interna del recurso RectangleShape2D
Configuración interna del recurso RectangleShape2D

Dentro de esa configuración, el campo Size es evidente y responde a las dimensiones del recurso.

El campo clave esté dentro del apartado Resource y se llama Local To Scene. Por defecto viene desmarcado, lo que significa que todas las instancias de la escena actual compartirán este recurso. En otros caso, esta configuración puede ser beneficiosa, pero en el mío provocaba el problema que te acabo de describir.

Por tanto, la solución era marcar este campo Local To Scene. Al hacerlo, cada instancia de la escena creará su propia copia del recurso y trabajará con ella. Al marcarlo y salvar la escena, ambos agentes mostraron la configuración correcta de sus respectivos (ahora sí) RectangleShape2D.

Problema corregido

Así que ten cuidado cuando utilices recursos en tus escenas de Godot. Ten presente que el comportamiento por defecto de Godot será compartir los recursos entre las distintas instancias de la escena. Por eso, si detectas algún caso en el que pueda haber instancias que necesiten personalizar los valores de ese recurso, deberás anticiparte a los problemas y marcar el valor Local To Scene, para que cada instancia tenga su propia copia del recurso.

Espero que este artículo te sirva para ahorrarte problemas como el mío.