Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 11: Desarrollo completo de un videojuego de plataformas 2D.
Ensamblaje del nivel principal y pantallas de transición
Clase 52 de 60 · 60 min
También disponible en: EN
El flujo de juego y la arquitectura de pantallas
Hasta este punto del curso has construido mecánicas aisladas: un personaje con control físico preciso, colisiones con plataformas y tilemaps, enemigos que patrullan, monedas coleccionables, barras de vida e interfaces dibujadas en el evento Draw GUI. Sin embargo, un videojuego comercial no se compone de elementos desconectados o pruebas técnicas que se ejecutan directamente desde el editor; requiere un flujo integral (game flow) estructurado y coherente.
El flujo de juego describe el viaje continuo que experimenta el usuario desde que arranca la aplicación hasta que la cierra. Para que el jugador sienta una experiencia acabada, el software debe gestionar transiciones limpias: arrancar en una sala técnica de inicialización, presentar un menú principal funcional, dar paso al nivel jugable ensamblado con todos sus elementos interactivos y desembocar en pantallas de resolución (victoria o Game Over) que permitan reiniciar el ciclo sin fallos de memoria ni variables huérfanas.
En GameMaker, este recorrido se organiza mediante una secuencia ordenada de Rooms (salas) y objetos de control persistentes. Si cambiamos abruptamente de sala con una llamada directa e instantánea a la función interna room_goto(), el corte visual resulta tosco, interrumpe la inmersión y puede provocar artefactos gráficos si la nueva sala tarda un par de fotogramas en cargar sus texturas. Por esta razón, el ensamblaje profesional exige una capa intermedia de transición visual que enmascare el cambio de estado.

Planificación y jerarquía de Rooms en el gestor de recursos
El primer paso consiste en auditar el panel de recursos de GameMaker (Asset Browser). El orden en que las salas aparecen dentro de la sección 'Rooms' en el gestor de salas define la sala de inicio por defecto del proyecto. Aunque en desarrollo solemos ejecutar directamente la sala en la que estamos trabajando, la versión final compilada siempre ejecutará la primera sala listada de arriba hacia abajo.
Para este videojuego completo, estableceremos una estructura estricta compuesta por cinco salas esenciales:
| Nombre de la Room | Resolución base | Función principal | Objetos obligatorios |
|---|---|---|---|
| rm_init | 640 x 360 | Inicialización técnica de variables globales, fuentes y sistemas persistentes. | obj_game_controller, obj_transition |
| rm_menu | 640 x 360 | Presentación de título, instrucciones mínimas y botón interactivo para jugar. | obj_menu_ui |
| rm_level_1 | 1920 x 720 (o superior) | Nivel principal ensamblado con tilemaps, jugador, enemigos, cámara y metas. | obj_player, obj_camera, obj_hud, enemigos, meta |
| rm_game_over | 640 x 360 | Pantalla de derrota tras perder todas las vidas. Permite reintentar o volver al menú. | obj_game_over_ui |
| rm_win | 640 x 360 | Pantalla de victoria al alcanzar la meta final con vida. Muestra puntuación final. | obj_win_ui |
La sala rm_init tiene una duración de exactamente un fotograma. Su única misión es ejecutar el código de arranque en el evento Create de un objeto inicializador global y, de inmediato, llamar al menú principal. Esta técnica evita que declaremos variables globales repetitivas dentro de salas que se visitarán múltiples veces.
El sistema de transición visual: obj_transition
Para ejecutar transiciones suaves mediante un fundido a negro (fade out / fade in), crearemos un objeto controlador persistente llamado obj_transition. Un objeto persistente (Persistent marcado en sus propiedades) no se destruye al cambiar de sala; viaja intacto entre escenas conservando sus variables y estados activos.
El gestor de transición funcionará bajo una máquina de estados sencilla de tres fases: inactivo (idle), oscureciendo la pantalla actual (fade out) y aclarando la nueva pantalla (fade in). Utilizaremos el evento Draw GUI para dibujar un rectángulo negro que cubra toda la superficie de la interfaz, independientemente del movimiento o posición de la cámara del juego.
Crea el objeto obj_transition en tu Asset Browser, marca su casilla 'Persistent' y programa el siguiente bloque en su evento Create:
// Evento Create de obj_transition
// Control del fundido visual
alpha = 0;
fade_speed = 0.04;
target_room = noone;
// Estados: 0 = inactivo, 1 = oscureciendo (fade out), 2 = aclarando (fade in)
transition_state = 0;
// Asegurar que el objeto no se duplique si ya existe uno en memoria
if (instance_number(obj_transition) > 1) {
instance_destroy();
}El siguiente paso es programar la lógica de avance temporal en el evento Step. Cuando la transición se active (estado 1), el valor de alpha aumentará progresivamente. En el momento exacto en que la pantalla quede completamente negra (alpha >= 1), invocaremos la función room_goto() para cambiar la escena detrás del velo oscuro y cambiaremos al estado 2 para comenzar a aclarar la nueva escena:
// Evento Step de obj_transition
switch (transition_state) {
case 1: // Fase de oscurecimiento (Fade Out)
alpha += fade_speed;
if (alpha >= 1) {
alpha = 1;
if (target_room != noone && room_exists(target_room)) {
room_goto(target_room);
}
transition_state = 2; // Pasar a fase de aclarado en la nueva sala
}
break;
case 2: // Fase de aclarado (Fade In)
alpha -= fade_speed;
if (alpha <= 0) {
alpha = 0;
target_room = noone;
transition_state = 0; // Regresar a inactivo
}
break;
case 0: // Inactivo
default:
alpha = 0;
break;
}Para pintar el fundido sobre cualquier elemento visual del juego, emplearemos el evento Draw GUI. Al utilizar las funciones de dibujo de interfaz gráfica, nos desligamos de las coordenadas del mundo y de las matrices de la cámara:
// Evento Draw GUI de obj_transition
if (alpha > 0) {
var _gui_w = display_get_gui_width();
var _gui_h = display_get_gui_height();
draw_set_color(c_black);
draw_set_alpha(alpha);
draw_rectangle(0, 0, _gui_w, _gui_h, false);
// Reiniciar los valores de dibujo por defecto para no afectar otros objetos
draw_set_alpha(1.0);
draw_set_color(c_white);
}Para hacer este sistema accesible desde cualquier script o evento de cualquier objeto del proyecto, encapsularemos la llamada de transición dentro de una función global de conveniencia. Crea un Script llamado scr_transitions y añade:
/// @function transition_to(_target)
/// @description Inicia una transicion suave hacia la sala indicada.
/// @param {Asset.GMRoom} _target Sala de destino.
function transition_to(_target) {
if (instance_exists(obj_transition)) {
with (obj_transition) {
if (transition_state == 0) {
target_room = _target;
transition_state = 1;
}
}
} else {
// Fallback de seguridad si no se creo obj_transition
room_goto(_target);
}
}💡 Regla fundamental de dibujo en GML: cada vez que modifiques draw_set_alpha() o draw_set_color(), debes restaurar sus valores predeterminados (1.0 y c_white) al finalizar el bloque. De lo contrario, todos los objetos que se dibujen después heredarán la transparencia o el tinte, provocando fallos visuales en el HUD y en los sprites de los personajes.
Ensamblaje del nivel principal: capas, actores y desencadenantes
Habiendo programado el puente visual, pasamos al ensamblaje del nivel interactivo dentro de rm_level_1. En lecciones anteriores configuraste tilemaps y colocaste instancias individuales para pruebas. Ahora integraremos el escenario completo mediante una estructura estricta de capas (Room Layers) que garantice el orden visual correcto y optimice el rendimiento.
Abre rm_level_1 y configura en el inspector de capas el siguiente apilamiento jerárquico, de arriba (capa superior visual) hacia abajo (capa más profunda):
| Nombre de Capa | Tipo de Capa | Contenido y Propósito |
|---|---|---|
| Foreground_Details | Asset / Tile Layer | Ramas altas, cadenas o pilares que pasan por delante del jugador. |
| Instances_Controllers | Instance Layer | Controladores de nivel (obj_camera, obj_hud, generadores). |
| Instances_Player | Instance Layer | El objeto obj_player exclusivamente, asegurando su visibilidad. |
| Instances_Entities | Instance Layer | Enemigos patrulleros (obj_enemy), monedas (obj_coin), llaves, puertas. |
| Tiles_Collisions | Tile Layer / Instance | Mosaicos sólidos donde el jugador y los enemigos caminan y colisionan. |
| Tiles_Decoration | Tile Layer | Variaciones visuales sin colisión (grietas, hierba, musgo). |
| Background_Parallax | Background Layer | Paisajes lejanos con desplazamiento relativo de cámara. |
| Background_Base | Background Layer | Color de cielo base o textura lejana sólida. |
Para conectar el nivel con la pantalla de victoria o el siguiente nivel, necesitamos un objeto meta interactivo. Crearemos obj_door (o meta de nivel) con un sprite identificable de puerta o bandera brillante. Este objeto detectará cuando el jugador lo toque y activará el paso al desenlace.
En el evento Collision de obj_door con obj_player, implementamos la verificación:
// Evento Collision de obj_door con obj_player
// Si el jugador llega a la meta, bloqueamos su entrada y pasamos a victoria
with (other) {
can_move = false;
hsp = 0;
vsp = 0;
}
// Reproducir efecto de sonido de victoria si esta importado
if (audio_exists(snd_stage_clear)) {
audio_play_sound(snd_stage_clear, 10, false);
}
// Iniciar la transicion a la pantalla de victoria
transition_to(rm_win);Gestión de derrota: derivación a Game Over
La contraparte de la meta es la gestión de la muerte del avatar. En el Módulo 7 implementamos el sistema de vidas y salud. Cuando la salud o las vidas del jugador llegan a cero, no debemos reiniciar bruscamente la sala; debemos canalizar el fallo hacia la pantalla de fin de juego (rm_game_over).
En el objeto obj_player o en tu controlador de estado del jugador, dentro del script o evento donde se procesa la pérdida de la última vida:
// Fragmento dentro del script de dano o evento Step del jugador
if (global.player_lives <= 0) {
// Desactivar controles para congelar el estado
can_move = false;
// Reproducir jingle de Game Over
audio_stop_all();
if (audio_exists(snd_game_over)) {
audio_play_sound(snd_game_over, 10, false);
}
// Enviar al jugador a la pantalla de Game Over
transition_to(rm_game_over);
}Implementación de las pantallas finales y retorno interactivo
Las pantallas rm_menu, rm_win y rm_game_over comparten una naturaleza similar: son pantallas de interfaz estáticas o semiestáticas que esperan una pulsación del usuario para redirigir el flujo. Vamos a crear los dos controladores para las pantallas de victoria y derrota.
Manejo de variables globales y reinicio seguro de estado
Uno de los errores más frecuentes en los proyectos de desarrolladores principiantes ocurre cuando el jugador pierde una partida y decide reintentarla: las monedas recogidas ya no aparecen, la barra de vida inicia vacía o la cámara aparece desubicada. Este fenómeno sucede cuando el desarrollador confía únicamente en las funciones nativas room_restart() sin limpiar o reinstanciar las variables globales persistentes.
Para evitar inconsistencias, definiremos un script central de reinicio que llamaremos siempre que una nueva partida deba comenzar desde cero. Crea un script llamado scr_game_management y escribe la siguiente función:
/// @function start_new_game()
/// @description Inicializa los datos de progreso del jugador para una partida limpia.
function start_new_game() {
global.player_lives = 3;
global.player_health = 100;
global.player_score = 0;
global.coins_collected = 0;
global.checkpoint_active = false;
global.checkpoint_x = 0;
global.checkpoint_y = 0;
// Iniciar el viaje hacia el primer nivel mediante la transicion visual
transition_to(rm_level_1);
}Ahora, en el botón o texto interactivo 'Jugar' de tu objeto obj_menu_ui (diseñado en el Módulo 8), en lugar de llamar directamente a room_goto(rm_level_1), invocamos la función de saneamiento:
// Accion al seleccionar 'Jugar' en obj_menu_ui
start_new_game();Verificación del ciclo completo de principio a fin
Con todos los engranajes programados, el flujo operativo del videojuego debe ejecutarse sin fricción. La secuencia lógica es la siguiente:
| Paso | Origen | Acción desencadenante | Destino | Estado de persistencia |
|---|---|---|---|---|
| 1 | Ejecutable / F5 | Carga inicial de GameMaker | rm_init | Se crea obj_transition y variables globales. |
| 2 | rm_init | Automático en 1er fotograma | rm_menu | Menú principal visible con opciones interactivas. |
| 3 | rm_menu | Selección de 'Jugar' (Enter/Clic) | rm_level_1 | start_new_game() reinicia datos y fade out cubre el cambio. |
| 4A | rm_level_1 | Colisión de obj_player con obj_door | rm_win | Bloqueo de inputs, sonido de victoria, fundido a rm_win. |
| 4B | rm_level_1 | global.player_lives llega a 0 | rm_game_over | Bloqueo de inputs, sonido fúnebre, fundido a rm_game_over. |
| 5 | rm_win / rm_game_over | Pulsación de tecla asignada | rm_level_1 o rm_menu | Reinicio controlado o retorno al menú principal. |
Ejercicios prácticos de ensamblaje
Pon a prueba tu comprensión técnica implementando y verificando las siguientes tres actividades obligatorias en tu proyecto:
Ejercicio 1 (Práctica de programación): Añadir una cortinilla de color alternativo a la función de transición. Modifica obj_transition y la función transition_to() para aceptar un segundo argumento opcional que permita indicar el color del fundido (por ejemplo, c_white para un destello celestial al tocar la meta de victoria, y c_black para la muerte por Game Over). Verifica que el rectángulo en Draw GUI utilice la variable interna fade_color asignada en la llamada.
Criterio de comprobación 1: Al morir, la pantalla se oscurece con c_black; al tocar la puerta de victoria, se ilumina con un fundido a blanco impecable sin romper el ciclo del juego.
Ejercicio 2 (Ensamblaje y diseño de niveles): Monta en rm_level_1 una sección completa y funcional que incluya: punto de inicio del jugador, al menos un enemigo patrullando una plataforma suspendida, una hilera de tres monedas, un foso con púas que reste salud o vidas, y el objeto obj_door colocado en el extremo derecho. Asegúrate de que las capas de Tilemap y las capas de instancias mantengan el orden jerárquico documentado en la tabla de este capítulo.
Criterio de comprobación 2: El jugador puede recorrer el escenario completo sin caer a través de los bloques de colisión, los enemigos patrullan sobre su respectiva plataforma sin salirse y la cámara sigue al jugador suavemente hasta la meta.
Ejercicio 3 (Integración de flujo continuo): Realiza una prueba integral de bucle cerrado. Arranca el juego desde rm_init, navega al menú, inicia el nivel, pierde deliberadamente todas las vidas para verificar la llegada a rm_game_over, pulsa la tecla para reiniciar el nivel y verifica que la puntuación vuelva a cero y las vidas a 3. Posteriormente, supera el nivel tocando la meta para comprobar la pantalla rm_win y regresa finalmente al menú principal.
Criterio de comprobación 3: El ciclo puede repetirse indefinidamente sin que se dupliquen instancias de obj_transition, sin caídas de tasa de cuadros por segundo (FPS) y sin que queden controles bloqueados tras los reinicios.
Resumen
- El flujo de juego (game flow) conecta de forma armónica las etapas de inicialización, menú, nivel y pantallas finales, eliminando saltos bruscos.
- Un objeto persistente de transición (obj_transition) ejecutado en Draw GUI permite realizar fundidos a negro o blanco universales e independientes de la cámara.
- La primera sala listada en el Asset Browser debe ser una sala técnica (rm_init) destinada a configurar variables globales y llamar de inmediato al menú.
- El nivel principal (rm_level_1) debe ensamblarse siguiendo una rigurosa jerarquía de capas (Backgrounds, Tiles, Instancias y GUI) para asegurar el orden visual correcto.
- Las transiciones hacia rm_win y rm_game_over deben ir acompañadas del bloqueo de controles del avatar y de funciones de reinicio global (start_new_game()) para prevenir datos corruptos en partidas consecutivas.
Practica lo aprendido
Ejercicio 1 · 1 punto
¿Cuál es la función principal de marcar la casilla 'Persistent' (persistente) en las propiedades del objeto obj_transition?
Ejercicio 2 · 1 punto
Ordena cronológicamente los estados y acciones que ocurren dentro de obj_transition durante un ciclo completo de cambio de sala:
- 1El objeto se mantiene en reposo con transition_state igual a 0 (inactivo).
- 2El valor de alpha llega a 0 y transition_state regresa a 0 (inactivo).
- 3Se activa la fase de oscurecimiento (transition_state = 1) y el valor de alpha aumenta progresivamente.
- 4La pantalla queda totalmente negra (alpha >= 1) y se ejecuta room_goto(target_room).
- 5Comienza la fase de aclarado (transition_state = 2) en la nueva sala y el valor de alpha disminuye.
Ejercicio 3 · 1 punto
Relaciona cada elemento de la arquitectura del juego con su función específica descrita en la lección:
- rm_init
- obj_transition
- obj_door
- rm_game_over
Ingresa para guardar tu progreso — Ingresar