Como depurar nuestro juego
Hasta ahora hemos visto como programamos los objetos, mediante acciones Drag and Drop o escribiendo código GML. Cuando creamos juegos sencillos es muy fácil corregir los posibles errores, basta con ir ejecutando nuestro proyecto, probar y ver que posibles fallos ocurren para depurarlos.
Pero conforme nuestro proyecto se hace más grande y se va complicando, es muy fácil cometer algunos errores, y necesitamos afinar la búsqueda de estos posibles errores que podamos tener: podemos tener un objeto que hace algo que no queremos y nos cuesta ver en que momento falla, usar funciones incorrectamente, mezclar algunas variables…
Todo esto no tiene porque detectarlo el editor de Scripts. Por ejemplo, si escribimos
if (a ==0) { a++;
Si no hemos cerrado con llave el if
, esto lo detecta fácilmente el editor y nos avisa. Estoy hablando de errores cuando ejecutamos nuestro juego, y que nos ocupará practicamente el 80% del tiempo de programación. ¡Hay que corregir todos los errores que se nos ocurran!
Cuando pasa esto, es necesario usar las herramientas de depuración de Game Maker Studio. Tenemos varias opciones que nos puede ayudar a ver esas inconsistencias en nuestro código, desde funciones que sólo sirven para detectar estos errores hasta un modo depuración al ejecutar nuestro juego.
Vamos a ver todas las opciones que nos ofrece Game Maker Studio para poder corregir errores.
La ventana de error al ejecutar un juego
Es posible que a veces nos hayamos encontrado una ventana como ésta cuando ejecutamos nuestro juego
a mi me ocurre mucho cuando uso una variable que todavía no se ha creado (por ejemplo en el evento Step). Parece que el mensaje no aclara mucho que está pasando, así que vamos a descifrar o traducir que significa.
En el primer párrafo nos dice que es un error FATAL, indicándonos el objeto, el evento y la acción dónde ocurre. De esta manera podemos buscar el fallo.
Luego vemos con más detalle dónde puede ser el error: nos indica que es al dar valor a una variable, nos indica la instancia, con un número mayor a 100000), y la línea en concreto dónde ocurre.
¿Como corregimos el error? Saber dónde falla puede indicar que se puede corregir en esa misma línea, pero no tiene porque ser así. En este ejemplo, se soluciona creando la variable en el evento Create.
Al final de la ventana tenemos tres botones. El botón Abort, para cancelar la ejecución de nuestro juego y así corregir el error, el botón Copy para copiar el error al portapapeles y el botón Clear que borra el mensaje de error de la ventana.
Este mismo error se reproduciría cuando creamos un archivo ejecutable, sea cual sea el dispositivo, Windows, iOS, Android, etc. Sería fácil de corregir, pero podemos tener otros error no tan sencillos de ver. Por ejemplo, un problema de rendimiento al grabar un fichero INI en un evento Step o Draw. Para ello tendremos que ejecutar nuestro juego en modo debug, pulsando el correspondiente botón o con F6. Veremos que en nuestro juego aparece una serie de información adicional que podemos usar para ayudarnos en que momento el rendimiento puedo fallar.
Comprobando el rendimiento de nuestro juego
Al ejecutar en modo depuración, en la esquina superior izquierda tenemos información extra, que lo veremos en forma de barra.
En la barra vemos la cantidad de memoria, la potencia dela CPU y la GPU (la parte gráfica) del juego en uso. La barra se compone de varios colores y de un tamaño que va variando. El tamaño indica el tiempo que requiere uno de los parámetros comentados, no hace falta aclarar que cuanto mayor sea la barra peor rendimiento tendremos. Vamos a analizar ahora los colores:
- El verde indica que hay un periférico con entrada y salida. Por ejemplo, el teclado, ratón, un pad o incluso funciones de red.
- El rojo indica la velocidad de actualización del evento Step.
- El amarillo es el tiempo del evento Draw.
- El naranja es el tiempo de actualización de la ventana de depuración si está activa.
- El blanco es el tiempo de espera de la GPU.
- El cian para el procesamiento de textos.
- El gris es el tiempo que necesita para borrar la pantalla en cada evento Draw.
- El rojo oscuro es el tiempo que tarda la GPU para borrar las imágenes en memoria.
Aparte de la barra con los diferentes colores que podamos encontrar, también tenemos más valores cerca de esa barra.
- Los FPS (frames por segundo). Es una referencia de la velocidad de tu juego. Aunque hablamos de la velocidad, no tiene nada que ver con la velocidad de la room que definimos en el editor de rooms, y que por defecto está a 30 steps por segundo, es decir, un valor fijo. Podemos ver que este valor puede variar mucho dependiendo de la sesión del juego en sí: instancias, coste por objeto, etc. Cuanto mayor sea este valor, más fluído irá el juego. La ventana del compilador, que hablaremos más adelante, informará del valor mínimo, máximo y promedio de este valor cuando cerramos el juego.
- Intercambio o swaps de texturas. Entre paréntesis tenemos un primer valor que indica el número de veces que nuestro juego intercambia texturas de imágenes. Ya hemos comentado que Game Maker Studio almacena todos los sprites y fondos en texturas para optimizar la carga, podemos decir en cada sprite y fondo a que textura queremos que se almacene. Yo suelo agruparlas por su uso en rooms o niveles (enemigos y fondos, opciones de menú…), más las texturas generales (botón de pausa, personaje principal, etc.). Si hay un cambio de nivel que aparece un enemigo nuevo, Game Maker carga una página de textura nueva con ese enemigo.
Con la versión Standar no se puede gestionar las páginas de texturas (en Windows no se aprecia un cambio de rendimiento), pero en versiones superiores si podemos hacerlo (y puedes notar cambios en otros dispositivos, por ejemplo en móviles).
Conclusión: si vemos un valor alto en este parámetro, se puede valorar gestionar las páginas para optimizar su rendimiento. - Lotes de vértices. Este número es muy técnico, es la cantidad de veces que enviamos gráficos a la GPU (por lotes). Imaginemos la GPU como si fuese la memoria RAM, una vez hemos cargado los gráficos podemos usarlos rápidamente, pero si se necesitan otros debemos volver a cargar los nuevos. Hacer eso puede afectar a la optimización del juego, así que este número tiene que ser muy bajo, igual que la opción anterior. Cada vez que se cambia una fuente, un color o algo que afecta de manera global a como pintamos en nuestro juego hay que enviar un nuevo lote, así que lo suyo es limitar ese número todo lo que se pueda.
Como véis, es posible ver, de una manera muy gráfica, dónde podemos tener problemas de rendimiento. Además, tenemos la función show_debug_overlay()
os permite activar esta barra en cualquier momento, así que la podríamos programar para mostrarla en algún lugar determinado.
Eliminar caché de los recursos compilados
Antes de pasar a las opciones avanzadas de depuración, me gustaría aclarar un concepto. Cuando ejecutamos un juego, vemos en la ventana de compilación que genera los diferentes recursos: SRT, PAGE, conversión de audios, etc. Es decir, todos los recursos de nuestro proyecto, sprites, sonidos, propiedades de los objetos, etc. sufren una transformación de datos para poder ser ejecutado.
Esto se hace para aumentar el rendimiento, pero es verdad que cuando transforma esos datos tarda un poco en hacerlo, así que Game Maker Studio solo lo hace cuando cree que es necesario. Por ejemplo, si no añadimos más sprites, cada vez que ejecutamos nuestro juego no volverá a cachear los sprites porque ya están generados.
Pero es posible que nos encontremos que esa caché se haya quedado obsoleta o corrupta: un sonido que se reproduzca mal, los gráficos no se ven bien, etc. Antes de pensar cualquier cosa, lo mejor es borrar la caché para que vuelva a generarla. Para borrarla, tenemos un botón en la barra de herramientas con forma de escoba .
Lo recomendable es hacerlo de vez en cuando mientras vamos probando, y siempre al crear el ejecutable final.
Módulo de depuración
Si queremos encontrar y corregir errores de nuestro código GML, debemos usar el módulo de depuración. Esta herramienta nos permite parar la ejecución en una parte concreta del código, consultar las variables del objeto (propias y del sistema) o las globales, etc. La ventana tiene una apariencia parecida a ésta:
Vamos a ver los diferentes botones y opciones de menú.
Menú y botones del depurador
En la parte superior nos encontramos con 4 opciones de menú. Son éstas:
- Archivo. Podemos volver a conectar con el módulo, por si se pierde la conexión, o salir de la ventana del depurador sin tener que cerrar el juego.
- Ventana. Podemos gestionar las diferentes subventanas que se crean en la depuración, incluso guardar el diseño de las ventanas actual para cargarlo en un futuro.
- Depuración. Se puede parar o reproducir el juego, a cambiar los puntos de interrupción que tengamos configurado (sobre los puntos de interrupción hablaremos más adelante).
- Ayuda. Simplemente consulta la ayuda oficial de la web de YoYoGames, el apartado de HelpDesk.
También tenemos una serie de botones en una barra de herramientas, con los que podemos controlar la depuración del código. Los botones son:
- Ejecutar. Si el juego está parado en algún punto, podemos volver a ejecutarlo hasta la siguiente interrupción.
- Pausa. Para pausar el juego.
- Reiniciar. Reinicia el juego desde el principio.
- Paso a paso por instrucciones. Si hemos puesto un punto de interrupción, al pulsar este botón avanzamos en cada línea de instrucción. Por ejemplo, si estamos a punto de llamar un script, saldríamos del código dónde estamos ahora y entraríamos en la primera línea del script.
- Paso a paso por procedimientos. Avanzaríamos por cada bloque de código. En el ejemplo anterior, no entraríamos en el script y seguiríamos en el evento que estamos actualmente.
- Paso a paso para salir. Siguiendo con el ejemplo, una vez hemos entrado en el script, si pulsamos este botón lo ejecutaría completamente y volveríamos al evento desde dónde lo hemos llamado.
- Actualizaciones en tiempo real. Puedes activar y desactivar las actualizaciones en tiempo real de la información de la depuración. Todos los valores que vemos, variables globales, instancias y demás podemos ver que valor tienen sin tener que parar la depuración.
Configuración de ventanas de aviso
Cuando estamos depurando necesitamos ver todos los detalles de nuestro juego, así que queremos toda la información de la que disponemos. El depurador contiene una serie de ventanas de control que podemos ajustar en el formulario. Podemos configurar estas ventanas para que muestre la que nos interese, y añadir las que creamos necesarias. Para abrir un nuevo aviso, hacemos click con el botón derecho y elige un nuevo Tipo.
Como existen varios tipos de aviso, es posible que algunos los queramos ver con más área del formulario que otros. Todas las ventanas son ajustables y acoplables, podemos ponerlas en la parte central, a un lado, solapadas con pestañas, etc. Veamos las opciones disponibles:
- Código fuente. Una de las ventanas más usadas a la hora de depurar, muestra el código fuente de nuestro juego en ejecución. Puede tener varias pestañas abiertas, dependiendo de los objetos y eventos que afecte. Si hemos añadido un punto de interrupción antes de ejecutar, en el momento que se ejecute el evento de ese código se mostrará, y veremos una indicación que se ha parado ahi mismo.
En ese momento podemos seguir ejecutando nuestro juego con los botones que hemos mencionado anteriormente, línea a línea, ejecutar hasta el siguiente punto, etc. Podemos añadir otros puntos de interrupción en ese momento del código, y si se hace sobre la misma mínea de uno que ya existe lo eliminará (ya sea pulsando la tecla <F9> o con el menú contextual del botón derecho).
También podemos abrir otras pestañas de código desde la ventana de instancias, si la tenemos abierta. - Globales. Desde aquí podemos ver las variables globales que han sido declaradas, así el sistema sabe que existen, y sus valores. Podemos establecer el tipo de datos sobre cada una de ellas (esto lo veremos más adelante).
- Locales. También nos interesa las variables locales, así que esta ventana muestra las propiedades del step actual, la instancia actual dónde se está ejecutando el código (self) y la instancia other si fuese el caso (estamos en un evento de colisión, sino vale lo mismo que self). Si entramos en un bloque
with
, entonces el apartado deself
cambiará de instancia por la del bloquewith
correspondiente.
Dentro de cada instancia, podemos encontrar las variables que hemos declarado o las variables del sistema (como por ejemplo, image_index, x, y, etc…). Las variables que vemos fuera, son las que se han declarado comovar,
porque estas variables son visibles para ese bloque de código del evento, así que no pertenecen a ningún objeto (instancia) y por eso se muestran aparte (recordad que se eliminan cuando salimos de ese evento). También estas variables podemos establecer un tipo de dato.
- Watches o seguimiento. He puesto el nombre en inglés porque no tengo muy clara su traducción. Esta ventana sirve para seguir la pista de una variable en concreto. Puede ser de cualquier tipo, una global, una dentro de una instancia, etc. Es una manera rápida de visualización para no tener que buscar en la ventana de globales ni locales. Con un click podemos establecer su tipo de datos, eliminarlo o borrar todo el seguimiento. Con doble click podemos modificar su valor, así que podríamos cambiar de variable editando una de las marcas.
- Instancia. Muy parecida a locales, vemos todas las variables de una instancia en el momento de su ejecución.
- Salida. Muestra la misma ventana que la ventana de compilación de la interfaz de Game Maker Studio. Si tenemos mensajes de control que se muestran en la ventana de compilación también lo veremos aquí como
show_debug_message()
.
- Todas las instancias. Si nos interesa ver todas las instancias que hay en la room, con sus datos correspondientes, disponemos de esta ventana.
- Instancia seleccionada. Podemos ver los datos de la instancia que queramos. Simplemente le damos al botón de pausa y hacemos click a la instancia que nos interesa (tiene que ser visible). De esta manera podemos seguir la pista a cualquier instancia que nos interese.
- Pila de llamadas. Aquí vemos la pila de todas las llamadas, desde los eventos a los scripts, y ver cuál es el momento desde dónde se llamó.
- Buffer. Esto es solo para cuando hemos creado un buffer en nuestro juego (por ejemplo un juego multijugador). Tenemos opciones para ver las distintas columnas del buffer, cambiar el tamaño de cada una, etc.
- Profiling o perfil. Aquí podemos ver una serie de estadísticas que nos puede ayudar a ver posibles problemas de rendimineto de nuestro proyecto. Veremos una visión general de como se está ejecutando nuestro juego: tiempo necesario para ejecutar un evento, cuantas veces se llama, etc. Así podemos saber cuál es el objeto, y evento, que consume más recursos y poder actuar sobre él.
En la parte superior de la ventana, vemos una serie de datos para poder interactuar y controlarlos. Vemos un botón para activar o desactivar el perfil y el número total de steps que se han producido, el tiempo total que ha trascurrido. Este valor se puede cambiar a tiempo promedio de cada paso, si seleccionamos el botón que hay al lado (marcado con una τ). También tenemos las columnas Time y Calls, que es el tiempo y las llamadas de cada evento (decidiendo si queremos ver el total o el promedio). También existe el botón “ejecutar el juego a velocidad máxima” (con el icono de una persona corriendo) para quitar la dependencia de la room con el debugador y ver así los FPS reales.
Las otras dos opciones que vemos son para controlar que exactamente queremos ver en esta ventana.
La primera opción es para decidir como queremos ver los datos, si en forma de árbol, evento y funciones utilizadas en la pilla de llamadas. Desde aquí también podemos ver su código fuente haciendo doble click en el evento. Si vemos el icono (+), significa que hay más llamadas a funciones que podemos visualizar. La otra forma de ver los datos es simplemente mostrando todos los eventos ordenados.
La segunda opción es para decidir que datos queremos ver. Si ponemos “GML”, veremoe eventos, funciones y scripts que se ejecutan en cada paso, si ponemos “engine”, solo veremos las opciones que son necesarias para ejecutar el juego, que suelen ser las más conflictivas (de ahí esta opción). También podemos decidir ambas, diferenciandolas con colores para saber de cuál estamos tratando.
Podemos exportar estos datos para analizarlos en un fichero, haciendo click con el botón derecho. Tendremos un fichero en formato .CSV, que se puede abrir en un programa de hojas de cálculo como Excel. Como véis, esta ventana es bastante completa dentro del depurador, de ahí su explicación exhaustiva. - Gráfico. Podemos ver con un gráfico los recursos de nuestro rodenador que estamos utilizando en nuestro juego. Podemos ver el uso de memoria (puede aumentar al crear muchas instancias), como el código GML gasta CPU, o ambos. Tenemos un combo para mostrar estos datos.
- Estado del renderizado. Aquí podemos ver cosas que afectan al evento Draw, como elegir un colo rd erelleno o el alpha del surface. Este dato solo se puede ver si pausamos el juego.
- Surfaces / Texturas. Sabemos, y sino lo digo ahora, que Game Maker Studio crea páginas de textura para los gráficos. Desde esta ventana podemos ver exactamente las texturas generadas en esa ejecución, para decidir si hay un gráfico que se puede cambiar a otra página.
Tipos de datos de las variables
Hemos comentado anteriormente que las variables que visualizamos puedes ser datos un poco más complejos que un simple número o un texto. Por ejemplo, si tenemos una variable que contiene una instancia, seguramente veamos un número 100004, o superior. Si es una array o una lista, veremos un número sencillo, como puede ser un 2.
Esto es debido a como Game Maker Studio almacena la información, que no tiene claro que dato está guardado, ya que, podemos decir, es un puntero o una referencia a dónde está almacenado de verdad (hablaremos en un futuro de estas cosas). Si hacemos click derecho en una variable, veremos una lista de las posibles opciones que puede ser realmente esa variable.
Sí, debemos decírselo nosotros, y el depurador interpretará el dato que le hemos dicho. Cuando hemos seleccionado el tipo de datos, podemos entonces navegar con las nuevas opciones que hemos elegido. Por ejemplo, si es una instancia, nos aparecerá las variables de objeto del sistema y las que hemos declarado, si es una lista, veremos un árbol de datos, etc. Al cambiar un tipo de datos de una variable, se actualizará en todas las ventanas que tengamos activas.
Breakpoints o puntos de interrupción
Los puntos de interrupción es el lugar del código, o una acción que hemos arrastrado, que queremos se pare la ejecución. Así podemos ver que está pasando en ese momento. Una vez hemos ejecutado el juego, se detendrá cuando pase por el código, y veremos que aparece el módulo de depuración.
No hace falta decir que no se parará si no pasa por esas líneas de código. Por ejemplo, si creamos el objeto y tenemos un evento Step, pensamos que seguro que pasará por ahí. Pero si el punto lo hemos puesto dentro de una cláusula if
, hasta que no se cumpla la condición no se detendrá el programa.
Para añadir un punto de ruptura, podemos hacerlo con la tecla <F9> sobre la línea actual, o con la opción que aparece en el menú al hacer click con el botón derecho (la que pone toggle breakpoint). Esto sirve tanto para código GML como para una acción, aunque en una acción veremos código al depurar.
Los puntos de interrupción se mantienen mientras probamos nuestro juego, excepto los que añadimos en la ventana de depuración. Si queremos borrar todos los puntos que hemos creado, una manera rápida es en el menú, la opción clear all breakpoints.
Conclusión
Hemos visto al detalle como funciona el módulo de depuración de Game Maker Studio. Tiene una gran cantidad de ventanas y herramientas para saber en todo momento los datos que necesitamos ver, y así corregir posibles errores, ya sean de programación o rendimiento. No hace falta decir que yo lo uso habitualmente. ¿Crees que se puede mejorar de alguna manera?
Un artículo excelente y muy completo David, sin duda valdrá la pena tenerlo a mano 🙂
La verdad, repasando todas las opciones aprendí cosas que nunca usaba 🙂
El módulo de debug anterior me gustaba mucho, y me costó acostumbrarme al nuevo porque no veía algunas opciones que tenía el anterior (como visualizar las variables mientras se ejecuta). Pero sólo eran opciones escondidas.