Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 8: HUD, interfaces, menús y sistema de pausa.
Uso del evento Draw GUI para elementos de interfaz
Clase 36 de 60 · 50 min
También disponible en: EN
Introducción al renderizado de interfaces y el problema de la cámara
A lo largo de los módulos anteriores hemos construido los cimientos de nuestro videojuego de plataformas: un personaje que salta y responde a colisiones sólidas, enemigos con patrullas programadas y objetos recolectables distribuidos a lo largo del nivel. Sin embargo, para que el jugador comprenda su estado dentro del juego, necesita retroalimentación visual inmediata. Es aquí donde entra el concepto de HUD (Heads-Up Display) o interfaz de usuario durante la partida: indicadores de puntos de salud, vidas restantes, contador de monedas y tiempo transcurrido.
Un error común al dar los primeros pasos en GameMaker consiste en dibujar los textos de puntuación o los iconos de vidas utilizando el evento estándar Draw tradicional (evento de dibujo del mundo). Cuando el nivel es estático y no tiene una cámara en movimiento, este método parece funcionar de manera aceptable. No obstante, tan pronto como el jugador camina hacia la derecha, la cámara se desplaza por el nivel y los elementos de la interfaz quedan rezagados o desaparecen fuera de la vista del usuario, permaneciendo anclados a las coordenadas espaciales del mapa en lugar de flotar sobre el cristal de la pantalla.
Para resolver este problema arquitectónico, GameMaker incorpora una capa de renderizado independiente denominada capa GUI (Graphical User Interface) y un evento especializado para gestionarla: el evento Draw GUI. En esta lección aprenderás a dominar este evento, comprenderás en profundidad la separación matemática entre el espacio de la Room y el espacio de pantalla, y sentarás las bases técnicas para todo el sistema de interfaces de nuestro proyecto.
Coordenadas de Room frente a Coordenadas de GUI
Para programar interfaces de manera predecible y profesional, es fundamental distinguir con precisión los dos sistemas de coordenadas que coexisten simultáneamente dentro del motor gráfico de GameMaker: el espacio de coordenadas del mundo (Room Space) y el espacio de coordenadas de la pantalla (GUI Space).
El espacio de coordenadas de la Room es el plano bidimensional donde residen físicamente las instancias del juego. Si un nivel mide 3840 píxeles de ancho por 1080 píxeles de alto, un bloque ubicado en la posición x = 2000, y = 800 siempre estará localizado en ese punto geográfico del mapa. Cuando una cámara enfoca la posición x = 1800, dicho bloque se verá en pantalla; si la cámara se mueve a x = 0, el bloque quedará completamente fuera de la vista. Las coordenadas de la Room están sujetas a la posición de la cámara, el ángulo de rotación de la vista y los factores de ampliación o reducción (zoom).
Por el contrario, el espacio de coordenadas de la GUI representa un plano bidimensional fijo que se superpone a todo lo que la cámara está enfocando, como si dibujáramos directamente con un rotulador indeleble sobre el cristal del monitor. En este sistema, la posición (0, 0) siempre corresponde a la esquina superior izquierda visible de la ventana del juego, sin importar en qué rincón del mapa infinito se encuentre el personaje principal ni hacia dónde esté apuntando la cámara.

| Característica | Evento Draw Estándar (Room Space) | Evento Draw GUI (GUI Space) |
|---|---|---|
| Punto de origen (0, 0) | Esquina superior izquierda del mapa del nivel completo. | Esquina superior izquierda de la pantalla visible o ventana. |
| Afectado por la cámara | Sí. Si la cámara se mueve, rota o hace zoom, los dibujos se desplazan. | No. El plano permanece completamente fijo independientemente de la cámara. |
| Orden de ejecución | Se dibuja durante la renderización del mundo (capas de instancias). | Se dibuja al final del ciclo de renderizado, por encima de todo el mundo. |
| Propósito principal | Personajes, plataformas, efectos ambientales, enemigos. | Marcador de vidas, texto de puntuación, minimapas, menús emergentes. |
| Escalado y resolución | Hereda la escala de píxel de la Room y de la Viewport. | Controlable de forma independiente con display_set_gui_size(). |
El ciclo de renderizado y el orden de los eventos de dibujo
GameMaker ejecuta un bucle de juego estructurado en fases muy rigurosas. Dentro de cada fotograma (frame), tras procesar las entradas del teclado y ratón (Begin Step, Step, End Step) y las colisiones físicas, el motor inicia la fase gráfica. Comprender el orden cronológico en que se disparan estos eventos evita que los elementos gráficos se solapen incorrectamente.
Durante la etapa de dibujo, GameMaker recorre los siguientes eventos secuenciales:
1. **Pre Draw:** Se ejecuta antes de procesar ninguna superficie, utilizado en técnicas avanzadas de shaders y buffers de renderizado. 2. **Draw Begin:** Inicio del renderizado del mundo, útil para fondos personalizados antes de que aparezcan las instancias. 3. **Draw (Evento estándar):** Aquí se dibujan todas las instancias visibles en la habitación según el orden de profundidad de las capas (Layers). Si un objeto tiene asignado un sprite y no posee código en el evento Draw, GameMaker ejecuta automáticamente la función draw_self(). 4. **Draw End:** Se ejecuta cuando todas las instancias y mosaicos del mundo han sido dibujados en la superficie de la habitación. 5. **Post Draw:** Se procesa sobre la superficie final antes de enviar la imagen a pantalla. 6. **Draw GUI Begin:** Marca el inicio de la capa de interfaz de usuario. 7. **Draw GUI:** Es el bloque central donde programamos la interfaz. Todo lo dibujado aquí se estampa por encima de todo el contenido del juego generado en los eventos Draw previos. 8. **Draw GUI End:** Último evento de renderizado, ideal para transiciones a negro, filtros de pantalla completa o cursores de ratón personalizados.
💡 Regla de oro: Jamás coloques la lógica de movimiento, detección de colisiones o modificación de vida de los personajes dentro de ningún evento Draw ni Draw GUI. Los eventos de dibujo están destinados exclusivamente a la representación visual. La lógica del estado del juego debe permanecer intacta dentro de los eventos Step.
Configuración de la resolución y escala de la GUI
De manera predeterminada, GameMaker establece el tamaño de la capa GUI con base en las dimensiones de la primera Room o de la ventana del juego. No obstante, en un juego de estética retro como el que desarrollamos, trabajar con una resolución de juego nativa baja (por ejemplo, 480x270 o 640x360) proyectada en un monitor moderno de 1920x1080 píxeles puede provocar dos escenarios diferentes según la dirección artística deseada:
Primer escenario: Interfaz con píxeles nítidos y grandes (Pixel-Perfect HUD). En este enfoque queremos que el texto y los iconos compartan exactamente el mismo tamaño de píxel que nuestro personaje y los bloques del escenario. Para lograrlo, forzamos que el lienzo de la GUI coincida exactamente con la resolución interna de la cámara.
Segundo escenario: Interfaz de alta definición (Hi-Res HUD sobre pixel-art). En otros títulos, los desarrolladores prefieren que el juego sea pixel-art pero los textos, números y contadores se lean en alta definición suave a 1080p. Esto se logra asignando una resolución de GUI mayor que la resolución interna del nivel.
Para gobernar este comportamiento con precisión matemática disponemos de tres funciones fundamentales de GML Code:
// Establece la resolución matemática del lienzo de la interfaz
display_set_gui_size(640, 360);
// Obtiene el ancho actual del lienzo de la GUI
var _gui_ancho = display_get_gui_width();
// Obtiene el alto actual del lienzo de la GUI
var _gui_alto = display_get_gui_height();Al definir explícitamente `display_set_gui_size(640, 360)`, garantizamos que sin importar si el usuario juega en una pantalla pequeña de laptop o en un monitor panorámico de 4K, nuestras coordenadas de pantalla siempre operarán en una cuadrícula virtual conocida: la posición `x = 640` siempre será el borde derecho absoluto de la pantalla visible, y `y = 360` el borde inferior.
Creación del objeto de control de la interfaz (obj_hud)
Para mantener una arquitectura de proyecto limpia y modular, nunca debemos encargar al propio personaje jugador (`obj_jugador`) la responsabilidad de dibujarse a sí mismo y a la vez pintar la barra de salud, los marcos decorativos y los contadores en pantalla. Si el jugador muere o su instancia se destruye temporalmente al reiniciar un punto de control, toda la interfaz desaparecería de golpe.
La mejor práctica de desarrollo consiste en crear un objeto controlador independiente. Crearemos un objeto sin sprite asignado denominado `obj_hud` (o `obj_control_interfaz`) que se encargará exclusivamente de leer las variables globales o de instancia y renderizar la interfaz.
Paso 1: Configurar el evento Create de obj_hud
Abrimos el explorador de recursos (Asset Browser), hacemos clic derecho sobre la carpeta **Objects**, seleccionamos **Create -> Object** y le asignamos el nombre `obj_hud`. En el panel de propiedades del objeto, añadimos el evento **Create** e introducimos el siguiente código GML:
/// Evento Create de obj_hud
// Fijamos el tamaño virtual de la interfaz gráfica
// Utilizaremos 640x360 para mantener concordancia retro con nuestra cámara
display_set_gui_size(640, 360);
// Márgenes estéticos para ubicar los elementos separados del borde de la pantalla
margen_horizontal = 16;
margen_vertical = 16;
// Variable de amortiguación visual para suavizar barras o animaciones futuras
alpha_interfaz = 1.0;Paso 2: Implementar el evento Draw GUI
En el mismo `obj_hud`, pulsamos el botón **Add Event**, desplegamos la categoría **Draw** y seleccionamos con atención **Draw GUI** (asegúrate de no elegir el evento Draw estándar). Añadimos el siguiente bloque de código documentado:
/// Evento Draw GUI de obj_hud
// Verificamos si la instancia del jugador existe en la escena para evitar errores
if (!instance_exists(obj_jugador))
{
exit; // Si no hay jugador (por ejemplo, durante una pantalla de carga), salimos del evento
}
// Configuramos las propiedades de dibujo de texto
// Restaurar los estados de dibujo es una buena práctica indispensable
draw_set_font(-1); // Utiliza la fuente predeterminada del sistema
draw_set_halign(fa_left); // Alineación horizontal a la izquierda
draw_set_valign(fa_top); // Alineación vertical arriba
// 1. Dibujar sombra del texto para garantizar legibilidad sobre cualquier fondo
draw_set_color(c_black);
draw_text(margen_horizontal + 1, margen_vertical + 1, "PUNTOS: " + string(global.puntos));
draw_text(margen_horizontal + 1, margen_vertical + 21, "MONEDAS: " + string(global.monedas));
// 2. Dibujar el texto principal en color blanco y amarillo
draw_set_color(c_white);
draw_text(margen_horizontal, margen_vertical, "PUNTOS: " + string(global.puntos));
draw_set_color(c_yellow);
draw_text(margen_horizontal, margen_vertical + 20, "MONEDAS: " + string(global.monedas));
// 3. Dibujar iconos de vidas en la esquina superior derecha de la pantalla
var _gui_ancho = display_get_gui_width();
var _x_vidas = _gui_ancho - margen_horizontal;
var _y_vidas = margen_vertical;
// Configuramos alineación a la derecha para este bloque
draw_set_halign(fa_right);
draw_set_color(c_white);
draw_text(_x_vidas, _y_vidas, "VIDAS: " + string(global.vidas));
// 4. Restauración obligatoria de propiedades de dibujo por defecto
draw_set_color(c_white);
draw_set_halign(fa_left);
draw_set_valign(fa_top);Gestión de fuentes y alineación de dibujo en la interfaz
Uno de los conceptos que más confunde a los desarrolladores principiantes en GameMaker es la naturaleza del estado global del sistema de dibujo (Draw State). Cuando llamas a funciones como `draw_set_color(c_red)` o `draw_set_halign(fa_center)`, no estás aplicando esa propiedad solo a la siguiente línea de código: estás modificando una máquina de estados global compartida por todas las instancias del juego.
Si cambias la alineación horizontal a `fa_center` en `obj_hud` y no la restauras a `fa_left` al terminar tu código, cualquier otro objeto en la habitación que ejecute `draw_text()` en su propio evento Draw asumirá que debe centrar su texto, provocando comportamientos erráticos en diálogos, etiquetas de enemigos o menús.
Las funciones de alineación tipográfica
GameMaker ofrece constantes integradas muy claras para alinear textos respecto a las coordenadas (x, y) proporcionadas a `draw_text()`:
- **Alineación horizontal (`draw_set_halign()`):** - `fa_left`: La coordenada x representa el borde izquierdo del texto (valor predeterminado). - `fa_center`: La coordenada x representa el centro horizontal exacto de la cadena de texto. - `fa_right`: La coordenada x representa el final o borde derecho del texto. Fundamental para anclar puntuaciones a la esquina derecha de la pantalla sin importar cuántos dígitos crezca el número.
- **Alineación vertical (`draw_set_valign()`):** - `fa_top`: La coordenada y es la línea superior de la caja tipográfica. - `fa_middle`: La coordenada y se ubica en el centro vertical de los caracteres. - `fa_bottom`: La coordenada y coincide con la línea base inferior del texto.
Ejemplo práctico: Barra de energía dibujada con formas primitivas en Draw GUI
En el Módulo 7 implementamos la variable de salud del jugador (`hp` y `hp_max`). En lugar de mostrar un simple número aburrido, vamos a programar en el evento Draw GUI una barra de salud visual estilizada con bordes de color oscuro, fondo de daño y relleno reactivo dinámico utilizando funciones de dibujo primitivas 2D.
Para este ejercicio práctico, utilizaremos las siguientes funciones geométricas:
- `draw_rectangle(x1, y1, x2, y2, outline)`: Dibuja un rectángulo con relleno o solo contorno según el valor booleano indicado en el último parámetro. - `draw_rectangle_color()`: Permite dibujar rectángulos con gradientes de color.
/// Evento Draw GUI de obj_hud (Sección Barra de Salud)
if (instance_exists(obj_jugador))
{
// Obtenemos los valores de salud del jugador
var _salud_actual = obj_jugador.salud;
var _salud_maxima = obj_jugador.salud_max;
// Calculamos el porcentaje normalizado de vida (rango de 0.0 a 1.0)
// Usamos clamp para asegurarnos de no dibujar una barra negativa o sobredimensionada
var _porcentaje = clamp(_salud_actual / _salud_maxima, 0, 1);
// Definición de las coordenadas de la barra en la pantalla
var _bx1 = 16; // Margen izquierdo
var _by1 = 45; // Ubicada debajo de los textos de puntuación
var _ancho_barra = 120;
var _alto_barra = 12;
var _bx2 = _bx1 + _ancho_barra;
var _by2 = _by1 + _alto_barra;
// 1. Dibujar fondo gris oscuro de la barra vacía
draw_set_color(c_dkgray);
draw_rectangle(_bx1, _by1, _bx2, _by2, false);
// 2. Dibujar el relleno dinámico de salud
// El ancho del relleno es proporcional al porcentaje de salud restante
var _relleno_x2 = _bx1 + (_ancho_barra * _porcentaje);
// Seleccionamos un color reactivo: verde si está sano, rojo si le queda poca vida
var _color_salud = c_lime;
if (_porcentaje < 0.3)
{
_color_salud = c_red;
}
else if (_porcentaje < 0.6)
{
_color_salud = c_yellow;
}
draw_set_color(_color_salud);
draw_rectangle(_bx1, _by1, _relleno_x2, _by2, false);
// 3. Dibujar borde exterior negro para enmarcar la barra
draw_set_color(c_black);
draw_rectangle(_bx1 - 1, _by1 - 1, _bx2 + 1, _by2 + 1, true);
// Restauración de color
draw_set_color(c_white);
}Analicemos minuciosamente cómo se comporta este bloque dentro del juego: si nuestro personaje sufre daño por el contacto de un enemigo (evento programado en el Módulo 7), la variable `salud` de `obj_jugador` descenderá. Instantáneamente, en el siguiente fotograma, el evento Draw GUI calculará la nueva variable local `_porcentaje`. Al multiplicarla por `_ancho_barra`, el rectángulo verde se encogerá suavemente hacia la izquierda. Y lo más importante: si el jugador camina 3000 píxeles hacia el este del nivel, la barra permanecerá inamovible exactamente en las coordenadas (16, 45) de la esquina superior izquierda de tu monitor.
Depuración y resolución de problemas frecuentes
Al trabajar con la capa GUI por primera vez, es habitual encontrarse con ciertos comportamientos imprevistos. A continuación, revisamos las anomalías más comunes y cómo solucionarlas con rigor profesional:
1. **La interfaz desaparece al colocar el objeto en la Room:** Comprueba si la propiedad `Visible` de la instancia o del objeto `obj_hud` en el editor está desmarcada. Si un objeto tiene la casilla `Visible` desactivada, GameMaker omite por completo todos los eventos de dibujo (tanto Draw como Draw GUI).
2. **El texto o sprites se ven borrosos o deformados:** Esto ocurre cuando la relación de aspecto (aspect ratio) configurada en `display_set_gui_size()` no coincide con la proporción de la ventana o de la cámara del juego. Por ejemplo, si tu juego renderiza en 16:9 (como 640x360 o 1920x1080) y fuerzas un tamaño GUI de 800x600 (proporción 4:3), los elementos aparecerán estirados horizontalmente.
3. **El HUD tiembla al moverse el jugador:** Este defecto visual clásico ocurre cuando el programador intenta crear una interfaz fija en el evento Draw tradicional sumando la posición de la cámara: `draw_text(camera_get_view_x(cam) + 20, ...)`. Debido a que la cámara actualiza su posición en momentos sutilmente diferentes respecto al ciclo de renderizado, se produce un desfase de un fotograma (jittering) que hace temblar el texto. Trasladar el código directamente al evento Draw GUI elimina este problema por completo, ya que la GUI no depende de ningún cálculo matemático de la cámara.
Ejercicios prácticos
Realiza las siguientes tres actividades para consolidar el dominio del espacio GUI antes de avanzar hacia el diseño integral del HUD en la próxima clase.
**Ejercicio 1: La prueba empírica del espacio relativo (Programación)** Crea un objeto de prueba llamado `obj_test_coordenadas`. En su evento Draw estándar, dibuja un círculo de color rojo en la posición (100, 100). Luego, en su evento Draw GUI, dibuja un círculo de color azul también en la posición (100, 100). Ejecuta el juego, haz que el jugador camine hacia adelante por el nivel con la cámara siguiéndolo y observa el comportamiento de ambos círculos. - *Criterio de comprobación:* El círculo rojo debe quedarse anclado a las baldosas iniciales de la habitación y desaparecer conforme la cámara avance. El círculo azul debe permanecer permanentemente visible e inmóvil en la esquina superior izquierda de tu monitor.
**Ejercicio 2: Posicionamiento dinámico dependiente de resolución (Programación)** Implementa en el evento Draw GUI de `obj_hud` un indicador centrado en la parte inferior de la pantalla que muestre el texto "NIVEL 1 - BOSQUE VERDE". No debes escribir coordenadas fijas en píxeles rígidos (como 320, 340). En su lugar, debes calcular la coordenada horizontal central y la vertical inferior utilizando las funciones dinámicas `display_get_gui_width()` y `display_get_gui_height()`, junto con la alineación tipográfica correspondiente. - *Criterio de comprobación:* Al cambiar el tamaño del lienzo en el evento Create mediante `display_set_gui_size(640, 360)` o cambiarlo a `display_set_gui_size(960, 540)`, el texto del nombre del nivel debe mantenerse perfectamente centrado en el borde inferior sin desplazarse hacia un lado ni cortarse.
**Ejercicio 3: Diagnóstico y depuración de estados gráficos (Análisis)** Supón que tras agregar un evento Draw GUI para dibujar un mensaje parpadeante de advertencia con `draw_set_color(c_red)` y `draw_set_alpha(0.5)`, notas que todos los bloques de tus plataformas y el sprite de tu personaje en el mundo del juego ahora se ven tintados de color rojo semitransparente. - Explica detalladamente por qué ocurrió este error en el pipeline de GameMaker. - Escribe el fragmento de código de corrección que restablezca los estados de renderizado adecuados al final del script de la interfaz. - *Criterio de comprobación:* Identificar la persistencia global del estado del motor gráfico y aplicar `draw_set_color(c_white)` y `draw_set_alpha(1.0)` al terminar la ejecución del evento.
Resumen
- El espacio de la Room se rige por la geometría del nivel y se encuentra sujeto a las transformaciones, rotaciones y zooms de la cámara.
- El espacio GUI representa un plano bidimensional superpuesto directamente sobre la pantalla de visualización, con origen inmutable en la esquina superior izquierda visible (0, 0).
- El evento Draw GUI se ejecuta al final de la cadena de renderizado, garantizando que los elementos de interfaz se dibujen siempre por encima de fondos, capas de baldosas e instancias del juego.
- display_set_gui_size(ancho, alto) permite controlar con exactitud la resolución virtual del plano de interfaz, independizando la densidad gráfica de la UI del tamaño físico de la ventana.
- Las propiedades gráficas globales como draw_set_color(), draw_set_alpha() y draw_set_halign() deben restaurarse siempre a sus valores por defecto al culminar el evento para prevenir efectos secundarios indeseados en el resto del proyecto.
Practica lo aprendido
Ejercicio 1 · 1 punto
¿Por qué se recomienda crear un objeto controlador independiente (como `obj_hud`) para la interfaz en lugar de programar su dibujo dentro de `obj_jugador`?
Ejercicio 2 · 1 punto
Ordena cronológicamente los siguientes eventos de renderizado según el ciclo de dibujo de GameMaker, desde el primero que se ejecuta en el fotograma hasta el último:
- 1Draw Begin
- 2Draw (Evento estándar)
- 3Draw GUI
- 4Draw GUI End
- 5Post Draw
Ejercicio 3 · 1 punto
Para definir la resolución matemática del lienzo de la interfaz gráfica a unas dimensiones fijas, se debe invocar la función (640, 360). Además, según la regla de oro del motor, la lógica de movimiento y colisiones debe permanecer en los eventos y nunca colocarse en eventos de renderizado.
Ingresa para guardar tu progreso — Ingresar