Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 10: Diseño de niveles, cámaras y progresión.
Configuración de la cámara seguidora en GameMaker
Clase 46 de 60 · 50 min
También disponible en: EN
Introducción al sistema de visión en videojuegos 2D
Hasta este punto del curso, la mayoría de nuestras pruebas se han desarrollado en escenarios contenidos donde la acción transcurre dentro de los límites visibles de una única pantalla fija. Sin embargo, en el diseño clásico de juegos de plataformas (plataformeros 2D de estilo retro como Super Mario World, Sonic the Hedgehog o Celeste), el mundo del juego es sustancialmente más grande que la ventana de visualización del jugador. Para que el jugador pueda explorar cavernas subterráneas, cumbres montañosas o largos recorridos llenos de obstáculos, el motor debe decidir con precisión milimétrica qué porción del escenario proyectar en la pantalla en cada instante.
El sistema encargado de esta tarea es la cámara virtual. Lejos de ser un simple adorno, la cámara es una herramienta narrativa y mecánica fundamental: determina el ritmo de juego, proporciona anticipación frente a los peligros, previene saltos a ciegas (los temidos 'blind jumps') y comunica sensaciones físicas mediante sacudidas, aceleraciones y encuadres dramáticos. En GameMaker, el manejo de la visión se basa en una arquitectura de tres capas: la Room (escenario), la Camera (lente matemática) y el Viewport (puerto de visualización o monitor).
Fundamentos: Rooms, Cameras y Viewports
Para evitar confusiones habituales entre principiantes, es imprescindible clarificar la distinción entre estos tres conceptos fundamentales de GameMaker:
| Concepto | Definición | Función principal en el juego | Ejemplo práctico de dimensiones |
|---|---|---|---|
| Room (Sala) | El espacio físico global del nivel donde existen las instancias, capas y colisiones. | Contiene todo el mundo explorable por el jugador de principio a fin. | Ancho: 3840 px, Alto: 1080 px |
| Camera (Cámara) | Una estructura matemática que define una posición (X, Y) y un tamaño de encuadre rectangular dentro de la Room. | Determina exactamente qué porción del mundo está siendo 'grabada' en cada fotograma. | Ancho: 480 px, Alto: 270 px |
| Viewport (Puerto de Vista) | La región física de la ventana del sistema operativo o pantalla donde se proyecta la imagen captada por la cámara. | Escala la porción grabada por la cámara a la resolución nativa del monitor del usuario. | Ancho: 1920 px, Alto: 1080 px (Escala 4x uniforme) |
Imagina que la Room es una maqueta gigante de una ciudad en miniatura. La Camera es una cámara de cine que colocas sobre un trípode o un dron por encima de la maqueta, grabando un área de 480 por 270 milímetros. El Viewport es el televisor del salón donde sintonizas la señal que emite esa cámara de cine. Si la cámara se desplaza hacia la derecha, la imagen del televisor cambiará, aunque la maqueta completa permanezca inmóvil en la habitación.

Configuración nativa desde el Editor de Rooms
GameMaker ofrece un mecanismo integrado sin código para asignar una cámara seguidora directamente desde la interfaz gráfica del Inspector de Salas. Aunque este método tiene limitaciones en proyectos comerciales avanzados, es la forma más rápida de comprender el funcionamiento interno del subsistema de vistas.
Para activar este sistema paso a paso en tu sala de plataformas:
1. Abre en el Asset Browser la sala de tu nivel (por ejemplo, `rm_nivel_1`). 2. En el panel izquierdo de propiedades de la sala, despliega la sección denominada **Viewports and Cameras**. 3. Marca la casilla **Enable Viewports** (Habilitar puertos de vista). Si esta casilla no está marcada, GameMaker dibujará toda la sala estirada o recortada a la ventana principal sin utilizar cámaras. 4. Despliega la pestaña **Viewport 0** y marca la casilla **Visible**. 5. En la subsección **Camera Properties**, define el ancho y alto del encuadre. Para mantener la estética retro de nuestro proyecto con píxeles nítidos y formato panorámico 16:9, configuraremos `Width: 480` y `Height: 270`. 6. En la subsección **Viewport Properties**, define el tamaño con el que se mostrará esa ventana. Puedes fijar `Width: 1920` y `Height: 1080` (una relación exacta de 4 veces la resolución interna). 7. En la subsección **Object Following**, haz clic en el selector de objetos y selecciona a nuestro personaje jugable: `obj_jugador`. 8. Ajusta los bordes horizontales y verticales (**Horizontal Border** y **Vertical Border**). Estos valores representan la distancia mínima (en píxeles) que el jugador puede aproximarse al borde del encuadre antes de que la cámara comience a empujar su posición. Configura `Horizontal Border: 200` y `Vertical Border: 100`. 9. Los campos **Horizontal Speed** y **Vertical Speed** determinan la velocidad máxima a la que se desplazará la cámara para alcanzar al jugador. Si colocas `-1`, la cámara seguirá al jugador de manera instantánea y rígida.
💡 Importante: Si dejas la velocidad en -1, cada micro-movimiento o salto del jugador se reflejará sin amortiguación en la pantalla, lo que puede causar fatiga visual y sensación de rigidez mecánica. Sin embargo, si estableces una velocidad fija como 4 o 6 píxeles por cuadro, si el jugador se mueve más rápido que ese valor (por ejemplo, corriendo o cayendo), el personaje se saldrá de la pantalla.
Debido a estas limitaciones del seguimiento automático básico (carencia de aceleración elástica, incapacidad para anticipar caídas, falta de sacudidas de pantalla o zooms programados), en desarrollo profesional se suele prescindir del 'Object Following' del editor de salas en favor de un objeto controlador dedicado programado en GML Code.
El Objeto Controlador de Cámara: obj_camara
Para tener control total sobre la experiencia de visualización, crearemos un objeto gestor llamado `obj_camara`. Este objeto será el único responsable de calcular las coordenadas óptimas de la vista, garantizar que la cámara no revele los bordes vacíos fuera del nivel y aplicar técnicas de suavizado matemático.
Crea un nuevo objeto en tu árbol de recursos y nómbralo `obj_camara`. No requiere ningún sprite asignado, ya que actuará exclusivamente como un ente lógico invisible en la escena.
Inicialización en el Evento Create
En el evento **Create** de `obj_camara`, inicializaremos la cámara virtual activa, definiremos la resolución de diseño y configuraremos el puerto de vista mediante las funciones del motor de GameMaker. Añade el siguiente código en el evento Create:
/// Evento Create de obj_camara
// 1. Definir la resolución base del encuadre (formato 16:9 retro)
ancho_camara = 480;
alto_camara = 270;
escala_ventana = 3; // Escala para la ventana en el escritorio (480*3 = 1440, 270*3 = 810)
// 2. Crear la cámara interna de GameMaker
camara = camera_create_view(0, 0, ancho_camara, alto_camara, 0, -1, -1, -1, 0, 0);
// 3. Habilitar el sistema de vistas y enlazar la cámara al Viewport 0
view_enabled = true;
view_visible[0] = true;
view_set_camera(0, camara);
// 4. Configurar el tamaño físico de la ventana y el búfer de dibujo (Application Surface)
window_set_size(ancho_camara * escala_ventana, alto_camara * escala_ventana);
surface_resize(application_surface, ancho_camara * escala_ventana, alto_camara * escala_ventana);
// Centrar la ventana en el monitor del jugador tras el redimensionamiento
alarm[0] = 1;
// 5. Variables de seguimiento e interpolación
objetivo = obj_jugador;
suavizado = 0.1; // Factor de interpolación (entre 0.01 y 1.0)
// Posición inicial centrada en el objetivo si existe
if (instance_exists(objetivo)) {
x = objetivo.x - (ancho_camara / 2);
y = objetivo.y - (alto_camara / 2);
} else {
x = 0;
y = 0;
}
// Aplicar la posición inicial directamente a la cámara
camera_set_view_pos(camara, x, y);Para asegurarnos de que la ventana quede centrada en la pantalla de la computadora después de llamar a `window_set_size()`, añadimos el evento **Alarm 0** en `obj_camara` con una sola instrucción:
/// Evento Alarm 0 de obj_camara
window_center();La elección crítica del evento: ¿Step o End Step?
Uno de los errores más frecuentes en el desarrollo con GameMaker es colocar la lógica de seguimiento de la cámara en el evento **Step**. Si la cámara actualiza su posición en el evento Step, se produce una condición de carrera temporal dependiente del orden de ejecución interna de las instancias.
Si `obj_camara` ejecuta su evento Step antes de que `obj_jugador` procese sus pulsaciones de teclado, velocidades y colisiones en su propio evento Step, la cámara se posicionará basándose en las coordenadas anteriores del jugador. Cuando el jugador se mueva un instante después, el renderizado final dibujará al personaje desfasado un fotograma con respecto al fondo. El resultado visual es un temblor o vibración incómoda (conocida en inglés como 'camera jitter') que degrada notablemente la fluidez visual.
Por esta razón, la cámara debe posicionarse SIEMPRE en el evento **End Step** (Paso Final). En este evento, GameMaker garantiza que todas las entidades del juego ya han resuelto sus velocidades, colisiones físicas e integraciones de movimiento, permitiendo que la cámara capture la posición espacial definitiva del jugador antes de que comiencen los eventos de dibujo (**Draw**).
Seguimiento elástico con Interpolación Lineal (lerp)
Un seguimiento cinematográfico y agradable no clava rígidamente al personaje en el centro geométrico de la pantalla, sino que utiliza una amortiguación elástica. En matemáticas y programación de videojuegos, esto se logra mediante la función `lerp()` (abreviatura de *Linear Interpolation*).
La función `lerp(a, b, t)` toma un punto de partida `a`, un punto destino `b`, y un porcentaje o factor normalizado `t` (donde 0 representa el 0% y 1 representa el 100%). Cada fotograma, la función calcula una posición intermedia desplazándose un porcentaje de la distancia restante. A medida que la cámara se aproxima al objetivo, la distancia se reduce, produciendo una suave desaceleración orgánica.
Límites del escenario con clamp()
Si el jugador camina hacia el extremo izquierdo del nivel (x = 0), centrar la cámara directamente en sus coordenadas provocará que la mitad izquierda de la pantalla muestre un vacío negro exterior al nivel. Para impedir que la cámara exponga áreas fuera de la Room, utilizamos la función matemática `clamp(valor, minimo, maximo)`.
El límite mínimo horizontal de la esquina superior izquierda de la cámara debe ser `0`. El límite máximo horizontal no es el ancho de la sala (`room_width`), sino el ancho de la sala menos el ancho de la cámara (`room_width - ancho_camara`). De forma idéntica, el límite vertical oscila entre `0` y `room_height - alto_camara`.
Implementación en el Evento End Step de obj_camara
Abre `obj_camara`, añade el evento **Step -> End Step** e introduce la siguiente lógica comentada minuciosamente:
/// Evento End Step de obj_camara
// 1. Verificar de forma defensiva la existencia de la instancia objetivo
if (!instance_exists(objetivo)) exit;
// 2. Calcular las coordenadas ideales donde desearíamos que esté la esquina superior izquierda
// Para centrar al jugador, restamos la mitad del ancho y alto de la cámara a su posición actual
var _pos_x_destino = objetivo.x - (ancho_camara / 2);
var _pos_y_destino = objetivo.y - (alto_camara / 2);
// 3. Aplicar interpolación lineal para deslizar la posición de la cámara suavemente hacia el destino
x = lerp(x, _pos_x_destino, suavizado);
y = lerp(y, _pos_y_destino, suavizado);
// 4. Restringir la posición dentro de las fronteras físicas de la sala actual
x = clamp(x, 0, room_width - ancho_camara);
y = clamp(y, 0, room_height - alto_camara);
// 5. Corrección de subpíxeles para estética retro (evitar desgarro y deformación de texturas)
// Al trabajar con gráficos pixel art, situar la vista en números decimales puede producir artefactos visuales
var _pos_x_final = floor(x);
var _pos_y_final = floor(y);
// 6. Actualizar las coordenadas de la cámara activa de GameMaker
camera_set_view_pos(camara, _pos_x_final, _pos_y_final);💡 Atención al redondeo: La línea con floor() es vital para juegos 2D de píxel arte retro. Si le pasas valores continuos flotantes (como x = 124.382) a camera_set_view_pos(), las líneas de un solo píxel pueden vibrar, engrosarse o desaparecer intermitentemente según la resolución de tu pantalla.
Comportamiento del evento Clean Up y gestión de memoria
En GameMaker, las cámaras creadas dinámicamente mediante código usando `camera_create_view()` consumen memoria nativa fuera de la recolección automática de basura. Si el jugador reinicia el nivel o cambia de sala y el objeto `obj_camara` es destruido o recreado, las cámaras huérfanas permanecerán en la memoria del sistema, causando una fuga de memoria (memory leak).
Para evitar fugas de memoria, añade el evento **Clean Up** en `obj_camara` y destruye explícitamente la estructura de datos de la cámara cuando la instancia deje de existir:
/// Evento Clean Up de obj_camara
// Destruir la cámara creada dinámicamente para liberar memoria en el motor
if (camera_exists(camara)) {
camera_destroy(camara);
}Técnicas avanzadas de cámara para plataformas 2D
Una vez dominada la base de seguimiento y límites, podemos enriquecer la experiencia de juego implementando técnicas adicionales que dotan a la cámara de dinamismo y mejor respuesta jugable.
Anticipación de avance (Look-Ahead)
En juegos de velocidad o exploración lateral, cuando el personaje corre hacia la derecha, lo más útil para el jugador no es ver el espacio vacío que acaba de dejar atrás a su izquierda, sino el terreno desconocido que tiene por delante. Esta técnica se denomina 'Look-Ahead' o desplazamiento predictivo.
Podemos incorporar un desplazamiento horizontal que dependa del signo de la velocidad horizontal (`hsp`) o de la escala horizontal del sprite (`image_xscale`) de nuestro personaje. De este modo, la cámara adelanta unos píxeles su objetivo hacia la dirección de avance:
/// Ejemplo de integración de Look-Ahead en End Step de obj_camara
var _distancia_adelanto = 48; // Píxeles que la cámara se anticipa hacia el frente
var _offset_x = 0;
// Comprobar la dirección hacia la que mira o camina el jugador
if (objetivo.hsp != 0) {
_offset_x = sign(objetivo.hsp) * _distancia_adelanto;
} else {
_offset_x = objetivo.image_xscale * (_distancia_adelanto * 0.5);
}
// Sumar el desplazamiento al cálculo del objetivo
var _pos_x_destino = (objetivo.x + _offset_x) - (ancho_camara / 2);
var _pos_y_destino = objetivo.y - (alto_camara / 2);Sacudida de pantalla (Screen Shake)
El efecto de 'Screen Shake' añade un tremendo impacto visual cuando el jugador recibe daño, destruye a un enemigo o activa una trampa. Consiste en añadir una perturbación aleatoria a la posición de la cámara que decrece progresivamente hasta llegar a cero.
Podemos añadir este comportamiento a nuestro `obj_camara` de manera muy sencilla. En el evento **Create**, inicializamos las variables de sacudida:
// Variables de sacudida de pantalla (Screen Shake)
fuerza_sacudida = 0;
amortiguacion_sacudida = 0.9;Y en el evento **End Step**, tras calcular la posición suave e imponer los límites con `clamp()`, sumamos la perturbación:
// Aplicar sacudida de pantalla si está activa
if (fuerza_sacudida > 0.2) {
_pos_x_final += random_range(-fuerza_sacudida, fuerza_sacudida);
_pos_y_final += random_range(-fuerza_sacudida, fuerza_sacudida);
// Reducir la fuerza gradualmente cuadro a cuadro
fuerza_sacudida *= amortiguacion_sacudida;
} else {
fuerza_sacudida = 0;
}
// Posicionar finalmente la cámara con la sacudida aplicada
camera_set_view_pos(camara, floor(_pos_x_final), floor(_pos_y_final));Cualquier objeto en el juego (como el proyectil de un enemigo o el propio `obj_jugador` al colisionar con una trampa de pinchos) puede detonar el efecto con una sola línea de código: `obj_camara.fuerza_sacudida = 8;`.
Ejercicios prácticos
Pon a prueba tus conocimientos sobre cámaras y viewports completando las siguientes actividades prácticas en tu proyecto de plataformas:
1. **Implementación de una sala panorámica extensa y validación de bordes:** Crea una nueva sala llamada `rm_pruebas_camara` con unas dimensiones de 2400 píxeles de ancho por 540 píxeles de alto. Coloca baldosas sólidas formando un suelo continuo y varios desniveles con plataformas a distintas alturas. Instancia en la capa de instancias únicamente a `obj_jugador` y a `obj_camara`. Comprueba que al desplazarte hasta el extremo izquierdo (x = 0) o al extremo derecho (x = 2400), la cámara se detiene de forma natural exactamente en el borde del nivel sin mostrar zonas vacías fuera de la sala.
2. **Control dinámico de suavizado según el estado del jugador:** Modifica el código de `obj_camara` para que el valor de la variable `suavizado` varíe dinámicamente según lo que haga el personaje. Cuando el jugador esté corriendo en el suelo, el suavizado debe mantenerse en `0.1`. Sin embargo, si el jugador está cayendo verticalmente a gran velocidad (cuando `vsp > 3` en `obj_jugador`), la cámara debe responder más rápidamente ajustando el suavizado a `0.25` para evitar que el personaje se escape del encuadre inferior. Comprueba que la transición visual sea suave y no provoque saltos bruscos.
3. **Zona muerta vertical (Vertical Deadzone) para saltos cortos:** En muchos juegos de plataformas clásicos, los saltos cortos sobre una superficie plana no deben mover la cámara hacia arriba y hacia abajo para evitar que el jugador sienta mareo por un movimiento vertical excesivo. Modifica el evento **End Step** de `obj_camara` implementando una 'zona muerta' vertical: si la diferencia entre la coordenada Y actual del jugador y el centro de la cámara es menor a 32 píxeles, la posición Y de la cámara no debe recalcularse. Verifica que dar pequeños saltos en el suelo mantenga el horizonte de la cámara completamente estático.
Resumen
- La arquitectura de visión en GameMaker se compone de tres elementos: la Room (mundo de juego completo), la Camera (lente matemática con coordenadas y dimensiones en la Room) y el Viewport (área de la pantalla o ventana del sistema operativo donde se proyecta la imagen).
- El editor de salas permite configurar el seguimiento de objetos de forma visual, pero carece de la flexibilidad y control elástico que se requiere para una producción comercial sólida.
- El cálculo de la posición de la cámara debe realizarse imperativamente en el evento End Step de un objeto gestor (obj_camara), garantizando que las entidades hayan completado todos sus movimientos y colisiones antes del dibujo.
- La función lerp() permite lograr un seguimiento elástico y orgánico mediante interpolación lineal porcentual en cada fotograma.
- La función clamp() restringe la cámara dentro de los límites de la sala (entre 0 y room_width - ancho_camara, y entre 0 y room_height - alto_camara), impidiendo que se visualice el exterior del escenario.
- En proyectos de estética pixel art, redondear la posición de la cámara con floor() antes de enviarla a camera_set_view_pos() evita el temblor de subpíxeles y deformaciones en las texturas.
- Las cámaras creadas mediante código deben ser destruidas en el evento Clean Up utilizando camera_destroy() para evitar fugas de memoria al cambiar de nivel o reiniciar la partida.
Practica lo aprendido
Ejercicio 1 · 1 punto
Relaciona cada concepto de la arquitectura de visión en GameMaker con su función y la analogía explicada en la lección:
- Room
- Camera
- Viewport
Ejercicio 2 · 1 punto
¿Por qué es fundamental posicionar y actualizar la cámara en el evento End Step en lugar del evento Step?
Ejercicio 3 · 1 punto
En el Editor de Rooms, si configuras Horizontal Speed o Vertical Speed con el valor , la cámara seguirá al jugador de forma instantánea y rígida. Para lograr un seguimiento elástico y suave por código, se recurre a la función , que realiza una interpolación lineal entre la posición actual y la de destino.
Ingresa para guardar tu progreso — Ingresar