Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 6: Animaciones, sprites, fondos y tilemaps.

Gestión de estados y subimágenes de sprites

Clase 26 de 60 · 50 min

También disponible en: EN

Introducción: La conexión entre la física y la respuesta visual

Hasta este punto del curso, nuestro personaje principal cuenta con un sistema de movimiento sólido: responde a las pulsaciones del teclado, calcula aceleraciones horizontales, experimenta la gravedad y detecta colisiones precisas contra el suelo y las paredes. Sin embargo, en la pantalla el personaje se percibe rígido o incompleto si su apariencia visual no acompaña lo que ocurre mecánicamente. Un personaje que corre a toda velocidad utilizando un dibujo estático, o cuyos pies se mueven en el aire mientras cae al vacío, rompe de inmediato la inmersión del jugador.

En GameMaker, la representación gráfica de cualquier instancia está gobernada por sus recursos de tipo Sprite y un conjunto de variables internas especialmente diseñadas para gestionar qué fotograma se dibuja y a qué velocidad se reproduce. En esta lección aprenderás a tomar el control absoluto del motor de animación mediante GML Code (GameMaker Language). Comprenderás cómo descomponer animaciones en subimágenes, cómo evitar desincronizaciones al cambiar de acción y cómo vincular la velocidad de reproducción de los sprites a las variables físicas calculadas en los módulos anteriores.

Anatomía de un Sprite en GameMaker: Subimágenes y velocidad

Un Sprite en GameMaker no es necesariamente una imagen fija solitaria; en la gran mayoría de los casos de desarrollo de videojuegos 2D, es una tira secuencial de fotogramas denominados técnicamente 'subimágenes' (sub-images). Cada subimagen representa una pose individual en una secuencia de animación, indexada con un sistema numérico de base cero. Si un sprite de carrera contiene 6 fotogramas, sus índices numéricos van estrictamente desde 0 hasta 5.

Dentro del Sprite Editor de GameMaker, cada recurso posee dos propiedades de configuración fundamentales que definen su comportamiento predeterminado: el origen del sprite (que en nuestro proyecto de plataformas fijamos en la parte media inferior, 'Bottom Centre', para facilitar el alineamiento con el suelo) y la velocidad de animación base (definida usualmente como 'Frames per Second' o FPS, por ejemplo a 12 o 15 FPS). Aunque estas opciones se configuren de forma visual en el editor, el verdadero poder surge cuando intervenimos sobre ellas en tiempo real desde el código de nuestros objetos.

Diagrama explicativo que muestra una secuencia de subimágenes indexadas de 0 a 5, con indicadores de avance temporal mediante la variable image_index y multiplicadores de image_speed.
Figura 1: Representación conceptual del avance de subimágenes cíclicas gobernadas por image_index e image_speed. · Imagen generada con IA

Variables internas fundamentales de animación en GML

Cada objeto instanciado en una sala dispone automáticamente de un grupo de variables integradas que interactúan con el motor gráfico en cada paso (Step) del bucle de juego. A continuación, analizamos las cuatro variables indispensables que gobernarán nuestras animaciones:

Variable GMLTipo de datoAccesoFunción principal
sprite_indexAsset Reference (Sprite)Lectura / EscrituraAlmacena el recurso de sprite asignado actualmente a la instancia (por ejemplo, spr_player_run).
image_indexReal (Punto flotante)Lectura / EscrituraIndica el fotograma actual mostrado. Internamente avanza de forma continua con decimales antes de truncarse para dibujar.
image_speedRealLectura / EscrituraFactor multiplicador de la velocidad de animación del sprite. 1 representa velocidad nominal, 0 detiene la animación y 0.5 la reduce a la mitad.
image_numberEntero positivoSolo LecturaDevuelve la cantidad total de fotogramas que posee el sprite asignado actualmente en sprite_index.

Es crucial entender que 'image_index' no es un simple número entero que salta de 0 a 1 y luego a 2 en cada paso de juego. Dado que el juego suele ejecutarse a 60 pasos por segundo (Room Speed / Game Speed) y una animación de sprite suele configurarse a 10 o 12 fotogramas por segundo, GameMaker suma una fracción decimal a 'image_index' en cada Step. Por ejemplo, puede sumar 0.2 en cada ciclo. La tarjeta gráfica solo dibuja la parte entera truncada, pero el contador decimal interno asegura que la animación fluya al ritmo exacto estipulado de manera independiente a la tasa de refresco global.

💡 Regla de oro: Nunca intentes cambiar el valor de 'image_number' por código. Es una variable de solo lectura que refleja la cantidad de fotogramas creados en el editor de recursos. Si intentas asignarle un valor, el compilador generará un error de ejecución.

El problema clásico del cambio de Sprite y cómo resolverlo

Un error sumamente común en desarrolladores principiantes ocurre cuando alternan el sprite del personaje entre el estado de reposo ('spr_player_idle') y el de carrera ('spr_player_run'). Supongamos que 'spr_player_idle' tiene 4 fotogramas (índices 0 a 3) y 'spr_player_run' tiene 8 fotogramas (índices 0 a 7). Si el código cambia 'sprite_index = spr_player_run' en cada fotograma mientras el jugador mantenga presionada la tecla derecha, GameMaker mantendrá el valor acumulado de 'image_index'.

Si el cambio se produce cuando 'image_index' valía 3.8, el nuevo sprite comenzará a reproducirse repentinamente a mitad de paso. Peor aún: si pasamos de un sprite con 10 fotogramas que iba por el índice 8 a un sprite de reposo que solo tiene 2 fotogramas, durante una fracción de segundo 'image_index' superará el límite del nuevo recurso, provocando parpadeos visuales indeseados hasta que el motor reinicie el ciclo.

Para evitar este comportamiento, debemos verificar si el sprite actual ya es el deseado antes de asignarlo. Si no lo es, realizamos el cambio y reiniciamos inmediatamente 'image_index = 0', garantizando que cada animación comience de forma limpia desde su primer fotograma.

// Comprobación segura antes de intercambiar sprites
if (sprite_index != spr_player_run)
{
    sprite_index = spr_player_run;
    image_index = 0;
}

Sincronización de velocidad de animación con el movimiento físico

En muchos videojuegos clásicos, la animación de correr se reproduce a una velocidad fija constante. Sin embargo, si nuestro personaje cuenta con aceleración y deceleración graduales (como programamos en el Módulo 4), reproducir la animación a toda velocidad cuando el personaje recién comienza a dar los primeros pasos produce un efecto visual conocido como 'patinaje' (skating): los pies del personaje se mueven más rápido de lo que el suelo se desplaza bajo él.

Podemos solucionar esto modulando dinámicamente el valor de 'image_speed' en función de la velocidad horizontal real ('hsp') del jugador. Si calculamos la proporción entre la velocidad horizontal actual y la velocidad máxima permitida ('move_speed_max'), obtendremos un multiplicador que escalará la cadencia de los pasos con absoluta naturalidad.

Control de subimágenes fijas: El salto y la caída

Las animaciones aéreas requieren un tratamiento especial. En un juego de plataformas tradicional, el salto no suele ser un bucle continuo de muchos fotogramas descontrolados, sino una pose de ascenso tensa y una pose de descenso o caída abierta. Frecuentemente, los desarrolladores utilizan un único sprite ('spr_player_jump') compuesto por dos subimágenes: la subimagen 0 para el ascenso y la subimagen 1 para la caída.

Para controlar esto manualmente, establecemos 'image_speed = 0'. De este modo, GameMaker suspende el avance automático del contador y nos permite a nosotros, mediante código, dictar exactamente qué subimagen mostrar evaluando el signo de la velocidad vertical ('vsp').

// Control manual de fotogramas aéreos
if (!grounded)
{
    sprite_index = spr_player_jump;
    image_speed = 0; // Congelamos el avance automático de fotogramas
    
    if (vsp < 0)
    {
        // El personaje está subiendo en el aire
        image_index = 0;
    }
    else
    {
        // El personaje ha alcanzado el ápice y está cayendo
        image_index = 1;
    }
}

Detección de fotogramas clave y eventos en la animación

En el diseño de juegos profesional, ciertos acontecimientos deben dispararse en sincronía con la animación: reproducir el sonido de una pisada cuando el zapato toca el suelo, generar una pequeña partícula de polvo, o habilitar la zona de daño de una espada durante el fotograma exacto del golpe. Para lograr esto desde GML en el evento Step, debemos monitorear el valor de 'image_index'.

Dado que 'image_index' almacena números reales con decimales que avanzan a razón del delta de tiempo de cada fotograma, una condición como 'if (image_index == 2)' casi nunca resultará verdadera, porque el valor flotante saltará probablemente de 1.88 a 2.12 sin coincidir exactamente en 2.0000. Para detectar el paso por un fotograma clave de forma infalible sin librerías externas, comprobamos si el fotograma actual se encuentra dentro de la ventana de incremento del paso actual, o convertimos el valor mediante 'floor(image_index)' controlando una bandera booleana para no repetir el evento en múltiples pasos continuos.

// Ejemplo conceptual: Detección de pisada en el fotograma 2 de spr_player_run
// Variables de soporte requeridas en Create: footstep_played = false;

var _current_frame = floor(image_index);

if (_current_frame == 2)
{
    if (!footstep_played)
    {
        // Aquí se ejecutará la acción una sola vez al entrar al fotograma 2
        // Por ejemplo: reproducir un efecto de sonido o emitir polvo
        footstep_played = true;
    }
}
else
{
    // Al salir del fotograma 2, rehabilitamos el disparador para el próximo ciclo
    footstep_played = false;
}

Implementación práctica paso a paso en obj_player

Vamos a integrar todas estas técnicas dentro del objeto principal de nuestro proyecto de plataformas, 'obj_player'. Mantendremos limpia la arquitectura del proyecto organizando las variables en el evento Create y agrupando la lógica de animación al final del evento Step, después de que se hayan resuelto los cálculos de velocidad y colisiones del Módulo 5.

Paso 1: Preparación de recursos. En el Asset Browser, asegúrate de contar con los siguientes sprites configurados con el punto de origen en 'Bottom Centre': - 'spr_player_idle': Animación de respiración o espera (al menos 2 a 4 fotogramas, velocidad: 6 FPS). - 'spr_player_run': Animación de carrera completa (al menos 4 a 8 fotogramas, velocidad: 12 FPS). - 'spr_player_jump': Animación aérea de 2 fotogramas (fotograma 0: subida; fotograma 1: caída).

Paso 2: Inicialización en el evento Create de 'obj_player'. Añadiremos las variables de referencia para recordar el estado de la animación previa y controlar la reproducción.

/// Evento Create de obj_player

// Variables de movimiento físico (creadas en módulos anteriores)
hsp = 0;
vsp = 0;
grav = 0.3;
move_speed_max = 3.5;
jump_force = -7.0;
grounded = false;

// Variables añadidas para el control de animaciones
footstep_trigger = false;
animation_base_speed = 1.0;

Paso 3: Integración en el evento Step de 'obj_player'. Añadiremos un bloque dedicado exclusivamente al control visual ('Sistema de Animación') al final del código del evento Step, aprovechando los resultados de 'grounded', 'hsp' y 'vsp'.

/// Final del Evento Step de obj_player - Control de Animaciones y Sprites

// 1. GESTIÓN DEL PERSONAJE EN EL SUELO
if (grounded)
{
    // Si no hay velocidad horizontal significativa, mostramos reposo
    if (abs(hsp) < 0.1)
    {
        if (sprite_index != spr_player_idle)
        {
            sprite_index = spr_player_idle;
            image_index = 0;
        }
        image_speed = 1.0; // Velocidad normal de reposo
    }
    else
    {
        // Si nos desplazamos horizontalmente, pasamos a carrera
        if (sprite_index != spr_player_run)
        {
            sprite_index = spr_player_run;
            image_index = 0;
        }
        
        // Ajustamos la velocidad de animación proporcionalmente a la velocidad física
        // Evitamos que baje de 0.3 para que no parezca completamente detenido al comenzar el paso
        var _speed_ratio = abs(hsp) / move_speed_max;
        image_speed = max(0.3, _speed_ratio);
    }
}
// 2. GESTIÓN DEL PERSONAJE EN EL AIRE
else
{
    if (sprite_index != spr_player_jump)
    {
        sprite_index = spr_player_jump;
    }
    
    // Congelamos la animación automática para fijar el fotograma manualmente
    image_speed = 0;
    
    // Subimagen 0 en fase de ascenso, subimagen 1 en fase de caída
    if (vsp < 0)
    {
        image_index = 0;
    }
    else
    {
        image_index = 1;
    }
}

💡 Nota sobre GML Visual: Quienes trabajen con nodos visuales pueden lograr este mismo comportamiento utilizando los bloques 'Set Instance Variable' asignando 'Sprite' e 'Image Index', combinados con el bloque de control condicional 'If Variable' para evaluar la condición de suelo y la velocidad.

Ejercicios prácticos

Desarrolla las siguientes actividades para poner a prueba y afianzar los conceptos estudiados en esta clase.

Ejercicio 1: Modulación de velocidad en reversa. Modifica el código de control de animación de carrera de modo que, si el jugador se desplaza en una dirección pero presiona momentáneamente el control opuesto (derrape antes de detenerse), la animación de correr reduzca su velocidad a la mitad (multiplicador 0.5) para reflejar visualmente la desaceleración abrupta.

Criterio de comprobación del Ejercicio 1: Observa al jugador en movimiento rápido; al soltar la tecla o presionar el lado contrario, la animación de las piernas debe desacelerar visiblemente de forma inmediata antes de cambiar al estado de reposo.

Ejercicio 2: Sincronización precisa de aterrizaje. En tu sprite 'spr_player_jump', agrega una tercera subimagen (índice 2) que represente la flexión de rodillas al tocar el suelo. En el evento Step, detecta el instante exacto en que el personaje pasa de estar en el aire a tocar suelo, y mantén el fotograma 2 activo durante 4 pasos de juego antes de permitir la transición a 'spr_player_idle'.

Criterio de comprobación del Ejercicio 2: Al caer de una plataforma elevada, el personaje no debe mostrar inmediatamente la postura erguida de reposo, sino una breve pose de impacto con las rodillas dobladas que finalice fluidamente.

Ejercicio 3: Notificador de fotograma en consola con show_debug_message. Implementa en el código de 'spr_player_run' la detección limpia del fotograma donde el pie derecho toca el suelo (por ejemplo, fotograma 1). Empleando la función 'show_debug_message("Paso derecho")', comprueba en la ventana de salida (Output) de GameMaker que el mensaje se imprima estrictamente una sola vez por cada ciclo de carrera, sin repetirse continuamente mientras ese fotograma esté activo.

Criterio de comprobación del Ejercicio 3: En la consola de depuración inferior de GameMaker, el texto debe aparecer en intervalos rítmicos exactos correspondientes a cada ciclo de la animación, sin desbordar la consola con cientos de mensajes idénticos en un solo segundo.

Resumen

  • Los sprites en GameMaker se componen de subimágenes indexadas secuencialmente a partir de cero (0, 1, 2, ... N-1).
  • Las variables clave para el control de animación son 'sprite_index' (qué imagen), 'image_index' (qué fotograma), 'image_speed' (a qué ritmo avanza) e 'image_number' (número total de subimágenes, de solo lectura).
  • Al cambiar de sprite mediante código, es indispensable comprobar previamente 'if (sprite_index != nuevo_sprite)' y reiniciar 'image_index = 0' para garantizar transiciones suaves y limpias.
  • Establecer 'image_speed = 0' detiene la animación automática y permite seleccionar fotogramas clave específicos manualmente, técnica ideal para controlar las poses de salto y caída con 'vsp'.
  • Vincular dinámicamente 'image_speed' con la velocidad física del jugador elimina la sensación de deslizamiento y aporta naturalidad al movimiento en juegos de plataformas 2D.

Practica lo aprendido

Ejercicio 1 · 1 punto

¿Qué ocurre si intentas modificar el valor de la variable interna `image_number` desde GML Code?

Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ejercicio 2 · 2 puntos

Completa el cuerpo de la condición para cambiar de forma segura al sprite `spr_player_run` y reiniciar la animación para que comience limpiamente desde su primer fotograma.

Tu código no se ejecuta: se comprueban requisitos concretos sobre el texto.

Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ejercicio 3 · 1 punto

Relaciona cada variable interna de GameMaker con su función correspondiente en la gestión de animaciones:

  • sprite_index
  • image_index
  • image_speed
  • image_number
Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ingresa para guardar tu progreso — Ingresar