Esta entrada tiene como objetivo enseñar a utilizar Godot 4 para .NET a través de una serie de ejemplos sencillos. El motivo de esta publicación es que mi entorno de desarrollo integrado (IDE) para la programación de videojuegos preferido y me gustaría fomentar su uso. Y bueno, siendo sinceros, existen numerosos aspectos que abordaré en esta serie de entradas que habría deseado que me fueran explicados en su día.
En este primer capítulo me centraré únicamente en tres sencillos ejercicios: un “Hola Mundo”, un “Hola Mundo” en tres dimensiones con texto giratorio (la cámara va rotando alrededor del texto) y un sistema básico de novela visual (donde codificaremos unas sencillas librerías DLL para separar la solución por capas). A lo largo del capítulo se explicarán paso a paso las acciones a realizar, pero como habréis notado por el resumen de dichos ejercicios, la dificultad será incremental. No obstante, la solución a los ejercicios se encuentra disponible en mi perfil de GitHub, por si fuera necesario consultarlo para resolver dudas (https://github.com/LeHamsterRuso/Godot4.NetExamples).
Por cierto, aunque veáis que las capturas de pantalla de esta entrada corresponden a la versión de Mac de Godot 4, que sepáis que esta guía sirve también tanto para Windows, como Linux.
Material necesario:
- Tarjeta gráfica compatible con OpenGL 3.3 o superior o Vulkan 1.0 o superior.
- SDK .NET 8.0 o superior: https://dotnet.microsoft.com/es-es/download
- VSCode (https://code.visualstudio.com/download) o cualquier versión de Visual Studio compatible con el SDK 8.0 de .NET.
- Godot Engine - .NET: https://godotengine.org/download
- Muy importante bajarse la versión .NET (suele ser la segunda opción en la lista).
Primer ejemplo, el "Hola Mundo".
Objetivos:
- Mostrar un texto en pantalla.
- El texto debe de estar centrado.
- La ventana debe de ejecutarse maximizada.
- El tamaño del texto y su posición debe de adaptarse al tamaño de la pantalla.
Pasos:
Abre Godot .NET y en la lista de proyectos clica en "+ Crear" (arriba a la izquierda).
En la popup que se abrirá clica en "Examinar" para indicar dónde quieres guardar tu proyecto y ponle un nombre obvio, del estilo "HelloWorld", en rederizador selecciona el modo "compatibilidad", deja el resto de opcioens activadas por defecto y clica en "Crear".
Verás la ventana siguiente:
A la izquierda, en la pestaña de escena, selecciona "Escena 2D":Al hacerlo, la vista central cambiará (veremos una especie de canvas 2D en vez de un espacio euclidiano en 3D):
Se te abrirá una popup de creación de nodos. En la barra de buscar teclea "label" para que se valla filtrando la lista de componentes seleccionables. Selecciona "Label" y dale a "Crear".
Te saldrá algo parecido a esto:
Con tu componente "Label" seleccionado en la pestaña de "Escena" (a la izquierda), gira tu vista al inspector de propiedades que sale a la derecha. Este inspector te permite, básicamente, alterar las propiedades y atributos del componente que tengas seleccionado en ese instante. Verás que por defecto la primera propiedad propuesta para un label es el "Text" y que éste está vacío. Haz clic en la propiedad y teclea "Hello World" (se trata de un campo de texto editable).
Acto seguido, en la barra de filtrado de propiedades, busca la palabra "size", despliega el apartado "Theme Overrides" y el subapartado "Font Sizes". En Font Sizes, marca la opción y asigna el valor 32.
Tras cambiar el tamaño de la fuente notarás que el texto pasa a ser legible en el editor (área central de la ventana), pero por desgracia este no está centrado.
En el editor de escenas, selecciona el botón de modo movimiento. Esto nos permitirá desplazar componentes "a ojo".
Verás que a nuestro label le han salido dos flechas, una roja y una verde. Clica en él y arrastra el objeto hasta el centro del rectángulo (que por cierto sirve para demarcar el alto y ancho de la pantalla). Da igual si no queda perfectamente centrado, sólo busco enseñarte que el modo movimiento existe.
Dale al botón de "Play" que hay arriba a la derecha.
La primera vez que ejecutes un proyecto Godot te preguntará cual es su escena principal. Dale "Seleccionar actual" y guarda la escena en curso con un nombre lógico (por ejemplo "HelloWorld.tscn").
Si lo has hecho bien deberías de ver una ventana que dice "Hello World".
Ahora vamos a hacer que quede más profesional. Cierra la ventana, haz clic derecho en el objeto Node2D, dale a "+ Añadir Nodo Hijo...", busca el componente llamado "CanvasLayer" y añádelo. Haciendo "drag&drop", arrastra el objeto "Label" dentro de tu nuevo "CanvasLayer". La arborescencia de la escena debería de quedar así: Node2D > CanvasLayer > Label.
Selecciona el label, clica en en el icono de ajustes de anclaje (en el centro) y en la mini popup que se abrirá pulsa en "completo" (un cuadrado blanco).
Verás que ahora ahora tu label ocupa todo el área de la pantalla, pero por desgracia no está centrado. Ve al inspector (a la derecha), borra la barra de filtros (habíamos escrito en ella "size") y selecciona "Center" como valor de las propiedades "Horizontal Alignment" y "Vertical Alignment". Verás que ahora nuestro texto sí que sale perfectamente centrado en la ventana.
Ahora vamos a hacer que nuestro ejemplo ocupe toda la pantalla y no una simple ventana. Accede a "Proyecto> Configuración del Proyecto".
Se te abrirá una nueva popup con toda la configuración de tu proyecto. En la pestaña de "General", selecciona "Visualización > Ventana" y en ella selecciona el modo "Maximized" en la sección de "Tamaño" y "Canvas_items" en el de "Estirar". El "Maximized" nos permite que la ventana arranque maximizada por defecto, mientras que el "Canvas_items" nos permite que todo lo que esté en el canvas conserve sus proporciones cada vez que la ventana sea redimensionada.
Segundo ejemplo, el "Hola Mundo" en 3D.
Objetivos:
- Mostrar un texto 3D en pantalla.
- La cámara debe de girar alrededor del texto.
- Manejo de assets.
- Creación de scripts.
- Vínculo de componentes entre el IDE y los scripts.
- Introducción al método _Process.
Ahora que hemos entrado en harina iré dando las directrices más rápido. Si tienes maña con Blender 3D puedes crear un objeto 3D de tipo "Text" que diga "Hello World", aplicarle un modificador de "Solidify", ponerle un material de textura negra o gris y exportarlo como objeto fbx o glTF. En la práctica los objetos glTF me dan menos problemas. No obstante, también puedes directamente descargar mi asset 3D desde GitHub (https://github.com/LeHamsterRuso/Godot4.NetExamples/blob/master/002---helloworld-3d/Assets/3D/HelloWorld.glb).
Una vez tengas tu asset 3D o hayas conseguido el mío, crea un nuevo proyecto y llámalo HelloWorld3D. En la pestaña de "Escena", selecciona "Escena 3D" y en el navegador de recursos (abajo a la izquierda) crea una carpeta "Assets", una subcarpeta "3D" y dentro mete el objeto 3D que has creado o que te has descargado.
Haciendo uso de "drag&drop", selecciona el fichero "HelloWorld.glb" y arrástralo dentro de la escena.
Selecciona el objeto "Node3D" en la pestaña de escena y añádele un objeto WorldEnvironment. Después selecciónalo y en el inspector (a la derecha) créale un nuevo "Environment". Despliega "Background", selecciona en "Mode" el valor "Custom Color" y asigna algún color claro.
Ahora añade una cámara 3D ("Camera 3D") a tu Node3D y haz uso de las flechas direccionales para centrar el texto (mueve la cámara, no el texto). Si te fijas, en el inspector de la cámara, puedes ver qué es lo que se está viendo a través de ella.
Dale a "Play", deberías de ver tu "Hello World" en 3D.
Ahora vamos a hacer que la cámara gire al rededor del texto. En tu navegador de recursos crea una carpeta llamada Scripts, haz clic derecho en ella y crea un nuevo script. Te saldrá una nueva popup, en ella selecciona el lenguaje "C#" (muy importante, por defecto te selecciona el "GDScript"), llámalo CameraMovement.cs y haz clic en "Crear".
Ábrelo con tu editor de código preferido (recomiendo VSCode) y edítalo para que quede así:
Si te fijas en el código, el "Export" espera que el editor le pase un objeto que será por el cual tiene que rotar nuestra cámara. Al mismo tiempo, el método "_Process" se disparará en cada frame, indicandónos en la variable "delta" el lapso de tiempo que ha pasado entre frames.
Guarda el fichero, vuelve a Godot y dale a la llave inglesa (en la versión de .NET es necesario compilar antes de poder asignar parámetros a nuestro script). Después, selecciona el script que hemos creado y arrástralo a la cámara. Notarás que a nuestra cámara le saldrá un nuevo icono con forma de pergamino (esto nos indica que el objeto tiene ligado un script).
Selecciona la cámara, ve al inspector y en la sección de "CameraMovement" selecciona "HelloWorld" como "Target".
Dale al botón de "Play", ahora tendrías que ver el texto de "Hello World" girando.
Tercer ejemplo, una novela visual.
Objetivos:
- Carga dinámica de recursos en escena.
- Creación de librerías DLL.
- Uso de .gdignore.
- Reemplazo de tipo de componentes.
- Separación por capas (front, back, datos).
- Introducción a los métodos _Ready e _Input.
Crea un nuevo proyecto y llámalo VisualNovelWithDll (por ejemplo). En la pestaña de "Escenas" selecciona una escena en 2D y añade al Node2D un CanvasLayer. A ese CanvasLayer, añádele a su vez un un TextureRect (con ajuste de anclaje "completo", para que ocupe todo el área) y dos labels (uno para el nombre del personaje y otro para su diálogo). Edita las propiedades de las labels para cambiar el tamaño de sus fuentes (por ejemplo 24 para el nombre el NPC y 32 para el diálogo) y juega con los ajustes de anclaje para dejar el layout a tu gusto. Edita también la propiedad "Expand mode" del TextureRect a "Fit Width Proportional", para que las imágenes que le carguemos luego pueden ser re-escaladas de forma proporcional.
En el navegador de recursos, crea una carpeta "Assets" y dentro de ella otra llamada "Backgrounds". En ella pondremos tres imágenes, una donde veamos un escenario vacío, otro con un primer plano de un personaje feliz y otro donde el mismo personaje se vea triste. Deben de llamarse respectivamente: 001.png, 002.png y 003.png. Igual que antes, si no puedes dibujarlos, puedes recuperarlos de mi GitHub para hacer el ejercicio (https://github.com/LeHamsterRuso/Godot4.NetExamples/tree/master/003---visualnovelwithdll/Assets/Backgrounds). Crea también una carpeta Scripts y añade en ella un nuevo script de tipo C# llamado "Dialogs.cs". La arborescencia del proyecto debería de quedarte así:
Abre VSCode, accede al explorador (el icono de los dos documentos a la izquierda y clica en "Crear proyecto de .NET".
En la ventanita que se te abrirá empieza a teclear "biblioteca" y selecciona "Biclioteca de clases". Acto seguido selecciona un subdirectorio de tu proyecto Godot donde guardarla, asígnale el nombre DLL y selecciona "crear un nuevo proyecto".
Una vez creado, en la raíz de la carpeta dll crea un fichero vacío llamado ".gdignore". Esto hará que el editor de Godot ignore todos los ficheros que añadas en tu carpeta de DLL.
En nuestra DLL, crearemos una clase llamada "Dialog.cs" que contendrá únicamente 3 strings: Uno para definir el fondo de imagen, otro para definir el nombre de quién habla y otro para mostrar el texto que dirá.
También crearemos una clase principal que se llamará Game, de naturaleza estática, y que contendrá una lista de diálogos (para hacerla puedes renombrar la clase Class1 que se crea por defecto):
Si os fijáis en el código de la clase, esta dll realmente carga los datos a mostrar y gestiona la lógica de navegación entre diálogos. Ahorra cierra el VSCode, vuelve a Godot y abre el script de Dialogs.cs que habíamos creado. Verás que la arborescencia del explorador del VSCode ha cambiado y que ahora te ha abierto directamente la carpeta donde tienes todo el proyecto de Godot. Ahí deberías de identificar dos ficheros "csproj": El fichero principal del juego y el de la DLL que acabamos de crear. Edita el primero y haciendo uso de las primitivas "ItemGroup" y "ProjectReference", añade manualmente una dependencia de la DLL en el proyecto principal. Debería de quedarte algo así, en función de cómo hayas montado la arborescencia:
De hecho, si os fijas cómo se VSCode ha creado el proyecto DLL, la organización queda un poco fea (con un subdirectorio DLL donde metemos las clases. Podemos jugar con la arborescencia a nuestro gusto, siempre que actualicemos los "csproj" en consecuencia y que manejemos con coherencia los namespaces de nuestras clases.
En esa captura he desplazado el contenido DLL/DLL en DDL/ y he actualizado el csproj raíz para apuntar a la ruta actuqalizada del csproj de la DLL.
Para verificar que no hemos roto nada, puedes hacer uso de la terminal de VSCode para compilar la solución y la DLL a través del comando "dotnet build":
Bueno, ya falta poco. Ahora ya puedes usar tu DLL en el proyecto de Godot... De hecho, como la DLL sale referenciada en el fichero csproj raíz, para hacer uso de sus clases nos bastará con utilizar el "using DLL" en la cabecera de nuestros scripts.
Ahora abre el fichero "Scripts> Dialogs.cs" que habíamos creado antes, dentro de Godot, y edítalo para recuperar los tres componentes que hemos empleado en nuestra escena (un texturerect y dos labels) y alimentarlos en función de la lógica que hemos implementado en nuestra DLL. Para hacer eso tenemos dos métodos de Godot que nos serán útiles: El método _Ready, que se lanza al arrancar una escena, y el método _Input, que se lanza cuando se detecta alguna interacción por teclado, ratón o gamepad.
De hecho, lo que haremos será crear una función FillScreen que se encargará de alimentar los labels y el texture rect y llamaremos a ésta desde los métodos _Ready (para acceder al primer diálogo) y _Input (para ir avanzando en los diálogos).
Vuelve a Godot, dale al icono de la llave inglesa para compilar, arrastra el script al nodo raíz y asocia los dos labels y el texture rect al script:
Dale a "Play", deberías de ver la siguiente escena en bucle (al ir al último diálogo volvemos al primero):
Volvamos a nuestro script y aplica los siguientes cambios:
Ahora si te fijas el comportamiento cambia, el texto se va mostrando letra a letra, como en una novela visual comercial:
Ahora vamos a aplicar un fondo básico a nuestro texto. Para ello duplica los dos labels que tenemos (selecciona uno, cópialo en el portapapeles, ve al nodo raíz y pégalo... y haz lo mismo con el otro). Acto seguido selecciona una de las copias y haciendo clic derecho cambia su tipo a "ColorRect". Esto nos permite, de forma simple, clonar las coordenadas y dimensiones del componente original.
Ahora, a través del inspector, asigna a tus dos ColorRect un color azulado con un valor de Alpha (letra A) cercano al 80, según tu gusto. El "alpha" es la capa de transparencia del componente, un valor cercano al 0 lo convierte en invisible y el valor 255 (el máximo) lo convierte en opaco. Un valor entre el 0 y el 255 significa que nuestro componente será más o menos transparente (el nivel de transparencia depende de si está más cercano al valor 0 o al 255).
Acto seguido desplaza los dos labels dentro de sus respectivos ColorRects: Esto nos permitirá que el texto se vea por encima del fondo. En caso contrario, el texto nos saldría azulado, debido a que la capa de transparencia se le estaría aplicando por encima.
Dale a play, ahora los textos deberían de verse con nuestro fondo básico:
Ahora vamos a reorganizar la DLL para separar la lógica del modelado de datos. La idea será que nuestra DLL pase a llamarse "Core" y crear una segunda DLL que llamaremos "Data". Esto nos permitirá, por ejemplo, separar el motor del modelo de datos, haciendo que el día de mañana sea más fácil migrar nuestro sistema a una base de datos o a un sistema de carga por archivos (json, csv, etc).
Para ello, vamos a duplicar la carpeta DLL (copiar+pegar en el explorador de ficheros), renombraremos el original a Core, renombraremos la copia a Data, tocaremos los tres csproj para corregir la nueva arborescencia, borraremos el Dialog.cs del proyecto Core, borraremos Game.cs de Data y actualizaremos los namespaces de ambos proyectos. Como Core dependerá de Data, tendremos también que referenciarlo en su csproj y hacer uso de "Using Data;" en la clase Game.cs.
Y ahora, para acabar con la entrada, nos queda sólo sanear la clase Game.cs de la dll "Core". Si nos fijamos, en ella hacemos una iniciación de datos mockeados, lo ideal sería llevarnos esa iniciación en la librería "Data", puesto que en el futuro queremos recuperar datos a través de algún tipo de datasource, hacia un fichero físico o una base de datos.
Para facilitar esta tarea de migración, atentos a la jugada, nos crearemos una clase estática Mock dentro de la DLL "Data", que contendrá por ahora nuestro guion.
No hay comentarios:
Publicar un comentario
Si te ha gustado la entrada o consideras que algún dato es erróneo o símplemente deseas dar algún consejo, no dudes en dejar un comentario. Todo feedback es bienvenido siempre que sea respetuoso. También puedes contactarme por estas redes sociales https://linktr.ee/hamster_ruso si lo consideras necesario.