19 julio 2024

Curso "Unity C# Mobile Game Development: Make 3 Games From Scratch" de GameDevTV

El curso que hice sobre desarrollo de juegos en Godot, para plataformas móviles, me supo a poco. Así que decidí hacer el equivalente para Unity y comparar las dos plataformas. En concreto, elegí otro curso de GameDevTV: "Unity C# Mobile Game Development: Make 3 Games From Scratch". En este caso lo compré en Udemy, donde ya tengo otros cursos.

Se basa en desarrollar tres proyecto: una especie de Angry Birds, un juego de carreras y un Asteroids. Todos son muy sencillos, pero tocan lo básico: configuración de físicas, tratamiento de cámara y mucho, muchísimo, de configuración de UI.

A nivel específico de plataformas móviles, el curso explica:

  • Configuración del editor para simular plataformas móviles.
  • Compilación para Android e iOS.
  • Gestión de entradas a través de pantallas táctiles, incluidas las entradas multidedo (multi-touch)
  • Notificaciones.
  • Anuncios (Ads).
  • Microcompras (in-app purchases).
Está bien en general, pero hay varios puntos en los que se nota que necesita una actualización. Lo más sangrante es que el mismo profesor reconoce alguno de esos puntos. Por ejemplo, al llegar a la parte de los Ads, la plataforma de Unity te ofrece dos opciones. El profesor admite que la opción clásica ya está obsoleta y que el desarrollo de Unity va por la segunda opción, ¿pero adivinas cuál explica al final? justo la opción que ha reconocido que es obsoleta.

También ocurre que hay varios momentos en los que Unity ha evolucionado y los componentes que usa en la clase no aparecen o no se comportan exactamente como en la clase. Cuando te pase eso y te quedes atascado te recomiendo que eches un ojo a la sección de comentarios de la clase de turno. Verás que está hasta arriba de alumnos preguntando, pero el profesor no contesta nunca. Aun así, hay que reconocer que GameDevTV pone a profesores asociados a responder las preguntas, aunque en la mayor parte de las ocasiones sus respuestas creo que no aportan mucha ayuda. Al final, las mejores pistas las consigues de los comentarios de otros alumnos. Yo he dejado también algunas aportaciones, por si sirven de ayuda a alguien.

Como plataforma, Unity está muchísimo más madura para hacer juegos, y obtener ganancias con ellos en plataformas móviles, que Godot. Se nota que tienen componentes para todo, si bien es cierto que en muchos momentos he echado de menos la mayor claridad de Godot y, sobre todo, su mayor rapidez a la hora de desarrollar. Se me llevan los demonios cada vez que modifico un script de Unity, cambio al editor para probarlo y tengo que quedarme 5 o 6 segundos mirando a la pantalla mientras Unity hace uno de sus famosos "domain reload". En comparación, el flujo de trabajo en Godot es mucho más ágil, con ejecuciones prácticamente instantáneas (incluso usando C#), por lo que no resulta tan cansino. En mi opinión, si quieres hacer un juego para móviles con la intención de sacarle rendimiento mediante anuncios o microcompras, la mejor opción es Unity, aunque yo no descartaría Godot para hacer un prototipo, ni mucho menos para juegos de PC (para los que Godot creo que está muy bien preparado).

Como conclusión: el curso está bien, merece la pena. A mí me ha permitido asentar conceptos, algunos de los cuales ya me había presentado el curso de Godot, y además me ha permitido entender por fin cómo configurar UIs en Unity (parece mentira, pero no les había cogido el tranquillo hasta ahora). Pero, en todo caso, ten en cuenta que algunos contenidos están antiguos y no funcionarán como esperas. Así que, si te atascas, lo mejor es que acudas a la sección de comentarios porque lo mismo descubres que la culpa no es de tu código. Con esa salvedad en mente, creo que es un curso al que se puede sacar mucho partido.

12 julio 2024

Curso "Master Mobile Game Development with Godot 4: From Concept to App Stores" de GameDev.TV

Sigo avanzando con los cursos incluidos en el último pack de Humble Bundle que compré con cursos de GameDev.TV.

Esta vez le ha tocado al curso "Master Mobile Game Development with Godot 4: From Concept to App Stores". Se puede encontrar tanto en GameDev.TV como en Udemy. Comprar el curso en uno u otro portal dependerá de tus preferencias y del que te ofrezca un mayor descuento en un momento determinado. El curso al final acaba siendo exactamente el mismo.

Está enfocado a la programación de juegos para plataformas móviles Android o iOS.

Se supone que el curso tiene un nivel intermedio aunque en realidad los primeros dos bloques de lecciones, en los que construyes un juego de plataformas muy básico sin tocar un móvil, funcionan muy bien como introducción (o recordatorio, si es tu caso) de toda la parte 2D de Godot.

El tercer bloque ya se mete en la parte más específica del curso y explica cómo configurar Godot para generar paquetes instalables en Android e iOS. A nivel de jugabilidad para móviles, se queda en enseñar cómo usar el acelerómetro del terminal y a detectar si la pantalla tiene escalado y a corregirlo.

El cuarto y el quinto bloques se me hicieron muy pesados, porque se centran en montar toda la parte de UI y a dotar de funcionalidad a los botones de los menús. Está bien, si no has visto ese tema antes para Godot, pero si ya te pilla sabiéndolo esos dos bloques se te pueden hacer largos. Aún así, tampoco recomiendo saltárselos porque lo que viene después se apoya en gran parte en el UI que montemos en esta parte.

El sexto bloque es el más interesante de todos porque se centra en cómo interactuar con la API de Google Play para ofrecer microcompras en tus juegos. Para ello, usa el plugin oficial de Godot. Este bloque tiene bastante complejidad y un par de puntos que no están bien explicados, no por dejadez del autor, sino porque son cosas que me parece que sólo ocurren cuando usas Google Play por primera vez y, una vez resueltas, ya no vuelven a pasar y te olvidas de ellas. Como el autor ya ha hecho varios proyectos en Google Play, no le han pasado esas cosas, y su lección avanza como un tiro, sin darse cuenta de que a los que hacemos esto por primera vez vamos a sufrir más eventualidades de las que salen. Así, por ejemplo, me ha parecido corta la explicación sobre cómo darse de alta como desarrollador en Google Play. También hay una lección, la 6.12 "Acknowledging", donde hay un hilo entero de comentarios de gente que nos hemos estrellado con lo mismo, sin que el autor hay acudido al rescate. Al final, tras muchas búsquedas por Internet, conseguí resolver mi problema y dejé mi solución en los comentarios. La solución fue desbloquear el modo desarrollador, no en el móvil (que eso sí se explica en el curso), sino en la misma aplicación de Play Store. Sospecho que el autor del curso no lo explicó porque es la típica cosa que desbloqueas al principio y luego te olvidas... hasta que vuelves a estrellarte con ello. Pero en un curso introductorio como este es un fallo no mencionarlo. 

El séptimo bloque es sobre cómo publicar la aplicación, pero, comparando con todo lo anterior, es una auténtica tontería por su sencillez.

El autor utiliza GDScript, pero yo iba traduciéndolo a Godot C# sobre la marcha, sin problema alguno hasta la parte de las microcompras, que me forzó a practicar cómo llamar a un componente en GDScript (el plugin) desde C#. Nada complejo. Una vez que te das cuenta de que se trata de cargar el plugin como un GodotObject y, a partir de ahí, llamar a sus métodos internos con el método Call(), todo se vuelve sencillísimo. Si acaso hay que tener en cuenta que el plugin te devuelve tipos propios de Godot, como Array o Dictionary, que no son exactamente los nativos de C#. En ese último caso reconozco que tuve que analizar el código fuente (en java) del plugin para entenderlo. Al autor del curso, todo esto de los tipos le resultó transparente, dado el tipado dinámico de GDScript.

El autor se explica bien. Es turco, pero su inglés es uno de los más claros que he oído en un curso, y si aun así lo de oír inglés fuera un problema para ti tienes los subtítulos, los cuales son bastante certeros por las pocas veces que he tenido que tirar de ellos. Me ha parecido también que las prácticas de programación que emplea el autor son "sanas" y que por ello acabas armando un programa bien estructurado. Eso de "aguas abajo tiras de los métodos de las referencias, mientras que aguas arriba devuelves señales" es un principio que yo ya venía sospechando por mi propia experiencia, pero que esta es la primera vez que lo oigo explícitamente mencionado, y es un principio de diseño que me ha gustado mucho. 

Por tanto, la sensación general es que, con sus pequeñas faltas, el curso es globalmente bueno e interesante. Lo recomiendo.

25 junio 2024

Cómo implementar Gizmos y Handles en Godot

Los Gizmos te permiten dibujar líneas y formas en la ventana del editor para representar visualmente valores de un objeto.

Por ejemplo, imagina que quieres crear un componente que implemente un cono de visión:


Un cono de visión se modela con dos parámetros:

  • Alcance: Es la distancia entre el observador y el objeto observado a partir de la cual este último deja de ser visible.
  • Apertura: Es el ángulo del cono. Generalmente se calcula como un ángulo desde el vector que marca la dirección de mirada del observador. Si el objeto se encuentra en un ángulo respecto al observador mayor que el ángulo de apertura, entonces el objeto no es visible.
Por tanto tu componente puede tener esos dos parámetros, en forma de campos, y los puedes editar desde el inspector de dicho componente. El problema es que resulta muy complicado fijar los valores idóneos de esos parámetros sin tener una referencia visual del resultado. Es más fácil comprender qué queda dentro del cono de visión si, al fijar la apertura en el inspector, en el editor de la escena aparece un triángulo representando el cono de visión con ese ángulo.

Para representar esas formas geométricas, que nos ayuden a representar los valores de los campos de nuestros componentes, es para lo que sirven los Gizmos. 

Por supuesto, los Gizmos sólo aparecerán en el editor. Son ayudas para el desarrollador y para el diseñador de niveles, pero serán invisibles al iniciar el juego final. 

En realidad, si has estado practicando con Godot, ya habrás usado Gizmos, por ejemplo al fijar la forma de un CollisionShape. Las cajas, círculos y demás formas geométricas que aparecen en el editor cuando quieres configurar un CollisionShape son precisamente Gizmos, dibujados tal y como vamos a ver aquí.

Precisamente, si te fijas en los CollisionShape, verás que, además del Gizmo, aparecen unos puntos sobre los que pulsar y arrastrar para cambiar la forma del componente. Esos puntos se denominan Handles y son las "agarraderas" para manipular lo que representemos en el editor. En este artículo también veremos cómo implementar nuestros propios Handles.

Visión lateral de un CollisionShape con forma de caja. Los bordes azules son el Gizmo, representando la forma, y los puntos rojos son los Handles, para cambiar la forma.


Los Gizmos se asocian a los nodos que complementan, de tal manera que Godot sepa qué Gizmos activar en caso de que se incluya un nodo determinado en la escena. Como hemos visto, muchos de los nodos de Godot ya tienen Gizmos asociados (como hemos visto con los nodos CollisionShape).

Creación de un nodo personalizado


Como no quiero enredar con los Gizmos por defecto de los nodos de Godot, vamos a empezar añadiendo un nodo personalizado a la lista de nodos de Godot. A ese nodo personalizado le asociaremos un Gizmo, con sus respectivos Handles, que nos sirva como ejemplo.

En nuestro ejemplo, para crear ese nodo personalizado he creado la carpeta nodes/CustomNode3D, dentro de la del proyecto. En esa carpeta podemos crear el script de nuestro nodo personalizado pulsando en la carpeta con el botón derecho del ratón y eligiendo Create New > Script.... Saldrá una ventana emergente como la siguiente, en la que he completado los valores para este ejemplo:



Una vez generado el script, sólo necesitaremos que implemente 2 propiedades públicas Vector3 exportadas. Yo las he llamado NodeMainPoint y NodeSecondaryPoint:

[Export] public Vector3 NodeMainPoint { get; set; }
[Export] public Vector3 NodeSecondaryPoint { get; set; }

No pongo captura, porque más adelante incluiremos código en la parte de set.

La idea es que al arrastrar los Handles en el editor se actualice el valor de las dos propiedades anteriores. A la inversa también debe funcionar: si cambiamos el valor de las propiedades en el inspector, los Handles deberán resituarse en las posiciones señaladas por las propiedades. Por otro lado, también dibujaremos un Gizmo en forma de línea, desde el origen del nodo hasta las posiciones de las propiedades.

Con eso debería bastar para ilustrar las mecánicas principales: representar con Gizmos propiedades de un nodo y cambiar dichas propiedades mediante Handles.

Lo siguiente será crear una carpeta addons dentro del proyecto de Godot. Los Gizmos y los Handles personalizados se consideran plugins por lo que el consenso es depositarlos en una carpeta addons dentro del proyecto.

Hecho eso, iremos a Project > Project Settings ... >  Plugins y pulsaremos el botón Create New Plugin. Nos saldrá una ventana como la de la siguiente figura, en la que yo ya he rellenado los valores para el ejemplo:



Hay que tener en cuenta que la carpeta que fijemos en el campo Subfolder, de la ventana anterior, se creará dentro de la carpeta addons que mencionábamos antes. Lo mismo pasará con el script del plugin, definido en el campo Script Name

Fíjate también en que he desactivado el check de Activate now?. Los plugins en GDScript se pueden activar inmediatamente, con el código de la plantilla que se genera, pero los plugin en C# requieren de cierta configuración previa, por lo que darán error si se intentan activar con el código de la plantilla por defecto. Tampoco es que el error resultante rompa nada, pero sale una ventana de error que hay que cerrar y queda feo. Así que lo mejor es desactivar ese check y dejar la activación para un paso posterior, como veremos a continuación.

Hecho lo anterior, se habrá generado una carpeta, dentro de addons, con un archivo plugin.cfg y el script de C# de la ventana anterior. El propósito de ese script será registrar en Godot el tipo representado por CustomNode3D para que podamos elegirlo en la lista de nodos del engine. Recuerda que CustomNode3D hereda de Node3D, así que tiene sentido incluirlo junto al resto de nodos.

Como en cualquier otro plugin, CustomNode3DRegister tendrá que heredar la Clase EditorPlugin e implementar los métodos _EnterTree() y _ExitTree(). En el primer método registraremos CustomNode3D como un nodo elegible en la lista de nodos, y en el segundo método lo daremos de baja para que deje de aparecer en dicha lista. La implementación es sencilla:

addons/custom_node3D_register/CustomNode3DRegister.cs


Como se puede ver, en el método _EnterTree() cargamos dos cosas: el script asociado al nodo personalizado y el icono con el que queremos representar el nodo en la lista de Godot. Para el icono he usado el que se incluye en todos los proyectos de Godot, copiándolo desde la raíz en la carpeta del nodo personalizado.

Luego, asociamos esos elementos a un nodo base, mediante el método AddCustomType() que es el que inscribe el nodo personalizado en la lista de nodos. Dado que el script del nodo personalizado hereda de Node3D es el que hemos usado como clase base en la llamada a AddCustomType(). Con esta llamada, cuando elijamos CustomNode3D en la lista de nodos, se creará un Node3D en la escena y se le asociará el script que hayamos definido.

La implementación de _ExitTree() es justo la contraria: usamos el método RemoveCustomType() para borrar al nodo personalizado de la lista de nodos.

Para hacer que se ejecute el registro, compilaremos el juego para que se hagan efectivos los cambios sobre CustomNode3DRegister.cs. Hecho eso, iremos a Project > Project Settings ... >  Plugins y nos aseguraremos de marcar el check de Enable del plugin CustomNode3DRegister. Eso hará que se ejecute su lógica y se inscriba a nuestro nodo personalizado en la lista de nodos. A partir de ahí, podremos localizar a nuestro nodo en la lista y añadirlo a la escena:



Añádelo a la escena antes de avanzar.

Creación de un Gizmo para el nodo personalizado


Ahora que ya tenemos nuestro nodo personalizado, vamos a crear un Gizmo que represente visualmente sus propiedades.

Los Gizmos se consideran addons, así que lo lógico será crear una carpeta addons/CustomNode3DGizmo para alojar sus ficheros. Esos ficheros serán dos: un script para definir el Gizmo, que será una clase que herede de EditorNode3DGizmoPlugin, y otro script para registrar el Gizmo, que heredará de EditorPlugin y se parecerá bastante al que hemos usado para registrar al nodo personalizado.

El script del Gizmo es el que tiene toda la enjundia. Yo lo he llamado CustomNode3DGizmo.cs. Como ya he dicho, debe heredar de EditorNode3DGizmoPlugin e implementar algunos de sus métodos.

El primero de esos métodos es _GetGizmoName(). Este método se limita a devolver una cadena con el nombre del Gizmo:

addons/custom_node3D_gizmo/CustomNode3DGizmo.cs

Algo más intrigante es el método _HasGizmo(), al que se le pasarán todos los nodos de la escena hasta que el método devuelva true para alguno de ellos, señalando que el Gizmo debe aplicarse a ese nodo. Por tanto, en nuestro caso, el método deberá devolver true cuando se le pase un nodo de tipo CustomNode3D:

addons/custom_node3D_gizmo/CustomNode3DGizmo.cs

Aquí hay que tener en cuenta cierto problema que se da en C# y no en GDScript. Aunque la comparación con "is" es sintácticamente correcta, en la práctica no funciona en Godot C# a no ser que la clase con la que comparemos esté marcada con el atributo [Tool]. Así que este es un buen momento para añadirle ese atributo a la cabecera de la clase CustomNode3D:

nodes/CustomNode3D/CustomNode3D.cs

En realidad, esto es una anomalía. No deberíamos necesitar el atributo [Tool] para hacer funcionar esa comparación. De hecho, el código equivalente de GDScript (el que aparece en la documentación oficial) no lo requiere. Se trata de un bug reportado varias veces en los foros de Godot y pendiente de solución. Hasta que lo resuelvan, el workaround en C# es usar el atributo [Tool].

El siguiente método a implementar es el constructor de la clase de nuestro Gizmo. En GDScript usaríamos el método _init(), pero en C# usaremos el constructor de la clase:


addons/custom_node3D_gizmo/CustomNode3DGizmo.cs


En ese constructor crearemos los materiales a aplicar a nuestro Gizmo y a sus Handles. No se trata de un material completo, como el que haríamos con un shader, sino más bien un conjunto de estilos a aplicar a las líneas que dibujemos para nuestro Gizmo. Se crean usando los métodos CreateMaterial(), para el Gizmo, y CreateHandleMaterial(), para el Handle. Ambos aceptan, como primer parámetro, una cadena con el nombre que queramos darle al material. Ese nombre se utiliza con el método GetMaterial() para obtener una referencia a dicho material. Esa referencia puede ser útil, por ejemplo, para asignársela a una variable de tipo StandardMaterial3D con la que personalizar a fondo el material, fijando los valores de sus respectivas propiedades. Sin embargo, lo normal es que no tengas que recurrir a ese grado de personalización y te baste con fijar el color de las líneas usando el segundo parámetro del método CreateMaterial(). Sin embargo, el método CreateHandleMaterial() no acepta ese segundo parámetro, por lo que no queda más remedio que recurrir, como decía antes, al método GetMaterial() (líneas 19 y 20 de la captura anterior) para obtener las referencias al material, con las que fijar el valor de su propiedad AlbedoColor (líneas 21 y 22).

En el constructor del ejemplo, he configurado que las líneas que se tracen entre el origen de coordenadas y la posición marcada por la propiedad NodeMainPoint utilicen el color rojo. Las líneas que vayan hasta la posición de la propiedad NodeSecondaryPoint usarán el color verde. He configurado los materiales de los respectivos Handles para que usen el mismo color.

Por último, queda el método _Redraw(). Es el que se encarga de dibujar los Gizmos cada vez que se llame al método UpdateGizmo() con el que cuentan todos los Node3D.

addons/custom_node3D_gizmo/CustomNode3DGizmo.cs


El método _Redraw() es como nuestra pizarra, y lo normal con una pizarra es borrarla al principio para pintar sobre ella. Esa es la razón por la que se suele llamar al método Clear() al principio del método (línea 29 de la captura anterior).

Luego recopilamos en un array de Vector3 las posiciones de las líneas que queremos dibujar. En este caso queremos trazar una línea entre el origen de coordenadas y la posición marcada por la propiedad NodeMainPoint, así que guardamos ambos puntos en el array (líneas 33 a 37 de la captura anterior). 

Con los Handles hacemos lo mismo, guardando en otro array los puntos en los que queramos que aparezca un Handle. En este caso, como queremos que aparezca un Handle en el extremo de la línea, el marcado por la posición de NodeMainPoint, sólo metemos esa posición en el array de Handles (líneas 38 a 41 de la captura anterior).

Finalmente, usamos el método AddLines() para dibujar las líneas a lo largo de las posiciones recogidas en el array (línea 42); y, por otro lado, el método AddHandles() para posicionar Handles en las posiciones recogidas en su array (línea 43). Fíjate que, en ambos casos, pasamos el material que define el estilo con el que queremos que se dibujen los elementos.

No lo he incluido en la captura anterior, pero la mecánica para dibujar la línea y el Handle de un segundo punto (en este caso NodeSecondaryPoint) sería la misma: confirmaríamos sus arrays de posiciones y los pasaríamos a los métodos AddLines() y AddHandles().

Manipulación de un Gizmo usando Handles


En este punto, nuestro Gizmo dibujará líneas y Handles dependiendo de los valores recogidos en las propiedades del nodo al que se asocia (en este ejemplo, CustomNode3D). Sin embargo, si pulsamos en los Handles, no pasará nada. Estos permanecerán inmóviles.

Para interactuar con los Handles, hay que implementar unos cuantos métodos más de EditorNode3DGizmoPlugin en nuestra clase CustomNode3DGizmo. Sin embargo, la documentación oficial de Godot no cubre dichas implementaciones. Si sigues el tutorial de la documentación oficial, te quedarás en la sección anterior de este artículo. Es extrañísimo, pero no hay nada en la documentación oficial que explique cómo manipular los Handles. Todo lo que sigue a partir de aquí lo he deducido probando e interpretando los comentarios de cada función a implementar. Quizás, a partir de este artículo, haga una aportación a la documentación de Godot para resolver esta carencia.

Veamos qué métodos hay que implementar en CustomNode3DGizmo para poder manipular los Handles que ubicamos en el método _Redraw().

El primero de todos es _GetHandleName(). Este método debe devolver una cadena con el nombre identificativo del Handle. Lo normal es devolver el nombre de la propiedad que se ve modificada por el Handle:

addons/custom_node3D_gizmo/CustomNode3DGizmo.cs


En la captura anterior hay que destacar dos cosas. 

La primera es que podríamos haber devuelto el nombre de la propiedad como una cadena puesta a mano, pero usando el método nameof() nos aseguraremos de que si refactorizamos el nombre de la propiedad, usando nuestro IDE, esta parte del código se actualizará también. 

La segunda cosa a destacar es que cada Handle está identificado con un entero, de tal manera que podemos saber para qué Handle se está pidiendo el nombre en función del parámetro handleId que se le pase al método _GetHandleName(). El entero de cada Handle depende del orden en el que metiésemos los Handles al llamar al método AddHandles() en el método _Redraw(). Por defecto, si dejas vacío el parámetro ids de AddHandles(), el primer Handle que le pases se le asignará el id o, al segundo el 1 y así. Sin embargo, si te fijas en la captura que puse más atrás de _Redraw(), yo no dejé vacío el parámetro ids. En vez de eso, le pasé un array con un único elemento. Ese elemento era un entero, definido como una constante, para forzar que a ese Handle se le asignase ese entero como id, y poder usar esa constante como identificador a lo largo del código.

addons/custom_node3D_gizmo/CustomNode3DGizmo.cs

Una vez que se implementa cómo identificar cada Handle, el siguiente paso es definir qué valor devuelve el Handle cuando pinchamos en él. Eso se hace implementando el método _GetHandleValue().

addons/custom_node3D_gizmo/CustomNode3DGizmo.cs


Al igual que con _GetHandleName(), a _GetHandleValue() también se le pasa el identificador del Handle para el que se está pidiendo el valor. Con el parámetro gizmo podemos obtener el nodo asociado al Gizmo, usando el método GetNode3D() (línea 130 de la captura anterior). Y una vez que tenemos una referencia al nodo podemos devolver el valor de la propiedad asociada a cada Handle (líneas 133 a 136).

Cuando pulses en un Handle, fíjate en la esquina inferior izquierda de la vista de escena del editor, aparecerá una cadena conformada por lo que devuelva _GetHandleName() y lo que dé _GetHandleValue() para ese Handle.

Ahora viene lo que puede ser la parte más difícil de este tutorial: usar el Handle para asignarle un valor a la propiedad asociada del nodo. Eso se hace implementando el método _SetHandle():

addons/custom_node3D_gizmo/CustomNode3DGizmo.cs

A ese método se le pasa un parámetro gizmo, con el que podemos tener acceso al nodo asociado, con el método GetNode3D(), tal y como hacíamos con _GetHandleValue(). También se le pasa el identificador del Handle para el que se está fijando el valor. Pero lo más importante es que se le pasa la cámara que está visualizando la escena y la posición en pantalla del Handle.

En este método tendremos que interpretar la posición en pantalla del Handle, para fijar, en función de esa posición, el valor de la propiedad del nodo asociada al Handle. En este caso parece fácil: la posición del Handle debe ser el valor que se guarde en la propiedad asociada, dado que tanto NodeMainPoint como NodeSecondaryPoint son posiciones. El problema es que os Handles se arrastran sobre la superficie bidimensional de la pantalla, esa es la razón por la que el parámetro screenPos es un Vector2, por lo que no es inmediato saber a qué coordenada tridimensional del escenario corresponde ese punto en la pantalla.

Cuando añadimos un nodo Camera3D a una escena, dicho nodo se representa con el siguiente Guizmo:


A mí me resulta muy esclarecedor pensar que nuestra cabeza esta en la punta de la pirámide del Gizmo y que miramos a una pantalla que se encuentra en la base. Sobre dicha pantalla se retroproyecta la escena que se encuentra delante de la cámara.

Supongamos que tenemos un objeto A en el escenario. La posición de la pantalla en la que se pinta A (vamos a llamar a esa posición Ap) es el resultado de trazar una línea recta entre el objeto y el foco de la cámara y ver en qué punto corta con el plano de retroproyección de la cámara:


  
Hasta ahí, todo es muy fácil. De hecho, la clase Camera3D tiene el método UnprojectPosition() al que se le pasa la posición tridimensional (Vector3) de un objeto de la escena y devuelve la posición bidimensional (Vector2) de ese objeto en la pantalla. En nuestro caso, si le pasásemos a UnprojectPosition() la posición de A, el método nos devolvería Ap (entendida esta como una posición bidimensional de la carpeta).

Ahora supongamos que en la posición Ap de la pantalla tuviéramos un Handle que representase la posición de A, y que arrastrásemos el Handle hasta la posición Bp de la pantalla, ¿Cómo calcularíamos la nueva posición del objeto en el espacio tridimensional? (vamos a llamar a esa nueva posición B). Lo lógico será aplicar el proceso inverso al que hicimos para retroproyectar el objeto sobre el plano de la cámara. Para ello trazaríamos una línea entre el foco de la cámara y Bp. Siguiendo ese razonamiento, la nueva posición del objeto estará a lo largo de esa línea, ¿pero dónde?, ¿en qué punto de la línea?

La clave está en darse cuenta de que el objeto se desplazará en un plano (Pm) paralelo al de la cámara. La intersección entre ese plano y la línea que parte del foco de la cámara, atravesando Bp, será la nueva posición del objeto (B):



El nodo Camera3D cuenta con un método ProjectPosition(). Sirve precisamente para convertir coordenadas bidimensionales de pantalla en coordenadas tridimensionales de escena. El método acepta dos parámetros. El primero es una posición bidimensional de la pantalla (en nuestro ejemplo Bp). Con ese parámetro, el método trazará la línea que parte del foco de la cámara y atraviesa la coodenada dimensional de la cámara (Bp). El segundo parámetro del método se denomina zDepth y es un float que señala la distancia desde el foco de la cámara a la que se situará el plano Pm con el que debe cortar la línea.  




Esa distancia será la longitud de la línea que parte del foco de la cámara e incida perpendicularmente sobre el plano Pm. En el diagrama anterior será la distancia entre el foco (F) y el punto D. 

¿Pero cómo podemos calcular esa distancia? Usando trigonometría. Si recordamos nuestra enseñanza secundaria, el coseno del ángulo entre el segmento FA y FD equivale a la relación de FD dividido entre FA. Así que FA multiplicado por el coseno nos dará FD.

Resulta que el cálculo anterior es tan habitual que la biblioteca de operaciones vectoriales de los engines de desarrollo lo incluyen con el nombre de Dot Product. Con ese operador podemos transformar la fórmula anterior en la siguiente:

La fórmula anterior quiere decir que si hacemos el Dot Product del vector FA sobre el vector normalizado de FD nos dará el vector completo FD. 

Es habitual visualizar el Dot Product como una proyección de un vector sobre otro. Si colocases una luz muy potente por detrás del vector FA, enfocándola de manera perpendicular al vector normalizado FDn, la sombra que proyectaría FA sobre FDn sería precisamente FD.

Por lo tanto, para conseguir la distancia FD, que nos sirva como parámetro zDepth sólo necesitamos hacer el Dot Product de FA sobre el valor normalizado de FD, que es precisamente el vector Forward del nodo Camera3D (por defecto, el valor inverso de su eje local Z). 

Todo este razonamiento se reduce a unas pocas líneas, en el método GetZDepth():

addons/custom_node3D_gizmo/CustomNode3DGizmo.cs


En dicho método, la variable vectorToPosition corresponde a FA y cameraForwardVector a FDn. El resultado devuelto por el método zDepth es FD y es lo que se utiliza en las llamadas a ProjectPosition(), en _SetHandle() para fijar las nuevas posiciones de NodeMainPoint y NodeSecondaryPoint.

Resuelto _SetHandle(), el único método que quedaría por implementar es _CommitHandle(). Este método es el responsable de ir construyendo el historial de modificaciones que vamos realizando sobre nuestros Handles, de manera que podamos movernos por dicho historial cuando hagamos undo/redo (Ctrl+Z o Ctrl+Shift+Z).


addons/custom_node3D_gizmo/CustomNode3DGizmo.cs


El historial se construye sobre un objeto de tipo EditorUndoRedoManager (en este caso _undoRedo), que se obtienes desde el objeto EditorPlugin que registra el Gizmo (en este ejemplo CustomNode3DGizmoRegister, del que hablaremos ahora) y le pasa por su constructor una instancia del EditorUndoRedoManager.

Con el EditorUndoRedoManager, cada entrada en el historial se crea con el método CreateAction() (línea 78 de la captura anterior). En cada entrada debe haber acciones que se ejecuten en caso de que el usuario haga Undo y de Do (llamadas cuando se haga Redo). Esas acciones pueden implicar fijar una propiedad, con los métodos AddDoProperty() y AddUndoProperty(), o ejecutar un método, con AddDoMethod() y AddUndoMethod(). Si la acción directa sólo ha implicado cambiar el valor de una propiedad, lo normal es que para deshacer esa acción sólo tengas que poner la propiedad a su valor anterior. Pero si la acción directa disparó algún tipo de método, además del cambio de método de la propiedad, entonces lo más probable es que necesite lanzar otro método para deshacer lo hecho por el primero. 

En el caso de este ejemplo, me limito a cambiar el valor de las propiedades de customNode3D, así que para el historial de acciones basta con usar los métodos Add...Property(). Estos métodos piden como parámetro la instancia dueña de las propiedades a modificar, la cadena con el nombre de la propiedad a manipular y el valor al que fijar dicha propiedad. Cada acción captura el valor que le pasemos al método Add...Property(). En el caso de AddDoProperty() le pasamos el valor que tiene en ese momento la propiedad (líneas 84 y 91); mientras que en el caso de AddUndoProperty() le pasamos el valor del parámetro restore, que contiene el valor rescatado del historial cuando hacemos Undo.

Cuando _CommitHandle() se llama con el parámetro cancel a verdadero, es equivalente a un Undo sobre el Handle, así que restauramos el valor de restore sobre la propiedad (líneas 101 a 106).

Por último, pero no menos importante, una vez que hayamos dado forma a los cambios de propiedad y llamadas de método que conforman la entrada en el historial, registraremos dicha entrada con CommitAction() (línea 109).

Actualización del Gizmo tras los cambios


La representación visual de un Gizmo puede tener que actualizarse por dos razones:
  1. Porque hayamos cambiado los campos del nodo representado desde el inspector.
  2. Porque hayamos manipulado los Handle del Gizmo.
La actualización del Gizmo se hace llamando al método UpdateGizmos() que tienen todos los Node3D. 

La cuestión es desde dónde llamar a ese método para asegurar que se actualizan los dos tipos de cambios anteriores. En las capturas de código anteriores verás que hay varias llamadas a UpdateGizmos() comentadas. Son pruebas de posibles sitios desde donde ejecutar ese método. Todas las llamadas comentadas tenían algún problema: o no se llamaban en alguno de los dos casos anteriores o se actualizaban a trompicones, como si hubiera algún problema de rendimiento. 

Al final, mis pruebas me han hecho llegar a la conclusión de que, en mi caso, el mejor sitio desde donde llamar a UpdateGizmos() es desde las mismas propiedades de CustomNode3D que estamos modificando. Por ejemplo, en el caso de NodeMainPoint:

nodes/CustomNode3D/CustomNode3D.cs

Al llamar a UpdateGizmos() desde el set de la propiedad, estando esta exportada, nos aseguramos que el método se llame tanto cuando se modifique la propiedad desde el inspector como desde el _SetHandle() del Gizmo.

Registro del Gizmo

Igual que pasó con el nodo personalizado, nuestro Gizmo también debe registrarse en el editor, para que este sepa que tiene que contar con él. Para ello volveremos a usar un plugin que haga la labor de registro.

addons/custom_node3D_gizmo/CustomNode3DGizmoRegister.cs

Crearemos el plugin con el mismo método con el que creamos el plugin CustomNode3DRegister, sólo que esta vez, el plugin se llamará CustomNode3DRegister y se basará en un script C# del mismo nombre.

En este caso cargamos el script de C# en el que hemos configurado el Gizmo y lo instanciamos, pasándole a su constructor una instancia de EditorUndoRedoManager, llamando al método GetUndoRedo() (líneas 13 y 14).

Hecho eso, registramos la instancia del plugin, entregándoselo al método AddNode3DGizmoPlugin() (línea 15).

También de manera similar a como hicimos en el registro del nodo personalizado, en este caso también aprovechamos el método _ExitTree() para dar de baja al Gizmo mediante el método RemoveNode3DGizmoPlugin() (línea 21). 

Finalizado el script, podremos activar el plugin desde Project > Project Settings... > Plugins, y la próxima vez que añadamos un CustomNode3D a la escena podremos usar los Handles del Gizmo. 

Puede que al principio no se distingan bien los Handles, porque ambos coincidirán en el origen de coordenadas:



Sin embargo, están ahí. Son los puntos que se adivinan en el origen del eje de coordenadas. Si pulsamos en dichos puntitos y arrastramos veremos como estos se mueven alterando el valor de las propiedades de CustomNode3D.



Conclusiones

Este artículo ha sido extremadamente largo, pero he querido solventar con él una carencia desoladora, por parte de la documentación oficial, sobre este tema.

Mi experiencia anterior había sido en Unity, que también tiene su propia API de Gizmos y de Handles. En comparación con Unity, el enfoque de Godot me ha parecido más compacto y sencillo, al concentrar tanto la configuración de los Gizmos, como de los Handles en una misma clase heredera de EditorNode3dGizmoPlugin. En contraste, para hacer lo mismo en Unity tienes que recoger el código de los Gizmos y el de los Handles en clases dispares.

Todo hay que decirlo, la documentación de Unity para este tema me ha parecido mucho más completa.

También hay que tener en cuenta que la API de Gizmos y Handles de Unity cubre tanto los juegos 2D como los 3D, mientras que en Godot, todo lo que hemos visto en este artículo, en realidad sólo aplica a los juegos 3D. No existe una clase EditorNode2DGizmoPlugin, o yo al menos no tengo claro cual sería el equivalente de todo este código para un juego 2D de Godot. Lo investigaré y cuando lo descubra probablemente haga otro artículo, pero en un primer vistazo a la documentación oficial no me queda nada claro cómo hacer todo esto en 2D.


Código de este tutorial

El código del proyecto utilizado como ejemplo está disponible para descarga en mi repositorio GodotCustomGizmoAndHandleExample en GitHub. No dudes en descargártelo para examinar detenidamente el código y probarlo tú mismo.

08 junio 2024

Cómo añadirle barras de vida a tus personajes en Godot

Es muy habitual que los personajes de los juegos cuenten con barras de progreso, a sus pies o sobre sus cabezas, para mostrar la cantidad de vida que les queda.

Godot con 2 nodos ideales para esto: ProgressBar y TextureProgressBar. Con el primero tenemos todo lo que podemos necesitar para una barra básica. El segundo nodo es una evolución el primero, que permite dotarle de una apariencia visual más atractiva, usando texturas en vez de colores plano. En este tutorial nos centraremos en ProgressBar, controlado este, usar el nodo TextureProgressBar es relativamente sencillo.

En Godot2D, ponerle una barra de progreso a tu personaje es muy simple. Te basta con añadir el nodo ProgressBar a la jerarquía de la escena del personaje y luego redimensionar y situar la barra usando sus puntos de agarre visuales.


A partir de ahí, sólo te quedaría configurar la apariencia visual de la barra e implementar su script, tal y como veremos más adelante.

Sin embargo, añadirle una barra de vida a un personaje de un juego 3D no es tan sencillo. El problema es que ProgressBar es un nodo 2D y no se puede añadir directamente a un juego 3D. De hecho, si intentas añadir el nodo a un personaje de un juego 3D, tal y como hemos hecho en el caso del 2D, verás que el editor de la escena conmuta a la pestaña 2D y no te deja colocar el nodo como parte de tu escena 3D.

El truco está en usar un nodo que ya empleamos en el artículo sobre los minimaps en Godot: el nodo SubViewport. Este nodo crea un área de visualización independiente en una región de la pantalla. En el caso de los minimaps lo empleamos para mostrar el punto de vista de la cámara cenital, al mismo tiempo que el resto de la pantalla continuaba mostrando lo que veía la cámara principal. En este caso, el papel del nodo será mostrar un elemento 2D en una región de la pantalla de un juego 3D.

En el caso de los minimaps, el truco funcionaba haciendo que el nodo Camera3D fuera hijo del SubViewport y colocando este en la región de la pantalla que nos interesase, usando un nodo SubViewportContainer.  

En el caso de las barras de vida se hace de una manera similar: colocas el nodo ProgressBar como hijo del SubViewport, pero en este caso ya no puedes usar el nodo SubViewportContainer porque este sitúa las cosas en una posición concreta de la pantalla y no de manera relativa a un personaje, para que se mueva con él por el escenario. Para eso, lo que podemos utilizar es un nodo Sprite3D. Este nodo, sí que puede colocarse en una posición relativa a un personaje, como parte de la jerarquía de su escena. Así que haremos que los nodos SubViewport y ProgressBar sean hijos del Sprite3D. 



Al terminar, la barra de vida seguirá sin verse. Esto se debe a que tenemos que configurar el Sprite3D para que sea este el que muestre la barra de vida. Dicho de otra manera, tenemos que configurar el Sprite3D para que este muestre la imagen renderizada por el SubViewport. Para eso, tenemos que buscar en el inspector la propiedad Texture del Sprite3D. Cuando la encuentres estará vacía, por lo que deberás crear en el ella un New ViewportTexture y seleccionar nuestro SubViewport en la ventana emergente que salga. Será a partir de ese momento cuando la barra se hará visible dentro de la escena de nuestro personaje.



Lo normal es que, más allá del cacharreo que puedas hacer para probar, concentres todos los nodos de la barra en su propia escena, de manera que puedas reutilizarla en personajes diferentes. Yo es lo que he hecho y es lo que se ve en la captura anterior.

Hasta ahí lo más difícil de la configuración de las barras de vida. Lo siguiente sería configurar la apariencia visual de la barra. Fijaremos su tamaño con la propiedad Size del SubViewport. Yo suelo desactivar la propiedad Show Percentage de ProgressBar para que no se muestre el tanto por ciento. En cuanto a los colores de la barra, tendremos que buscar la sección Themes Overrides, en el inspector del ProgressBar. Allí, tendremos que ampliar otra sección denominada Styles. Tiene dos apartados: Background y Fill. El primero sirve para definir la apariencia visual del fondo de la barra y el segundo la de la barra principal. Lo más sencillo es dotar a esas propiedades de sendos recursos StyleBoxFlat y editar su propiedad BG Color con los colores que deseemos. Por ejemplo, podríamos fijar el BG Color del fondo a un color con Alpha 0, para que el fondo fuese completamente transparente, y el color de la barra a azul.




Lo que quedaría sería la lógica para actualizar los valores de la barra conforme lo hiciesen los del personaje. 

Las tres propiedades básicas de un ProgressBar son: MaxValue, MinValue y Value. Las dos primeras se suelen fijar al principio, por ejemplo en el método _Ready(), y definen los valores máximo y mínimo que abarcará la barra. Por su parte, la propiedad Value es la que iremos actualizando a lo largo del juego para que el ProgressBar actualice la longitud de la barra en función del valor de Value en relación con el mínimo y el máximo.

Una aproximación que suelo seguir es crear un script  C# para el Sprite3D, con una referencia al ProgressBar:



A partir de esa referencia, creo propiedades para los valores máximo, mínimo y actual, de manera que cuando se modifiquen estos valores desde fuera del script, se actualice también su equivalente dentro de ProgressBar. Por ejemplo, para el valor máximo:


Las propiedades para los valores mínimo y actual son prácticamente idénticas.

He exportado estas propiedades para poder ponerle valores iniciales desde el inspector. Para que los valores que hayamos configurado en el inspector se apliquen a la barra de progreso, al comenzar el juego, tendremos que tirar del método _Ready():


Una vez arrancado el juego, serán las propiedades las que actualicen el ProgressBar ya que para entonces la referencia a esta no será null.

Lo que queda es ofrecer al exterior un medio para actualizar el valor de la propiedad CurrentValue y, con eso, el de la barra. Puedes hacerlo de muchas maneras, por ejemplo, haciendo que los que scripts que vayan a actualizar la barrar tengan un referencia al script de la barra y, a través de ella, manipulen la propiedad CurrentValue. Sería una aproximación válida, pero incrementaría el acoplamiento al obligar a establecer una referencia directa entre objetos. 

Otra opción, que reduzca el acoplamiento, es hacer que los scripts que modifican el nivel de vida emitan señales (la versión de Godot de los eventos) cada vez que se produzca un cambio y que la barra se suscriba a esas señales. En mi ejemplo he seguido ese enfoque y he incluido un handler, en el script de la barra, para responder a ese tipo de señales:


Luego, he suscrito ese handler a la señal emitida por el personaje cada vez que recibe daño:


En el caso de mi ejemplo, la señal del daño la emite el script CharacterLifeManager.cs, el cual define la señal de la siguiente manera:


La emisión de la señal anterior se realiza desde la línea 46 del método ApplyDamage() del script anterior, al cual se llama cada vez que el personaje recibe un golpe de sus contrincantes:


El hecho de usar deltas, en vez de valores absolutos, en el handler OnCurrentValueChanged() permite suscribirlo no sólo a señales de daño (que transmiten deltas negativas) como a señales de curación (cuyas deltas son positivas). En este caso, el script que gestiona las curaciones, cuando el jugador recoge una poción, emite una señal con delta positiva a la que podemos suscribirnos al igual que hicimos con la señal de daño:


La definición de la señal y su lanzamiento es muy similar al caso de la señal de daño, así que no la voy a repasar aquí.

Al basarnos en señales, hemos reducido el acoplamiento entre componentes y hemos conseguido una barra que esa altamente reutilizable ya que la podremos aplicar a cualquier elemento del juego siempre que este emita señales con el valor del incremento (ya sea positivo o negativo) cada vez que se produzca un cambio en el valor monitorizado. De esta manera, podremos reutilizar esta barra de vida, que hemos implementado aquí, con otros componentes para mostrar valores que no tienen por qué ser de vida, como por ejemplo munición, karma o nivel de blindaje.

Aquí acabamos el artículo, espero que te haya gustado. Si las explicaciones y las capturas no te hubiesen sido suficientes, puedes bajar el proyecto que he utilizado de ejemplo desde mi repositorio DungeonRPG en GitHub. He utilizado como base el mini juego que hice al seguir el curso de GameDevTV "Godot 4 C# Action Adventure: Build your own 2.5D RPG", el cual recomiendo muchísimo. El curso no abarca las barras de vida, pero el minijuego que implementa es una base excelente para aprender a crearlas.

01 junio 2024

Curso "Godot 4 Shaders: Craft Stunning Visuals" de GameDev.TV

Sigo haciendo los cursos que me compré en el pack de Humble Bundle, y esta vez le ha tocado a "Godot 4 Shaders: Craft Stunning Visuals" de GameDevTV.

A diferencia de otros cursos de GameDevTV, este no está en Udemy, por lo que no te quedará otra que verlo en la plataforma de GameDevTV. Esto tiene un par de pegas: no tienes subtítulos en inglés y no tienes la posibilidad de acelerar la velocidad del video. Afortunadamente, el autor del curso tiene buena pronunciación, por lo que se le entiende todo, y habla con buena cadencia, así que no hay necesidad de acelerar.

El curso dura unas 5 horas y se enfoca a darte un primer baño en shaders, que te quite el miedo a profundizar por tu lado. Para eso, se estructura en tres partes:

  • Fundamentos de los shaders.
  • Shaders 2D, enfocada a crear efectos habituales en este tipo de juegos: flashes, paso a monocromo, disoluciones, enmascaramientos, scrolls y distorsiones.
  • Shaders3D. Enfocada a fijar las propiedades básicas de albedo, metallic, roughness y normal. Aunque no profundiza mucho, lo remata con un tutorial para hacer el efecto de agua.
Ya había hecho cursos de shaders en Unity, pero este era el primero enfocado a Godot, y debo decir que me ha gustado mucho. En los cursos que había hecho de shaders en Unity, iba repitiendo las acciones de los tutoriales, pero me quedaba la sensación de que en realidad no estaba entendiendo los conceptos generales. Básicamente, repetía las cosas, pero no sabía muy bien por qué las hacía. Afortunadamente, este ha sido el primer curso en el que no me ha pasado eso. El autor hace un esfuerzo por explicar conceptos más básicos, como las fases fragment o vertex, o qué son las coordenadas UV, y el esfuerzo se agradece muchísimo. Por primera vez he entendido por qué hacía las cosas.

Otra agradable sorpresa ha sido que no me ha importunado que el autor prescindiese de los visual shaders de Godot y se centrase en los shaders de código. A priori, me parecía que eso podría hacer más complicado el curso, pero ahora creo que lo ha simplificado bastante. Si has programado en C#, el lenguaje que usa Godot para el código de sus shaders resulta muy parecido. No te cuesta ni 10 minutos cogerle el tranquillo. A partir de ahí, me da la sensación de que los shaders en código quedan más concisos que sus equivalentes visuales, lo que me ha facilitado seguir el curso.

En lo referente a contenidos, el apartado de shaders 2D me ha parecido muy completo. Sin embargo, el de shaders 3D me ha parecido que se queda algo corto. En la parte de 3D se queda en lo más básico.

En todo caso, para un curso de 5 horas, creo que está muy bien y se hace con gusto. La inmediatez de Godot hace comodísimo seguir el curso. ¡Qué diferencia con Unity y sus recargas de dominio cada vez que rozas el editor!.

Así que nada: os recomiendo el curso.

26 mayo 2024

Curso "Godot 4 Multiplayer: Make Your Own Online Game" de GameDev.TV

Hace unos días que finalicé el curso que da título a este artículo y ya estoy listo para contaros lo que me pareció. 

Hice el curso en Udemy. También se encuentra en la plataforma propia de GameDevTV, aunque como ya tengo otros cursos de otras temáticas en Udemy prefiero seguir comprándomelos allí para tenerlos todo en un mismo sitio. Otra ventaja de Udemy es que suele contar con subtítulos en inglés, lo cual puede ser un auténtico salvavidas si el profesor tiene un acento muy cerrado. En teoría, la plataforma de GameDevTV debería permitir activar subtítulos, pero no he visto cómo en los pocos cursos que tengo allí.

En Udemy, el curso se anuncia prometiendo cubrir los siguientes temas:

  • Construcción de un juego online multijugador usando las librerías de Godot 4.
  • Crear mecánicas habituales en los juegos multijugador como baldosas interruptoras, objetos movibles, puertas a desbloquear, etc.
  • Sincronización de estas mecánicas a través de internet para permitir que los jugadores puedan interactuar entre sí y colaborar para superar los retos.
  • Usar la infraestructura de W4 Games para permitir que los jugadores puedan conectarse entre sí aunque no estén en la misma red local.
El juego que desarrolla para explicar estos principios es un sencillo plataformas 2D, en el que 2 jugadores podrán colaborar para alcanzar baldosas interruptoras que abran puertas y hagan aparecer puentes que permitirán acceder a zonas previamente inalcanzables. También se incluye la mecánica de abrir un cofre, para recoger una llave con la que abrir la llave de finalización de nivel. Como verás, el juego en si mismo es muy sencillo, pero permite cubrir una serie de mecánicas muy habituales en este tipo de juegos. 

Todo el primer bloque del curso, se dedica a darle forma al juego base. Si hace tiempo que no tocas Godot, te puede servir como repaso. Si nunca has tocado Godot, también te puede valer como tutorial introductorio.

En el segundo bloque es donde se empieza a implementar que los jugadores puedan conectarse entre sí, dentro de una misma red local. Al final resulta que, a Godot, le bastan 2 nodos especializados para ofrecer una experiencia multijugador completa. Todo un alarde de sencillez y elegancia que dice mucho de este engine. Entre esos 2 nodos especializados y las llamadas RPC desde el código, ya tienes todo lo que necesitas para montar tu juego online.

En el tercer bloque, el más largo, es donde se va explicando cómo aplicar los conceptos del bloque anterior para que las distintas mecánicas del juego se puedan sincronizar a todos los jugadores al mismo tiempo y que estos vean siempre el mismo estado del juego.

El curso implementa su código en GDScript, pero yo lo he seguido implementando mi código en C# y no he tenido problema alguno.

El profesor se explica muy bien, su pronunciación es buena y no divaga. El proyecto que ha elegido para sustentar sus explicaciones es lo suficientemente sencillo como para no estorbar por complejo, pero más que suficiente para cubrir las mecánicas más habituales que se suelen implementar en un plataformas. Al final del curso acabas teniendo una idea bastante buena de cómo articular un juego multijugador básico. 

Aun así, hay cosas mejorables. Para empezar, las herramientas multijugador de Godot (los 2 nodos y las llamadas RPC) se cubren de una manera bastante práctica, lo cual no está mal, pero me he quedado con las ganas de que profundizase en la parte teórica. Al final acabas intuyendo lo que hace cada cosa, pero no con la seguridad de saber lo que hace por dentro. Por ejemplo, se tratan las llamadas RPC y se usan ampliamente en el curso, pero me habría gustado que se detuviera más en ellas para explicar su funcionamiento general y cómo se utilizarían en otros contextos.

Otra cosa mejorable, es que a pesar de mencionar los servicios de W4 Games, en el curso no se les menciona para nada. Es cierto que en la letra pequeña advierten que esos contenidos no están disponibles aún y que llegarán más adelante, pero hace tiempo que W4 Games abrió su beta para que la gente probase sus servicios y esperaba que a estas alturas hubieran incluido esa parte en el curso, o que al menos hubiesen dicho cuándo lo harían. Nada de eso ha ocurrido y esa es la razón por la que no le he dado 5 estrellas en Udemy. Si finalmente, acaban incluyendo el bloque explicando cómo usar los servicios de W4 Games, revisaré al alza la puntuación.

A pesar de eso, el curso es muy recomendable, si bien no al precio completo con el que lo anuncian en Udemy y en GameDevTV. Mi consejo es que espera a que esté de oferta en alguna de esas dos plataformas para comprártelo. En GameDevTV no lo sé, pero en Udemy las ofertas son muy frecuentes, así que mi consejo es que no cometas la novatada de comprarlo por 50 o 90 € cuando es muy frecuente verlo ofertado por 10 o 15.

08 mayo 2024

"Programming Game AI by Example" por Mat Buckland

No es fácil encontrar buenos libros sobre inteligencia artificial (IA) aplicada a juegos. La mayoría no pasan del nivel introductorio y cuando llegan a explicar el algoritmo A*, lo consideran ya material avanzado y lo dejan ahí.

En mi opinión, la gran joya en esta materia es el "AI for Games" de Ian Millington, que combina unos contenidos vastísimos con unas explicaciones claras, detalladas y realmente pertinentes para el desarrollo de juegos. Desde que lo leí no me había encontrado con otra obra que se le pudiera comparar hasta que me topé con este al que le dedicamos el artículo. No en vano, el libro de Buckland fue una de las referencias de Millington.

Es cierto que "Programming AI by Example" no alcanza en amplitud de contenidos a "AI for Games", pero sí es cierto que los que trata lo hace con gran profundidad y claridad. También hay que reconocerle que trata varios temas ausentes de la obra de Millington, como la integración de sistemas de scripting en nuestros juegos (para simplificar el diseño de los algoritmos de IA) y el capítulo sobre agentes guiados por objetivos.

El libro está lleno de buenas figuras ilustrativas que permiten seguir las explicaciones. 

A diferencia de Millington, las implementaciones no las ilustra en pseudocódigo, sino en C++; lo que para mi gusto es un punto negativo porque no se trata precisamente del lenguaje más intuitivo del mundo. También es cierto que el libro acumula ya algunos años y, cuando se publicó por primera vez, la única alternativa para crear juegos era C++. En todo caso, las explicaciones son lo suficientemente extensas y claras como para que el entendimiento pleno del código no sea imprescindible.

Me ha llamado también la atención la insistencia en mostrar diagramas de UML de las jerarquías de clases de las diferentes implementaciones. Entiendo por qué lo hace y tampoco es que sobren, pero es algo que me ha hecho sonreír por lo pasado de moda que está. Ya no se suelen ver diagramas de UML en los libros.

A pesar de todas esas señales del tiempo transcurrido desde la publicación del libro, el campo de la IA para juegos no ha avanzado tanto como para dejar desfasado su contenido. Estos siguen estando vigentes. Si acaso, se echan en falta algunas herramientas que han surgido posteriormente como los árboles de comportamiento o el aprendizaje neuronal.

Conclusión: el libro me ha parecido excelente. Recomiendo que lo leas lectura  antes del de Millington. De esa manera, se puede potenciar su utilidad como pieza introductoria para facilitar la comprensión posterior del Millington y ampliar con este lo que deja pendiente el de Buckland. Con esta aproximación creo que cubrirás lo mejor que se ha escrito hasta el momento en el campo de la IA para juegos.