Recuerda que los Gizmos son ayudas visuales que se activan en el editor, asociadas a un nodo concreto, para representar visualmente las magnitudes de algunos de los campos de un nodo. Por ejemplo, si un nodo tuviese un campo Direccion, podríamos implementar un Gizmo que dibujase una flecha que naciese del nodo y apuntase en la dirección configurada en el campo. De esa manera, se facilitaría la configuración del campo porque veríamos el resultado que nos ofrecerían los distintos valores que metiésemos. Por su parte, los Handles son puntos de agarre que se dibujan junto al Gizmo que nos permiten manipularlo de manera visual y, con ello, al campo que hay por debajo. En el ejemplo que estoy planteando, un Handle sería un punto que se dibujaría en el extremo de la flecha del Gizmo. Seleccionando ese punto y arrastrando con el ratón, la flecha cambiaría de dirección y, al hacerlo, cambiaría el valor del campo Dirección del nodo al que va ligado el Gizmo.
En un artículo anterior ya hablé sobre cómo implementar Gizmos e incluso Handles en proyectos de Godot. El problema de esa implementación es que sólo valía para proyectos 3D. Godot tiene tan separadas su API 3D y 2D que lo que vale en una no sirve para la otra. Que todo girase en torno al nodo EditorNode3DGizmo debería haberme dado una pista.
Por tanto, en un proyecto 2D de Godot no vamos a poder utilizar el método de EditorNode3DGizmo para dibujar Gizmos y Handles en pantalla. En este artículo veremos un método alternativo para incluir Gizmos en nuestro proyectos 2D. No hablaré de Handles por motivos que explicaré al final del artículo.
Lo primero es señalar que, a diferencia de Unity, Godot no ofrece nodos especializados para dibujar Gizmos en 2D. Las funcionalidades nativas de dibujado son tan potentes que basta con emplearlas cuando el nodo se ejecuta dentro del editor. La clave está en cómo hacer que el nodo se ejecute dentro del editor y que además sepa distinguir cuándo está dentro del editor y cuándo se está ejecutando como juego.
A modo de ejemplo, vamos a implementar un Gizmo de distancia circular. Tendrá forma de nodo que podremos asociar a otros que tengan campos relativos a distancias circulares respecto a su posición. Para ver una posible implementación, lo vamos a asociar a un agente de huida, el cual define una distancia de pánico. Si otro agente se acerca por debajo de la distancia de pánico, el de huida se alejará. El propósito del Gizmo será representar visualmente dicho campo de distancia de pánico.
Uso de un Gizmo para representar el campo Panic Distance |
Como se puede ver en la figura, el Gizmo dibuja una circunferencia alrededor del agente, con un radio igual al valor del campo PanicDistance. Tenemos que lograr que esa circunferencia se actualice cada vez cambiemos el valor de PanicDistance.
A nivel de la escena del nivel no se puede ver el nodo del Gizmo, porque está incluido dentro de la escena FleeSteeringBehavior (seleccionada en la figura). Si abrimos la implementación de esa última escena ya veremos el nodo de nuestro Gizmo.
El nodo de nuestro Gizmo |
En la figura anterior, se puede ver que nuestro Gizmo tiene la forma de un nodo personalizado denominado CircularRange. Este nodo ofrece una API muy sencilla. A nivel de inspector exporta una variable de color (RangeColor) para que podamos decidir el color de la circunferencia dibujada. A nivel de código, el nodo ofrece una propiedad pública con el radio de la circunferencia. El nodo padre que use CircularRange no tiene más que actualizar la propiedad del radio, para que CircularRange redibuje la circunferencia con el radio actualizado.
Lo que pasa es que no encontrarás CircularRange entre los nodos disponibles en Godot. Tendremos que implementarlo y registrarlo en Godot para que este lo muestre en su lista. Todo eso se puede hacer a través de un plugin.
Creación de un plugin
Esta parte es muy similar a la del artículo sobre los Gizmos y los Handles 3D. Hay que utilizar el wizard de Godot para crear la estructura de nuestro plugin. El wizard está en Project --> Project Settings... --> Plugins --> Create New Plugin. Te saldrá una ventana como la siguiente:
Para que te hagas una idea, algunos de los datos que configuré para el mío fueron los siguientes:
Configuración del plugin |
En el apartado de "Subfolder" puse res://addons/InteractiveRanges para que me crease esa carpeta y construyese el esqueleto del plugin allí. Dejé la casilla de "Activate now?" en blanco, porque en C# tienes que implementar el código de tu nodo y compilarlo, antes de activar el plugin que lo usa.
Una vez que creas el plugin se generará la carpeta que dijiste y se poblará con un archivo plugin.cfg con los datos que has metido a través del wizard.
Contenido de plugin.cfg |
En mi caso, planeo incluir varios tipos de Gizmos en el plugin, así que he creado una carpeta específica para el Gizmo del ejemplo: res//addons/InteractiveRanges/CircularRanges. En esa carpeta he metido todos los recursos necesarios para el nodos a implementar.
Contenido final de la carpeta del plugin |
Implementación del nodo del Gizmo
Para implementar el nodo, sólo necesitamos un script con su código y un icono que lo represente al buscar en la lista de nodos de godot.
Para el icono, yo he buscado uno en formato SVG, por aquello de facilitar su escalado. Cuanto más sencillo y conceptual mejor. En mi caso es un mero círculo, con una línea que va desde su centro a su perímetro. Por coherencia con el resto de iconos de Godot, le he dado el color morado típico de los nodos 2D de Godot.
Para el código del Gizmo, en mi caso me ha bastado con heredar de Node2D.
Primera parte de la implementación del nodo en CircularRange.cs |
Observa que he decorado a la clase con el atributo [Tool]. Esto es imprescindible para que su implementación se ejecute dentro del editor. De otra manera, el nodo estaría inerte hasta la ejecución del juego, pero este nodo no tiene utilidad durante el juego sino para ayudarnos a nosotros en el editor.
Tal y como describí más arriba, el nodo exporta una propiedad TangeColor para que podamos configurar el color del Gizmo desde el inspector.
También hay otra propiedad, Radius, que no se exporta al inspector pero que es pública para dejar que los nodos padres que utilicen el Gizmo puedan actualizar el radio representado. Fíjate en la línea 27, cada vez que se actualice el valor de Radius se forzará un redibujado del Gizmo mediante QueueRedraw().
QueueRedraw() encola una petición para que se llame lo antes posible al método de dibujado del método. Este método es _Draw() y debemos implementarlo con el aspecto que le queramos dar al Gizmo.
Implementación del dibujado del Gizmo en CircularRange.cs |
Como puedes ver, no hay que complicarse mucho para dibujar una circunferencia. Una simple llamada, en la línea 35, a DrawCircle con la posición del centro del círculo (relativa al nodo padre), el radio y el color. El parámetro "filled" sirve para definir si queremos un círculo relleno de color (true) o una mera circunferencia (false), vacía por dentro y coloreada en su perímetro.
Fíjate en la línea 33. Es importante. La llamada a Engine.IsEditorHint() devuelve verdadero si el nodo se está ejecutando dentro del editor y falso si se está ejecutando con el juego. Dado que sólo queremos que el Gizmo se dibuje en el editor, abandonaremos el método _Draw() si Engine.IsEditorHint() devuelve falso.
Es muy importante utilizar Engine.IsEditorHint() en todos los métodos de un nodo [Tool]. Habrá nodos de este tipo que sólo queremos que se ejecuten en el editor, por lo que todos los métodos tendrían que empezar con una guarda como la anterior. También habrá casos en los que haya nodos mixtos, que queramos que tengan una funcionalidad en el editor y otra durante el juego, por lo que cada método deberá incorporar una guarda (o varias) que limiten la ejecución del código, en función de lo que devuelva Engine.IsEditorHint(). Este ejemplo es sencillo, pero no siempre será así. Incorporar las guardas de Engine.IsEditorHint() puede complicar tu código y puedes sufrir errores porque se ejecute durante el juego código que estaba pensado para el editor. Por eso, la recomendación es que decores con el atributo [Tool] sólo los nodos imprescindibles. De hecho, si tienes nodos mixtos (con funcionalidad para el editor y el juego) mi recomendación es que intentes separar su funcionalidad en dos nodos: uno con la funcionalidad para el juego y otro con la del editor.
Registro del nodo del Gizmo dentro de Godot
Volvemos a una parte común al artículo de los Gizmos 3D: el registro del nodo de nuestro Gizmo, para que Godot lo muestre en la lista de nodos.
El script que se encarga del registro es el que configuraste en el wizard de creación del plugin. En mi caso InteractiveRanges.cs. En mi caso, lo he situado en la carpeta raíz del plugin.
Registro del nodo del Gizmo en InteractiveRanges.cs |
Como recordarás, el registro del nodo es muy sencillo. Basta una llamada al método AddCustomType() (línea 30), al que le pasamos el nombre del nodo, el nodo base del que hereda, la ubicación de su script y el icono que lo representará.
En mi caso, como planeo crear más Gizmos, he concentrado todo el proceso en el método RegisterCustomNode() (línea 22) al que llamaré cada vez que quiera registrar un nuevo Gizmo. Esa llamada se produce desde el método _EnterTree() (línea 8) que es el que se ejecuta cada vez que se activa el plugin.
Una vez finalizado este último script, ya tenemos todo lo que necesita nuestro plugin para funcionar. Es el momento de compilar y acudir a Project --> Project Settings... --> Plugins para activar el plugin.
Cuando lo hayamos hecho podremos elegir nuestro nodo de la lista de Godot.
Uso de nuestro Gizmo personalizado
Cuando hayamos incluido el nodo del Gizmo dentro de la jerarquía de la escena, tendremos que integrarlo con el resto del código.
Estructura de la escena del ejemplo |
En nuestro ejemplo, el nodo que usará al Gizmo pasándole sus datos es el nodo FleeSteeringBehavior.
FleeSteeringBehavior localiza a sus nodos hijos, y obtiene referencias a ellos, en su método _Ready().
El nodo FleeSteeringBehavior consigue referencias a sus hijos en el método _Ready() |
Que no te despiste la llamada a FindChild(). Se trata de un extension method que me he implementado para poder buscar a los hijos por tipo. Lo he tenido que implementar yo porque el FindChild() que viene por defecto con Godot sólo busca nodos por nombre. No es que hacer las búsquedas por nombre hubiera sido un drama, pero en este caso he preferido hacerlo por tipo.
Implementación del extension method para buscar nodos hijo por tipo |
Una vez obtenida la referencia al Gizmo, FleeSteeringBehavior debe hacer uso de ella cada vez que se cambie el valor del campo a representar con el Gizmo.
Actualización del Gizmo desde FleeSteeringBehavior |
Este tipo de cosas es la razón de ser de las propiedades. En este caso, cada vez que se actualice el valor de la propiedad PanicDistance de FleeSteeringBehavior, este actualizará la propiedad Radius del Gizmo (línea 50). Recuerda que Radius es a su vez una propiedad que fuerza al redibujado del Gizmo cada vez que alguien cambia su valor. De esta manera, el Gizmo se actualizará cada vez que se produzca un cambio en PanincDistance.
Es importante destacar que FleeSteeringBehavior debe marcarse también como [Tool] para que la lógica ligada a la propiedad PanicDistance también se ejecute en el editor.
Conclusión
Los Gizmos son extremadamente útiles a la hora de dar forma a tu juego. Te permiten contar con orientaciones del efecto de los valores de los campos de los nodos, sin tener que ejecutar el juego, lo que acelera el desarrollo.
Unity cuenta con una implementación muy madura para los Gizmos y los Handles. De hecho, las APIs de unos y de otros son redundantes en algunos apartados. Espero poder hacer un artículo al respecto pronto.
El caso de Godot no está tan maduro, parece haber cierto esfuerzo en llegar al punto en el que está Unity, pero aún se notan las carencias. Los Gizmos y Handles como tal sólo parecen existir en el mundo 3D, tienen limitaciones (sólo se pueden dibujar rectas) y aun así están muy pobremente documentados, como ya mencioné en mi artículo al respecto. El caso de los Gizmos 2D es más llamativo aún ya que no parece haber una API especializada, sino que se usan las primitivas de dibujo del engine. En sí mismo, eso no es malo, pero llama la atención que no se haya hecho un esfuerzo para generar clases auxiliares que hiciera similar la gestión de Gizmos en 3D y 2D.
Pero lo peor viene en el caso de los Handles en Godot 2D. Tampoco existen como tales, pero es que su implementación manual es compleja o incluso imposible. En mi caso, conseguí una implementación parecida a un Handle haciendo que InteractiveRanges.cs interceptase las pulsaciones de ratón, implementando los métodos _ForwardCanvasGuiInput() y Handles() de la clase madre EditorPlugin, y calculando que las posiciones se produjesen sobre un círculo dibujado sobre el Gizmo, a modo de Handle. Sin embargo, esa implementación sólo funcionaba cuando se seleccionaba el nodo del Gizmo en la jerarquía, lo que la hacía inútil ya que lo normal es que el Gizmo estuviese oculto, como nodo auxiliar dentro de otras escenas, por lo que al seleccionar estas lo que se pulsaría serían los nodos raíces de esas escenas y no la del Gizmo, por lo que este no llegaría a dibujarse. Le di muchas vueltas, busqué mil veces en internet, hasta que finalmente me di por vencido. Mi impresión es que no deben usarse muchos handles en el desarrollo con Godot.. Quizás me equivoco, pero poca gente pregunta por el tema en Internet y, desde luego, nadie plantea ni una solución ni una alternativa a la problemática que me he encontrado.
A ver si encuentro un hueco para explicar cómo se conseguiría una funcionalidad muy similar en Unity, para que así podáis contrastar el soporte que ofrecen ambos engines.