Kabosu - Creando cosas
Publicado: 2024-08-02
Etiquetas: Godot, Juegos, Proyectos
En este artículo explico cómo he creado un pequeño minijuego con Godot que, supuestamente, sirve para entrenar la visión.
Hacía meses que no tocaba Godot. Siempre que tengo un parón de este tipo se me olvida cómo funciona el lenguaje GDScript y tengo que hacer algún tutorial para refrescar la memoria. Esta vez, para no volver a hacer el tutorial 2D me propuse programar un minijuego sencillo desde cero. Se me ocurrió reproducir uno de los ejercicios del juego Sight Training de Nintendo DS (conocido como Flash Focus en Estados Unidos). En este ejercicio aparece un número durante unas décimas de segundo y a continuación hay que escribirlo. Si acertamos el siguiente número será más largo y por tanto más difícil de reconocer en el escaso tiempo que está en pantalla. No estoy 100% seguro de si era así porque hace una década que lo jugué pero al menos la mecánica me servía para practicar cosas de Godot.
En este juego hay varios estados diferentes con transiciones muy sencillas entre ellos.
Me puse a buscar si había alguna forma sencilla de hacer una máquina de estados en Godot y encontré este artículo de la documentación oficial: State design pattern. Era una poco más complicado de lo que yo necesitaba pero copié el código adaptándolo a mis necesidades.
La idea básica es tener una clase base State
y luego una subclase para cada estado del juego. En mi caso StartState
, ShowState
, etc. Con la función change_state
se van creando y destruyendo instancias cuando se cambia de estado.
Cuando terminé de copiar el código me di cuenta de una cosa: no funcionaba en mi Godot 4.2.2. En ese momento me fijé que la página de la documentación que estaba leyendo era de Godot 3.2 y algunas cosas como las referencias a funciones habían cambiado desde entonces. Estuve buscando pero no parecía haber una guía equivalente en la documentación para máquinas de estados en la última versión de Godot. Extraño. En todo caso el código no era muy complicado así que lo adapté como buenamente pude.
En la guía crean una función setup
que se usa para que cada estado tenga una referencia a la función change_state
.
func setup(change_state, animated_sprite, persistent_state):
self.change_state = change_state
self.animated_sprite = animated_sprite
self.persistent_state = persistent_state
En un estado puedes escribir por ejemplo change_state('show')
para cambiar al estado ShowState
.
Yo no conseguí que la referencia funcionase en mi versión de Godot así que simplemente moví la función al nodo raíz de mi escena y lo llamo desde el estado con self.persistent_state.change_state('show')
. Un poco más largo pero me sirve.
Estos son los nodos de mi escena. Son todo etiquetas de texto para escribir excepto un Timer y una gráfica de los que hablaré dentro de un poco. En cada estado voy mostrando los textos que necesite y ocultando el resto.
La implementación de cada estados son unas pocas líneas. Por ejemplo este el código completo del estado Show
. Este estado muestra el número durante 6 décimas de segundo y luego pasa al estado Type. Es el único estado en el que se usa el Timer.
extends State
class_name ShowState
var timer
func _ready():
self.persistent_state.numberLabel.set_visible(true)
self.persistent_state.continueLabel.set_visible(false)
timer = self.persistent_state.timer
timer.set_one_shot(true)
timer.start(0.6)
func _process(_delta):
if timer.is_stopped():
self.persistent_state.change_state('type')
En el estado final Check comprobaba si NumberLabel
(el número aleatorio) era igual a ResultLabel
(el número introducido por la persona que está jugando). Si son iguales aumenta la variable size
y pasa al estado Start
para volver empezar con un número más grande. Si son distintos size
pasa a tener el valor 1.
El juego original, Sight Training, se basada en practicar un poco cada día para ir mejorando la visión así que pensé en añadir una gráfica que fuera mostrando hasta qué longitud había llegado en cada partida.
Añadí un estado Main
que funcionaba como menú principal. Salía al principio del juego y entre partidas y mostraba el nombre del juegos "Numbers" y la gráfica de progreso.
Estuve un par de horas implementando el dibujado de la gráfica. Tenía todos los resultados guardados de la lista Global.results
así que solo tenía que dibujar líneas de altura proporcional a cada resultado. Usando draw_line
, draw_circle
, etc iba dibujando todos los elementos de la gráfica. Hacerlo bien no es una tarea sencilla. Hay que mirar primer cuál es el resultado máximo conseguido para saber la escala vertical. También cuántas partidas ha jugado para saber la horizontal. La longitud y posición de las líneas dependerá de estas escalas. A continuación se puede ver el resultado que conseguí:
No estaba del todo mal pero no conseguí escribir la leyenda de la gráfica ni las marcas verticales de 5, 10, etc. draw_text
no mostraba nada por más que probara cosas. Al final, al no saber cómo mostrar el texto y viendo que mi código era bastante guarro decidí ver si había alguna extensión disponible en la librería de assets para crear gráficas de forma sencilla. Y la había: encontré una llamada Graph2D de LD2Studio. La añadí a mi proyecto y en unos minutos tenía la tenía funcionando. Edité un poco el código para cambiar el color con el que dibujaban los ejes para hacerlo consistente con la paleta de mi juego.
He subido el juego itch.io por si alguien quiere jugarlo via web. Lo he llamado Vision Numbers porque ya tenía un proyecto (oculto) con el nombre Numbers. Debería funciona en cualquiera de los navegadores populares.
De momento considero el juego como finalizado aunque si en un futuro le decido tiempo me gustaría cambiar algunas cosas. Quizá implementar alguno de los otros ejercicios de Sight Training para hacer el juego más completo.