Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 9: Música, efectos sonoros y partículas.

Efectos de polvo al saltar y correr

Clase 44 de 60 · 60 min

También disponible en: EN

Efectos de polvo al saltar y correr: Aportando peso y respuesta visual

Uno de los factores que separa un prototipo rígido de un videojuego satisfactorio al interactuar con el mando es lo que comúnmente se denomina «game feel» o respuesta visual y física. Cuando un personaje corre sobre un terreno de tierra o piedra, la ausencia de cualquier interacción física visible hace que parezca que flota o patina sobre el escenario. El polvo es el recurso visual por excelencia en los videojuegos de plataformas en dos dimensiones para anclar al personaje al entorno, transmitir peso, aceleración e inercia.

En la lección anterior aprendiste los conceptos fundamentales de los sistemas de partículas en GameMaker: el emisor, los tipos de partículas y la gestión del sistema para evitar fugas de memoria. En esta clase llevaremos esa teoría a la práctica activa, vinculando la generación de partículas con la máquina de estados y las variables de movimiento horizontal y vertical de nuestro personaje principal (`obj_player`), creadas a lo largo de los módulos de colisiones y movimiento.

¿Cuándo debe aparecer el polvo? Los tres momentos clave

Para que el polvo se sienta natural y aporte información al jugador en lugar de saturar la pantalla con ruido visual, debe responder a eventos mecánicos concretos dentro del bucle de juego. Existen tres momentos críticos en los que el polvo cumple una función expresiva indispensable:

1. **La carrera constante (Pasos):** Pequeñas motas de polvo o pequeñas nubes tenues que se desprenden intermitentemente de los talones del personaje mientras se desplaza por el suelo a velocidad considerable. 2. **El impulso de salto:** Un estallido concentrado hacia abajo y hacia los lados en las coordenadas de la base del personaje justo en el fotograma exacto en el que abandona la superficie mediante el comando de salto. 3. **El impacto de aterrizaje:** Una dispersión horizontal hacia ambos costados (izquierda y derecha) en el instante preciso en que el personaje deja de estar en el aire y colisiona nuevamente contra una superficie sólida, reflejando la absorción de la inercia vertical.

Diagrama explicativo de los tres momentos de emisión de partículas de polvo: carrera con pequeñas bocanadas hacia atrás, salto con impulso descendente y aterrizaje con dispersión horizontal lateral.
Mecánicas de dispersión de polvo según el estado del jugador: carrera, despegue y aterrizaje. · Imagen generada con IA

Arquitectura recomendada: El gestor de efectos globales

En proyectos de desarrollo con GameMaker, es tentador crear sistemas de partículas dentro del evento `Create` del propio personaje (`obj_player`). Sin embargo, en un entorno de desarrollo estructurado y escalable, esta práctica genera acoplamiento indeseado: si el jugador muere y su instancia se destruye, el sistema de partículas podría desaparecer prematuramente, cortando las partículas activas en el aire, o bien provocar fugas de memoria si se recrea en cada reaparición.

La mejor práctica consiste en delegar la creación y el mantenimiento de los sistemas de partículas en un objeto controlador permanente, por ejemplo `obj_fx_manager`, o centralizarlos a través de variables globales seguras. En este módulo utilizaremos un objeto gestor persistente que inicializa los tipos de partículas al arrancar la escena y los mantiene listos para que cualquier entidad del juego pueda emitirlos cuando sea necesario.

Crea un nuevo objeto en tu proyecto y nómbralo `obj_fx_manager`. En sus propiedades, marca la casilla **Persistent** (Persistente) para asegurar que acompañe al jugador a través de todas las pantallas del juego sin reinicializarse.

// ====================================================================
// OBJETO: obj_fx_manager
// EVENTO: Create
// PROPÓSITO: Inicializar el sistema de partículas global y configurar
//            los tipos de partículas de polvo para el jugador.
// ====================================================================

// 1. Creación del sistema de partículas global en una capa visual profunda
global.part_sys = part_system_create();
part_system_depth(global.part_sys, -100); // Se dibuja por delante del fondo pero detrás del HUD

// 2. Definición de la partícula de polvo para la CARRERA
global.pt_dust_run = part_type_create();
part_type_shape(global.pt_dust_run, pt_shape_smoke);
part_type_size(global.pt_dust_run, 0.08, 0.15, -0.005, 0); // Comienza pequeña y encoge
part_type_scale(global.pt_dust_run, 1, 1);
part_type_color1(global.pt_dust_run, c_silver);
part_type_alpha3(global.pt_dust_run, 0.6, 0.4, 0.0);       // Nace semi-opaca y se desvanece
part_type_speed(global.pt_dust_run, 0.2, 0.6, -0.01, 0);   // Velocidad suave de deriva
part_type_direction(global.pt_dust_run, 70, 110, 0, 0);    // Sube levemente hacia arriba
part_type_gravity(global.pt_dust_run, 0.01, 270);          // Muy leve resistencia de caída
part_type_life(global.pt_dust_run, 15, 25);                // Vida corta (entre 15 y 25 pasos)

// 3. Definición de la partícula de polvo para el SALTO
global.pt_dust_jump = part_type_create();
part_type_shape(global.pt_dust_jump, pt_shape_smoke);
part_type_size(global.pt_dust_jump, 0.15, 0.25, -0.01, 0);
part_type_scale(global.pt_dust_jump, 1, 1);
part_type_color2(global.pt_dust_jump, c_white, c_gray);
part_type_alpha3(global.pt_dust_jump, 0.8, 0.5, 0.0);
part_type_speed(global.pt_dust_jump, 1.0, 2.0, -0.05, 0);
part_type_direction(global.pt_dust_jump, 20, 160, 0, 10);  // Se abre hacia los costados
part_type_life(global.pt_dust_jump, 20, 30);

// 4. Definición de la partícula de polvo para el ATERRIZAJE
global.pt_dust_land = part_type_create();
part_type_shape(global.pt_dust_land, pt_shape_smoke);
part_type_size(global.pt_dust_land, 0.2, 0.35, -0.012, 0);
part_type_scale(global.pt_dust_land, 1, 0.6);              // Aplanada horizontalmente
part_type_color2(global.pt_dust_land, c_white, c_ltgray);
part_type_alpha3(global.pt_dust_land, 0.9, 0.4, 0.0);
part_type_speed(global.pt_dust_land, 1.5, 3.2, -0.08, 0);  // Expulsión veloz hacia los lados
part_type_life(global.pt_dust_land, 20, 35);

Analicemos las decisiones técnicas tomadas en esta configuración. La función `part_type_alpha3` es una herramienta clave de GameMaker: define tres fases en la opacidad de la partícula (inicio, mitad de su ciclo vital y final). Al establecer `0.6` al nacer, `0.4` a mitad de vida y `0.0` al morir, nos aseguramos de que el polvo nunca desaparezca de golpe frente a los ojos del jugador mediante un parpadeo tosco, sino que se disuelva gradualmente en el aire.

Asimismo, el factor negativo en el tercer argumento de `part_type_size` (denominado *size increase*) indica cuánto debe cambiar el tamaño por cada fotograma. Un valor como `-0.005` hace que la partícula se contraiga orgánicamente hasta esfumarse por completo.

Limpieza de memoria obligatoria (Clean Up)

Como se destacó al abordar los sistemas de partículas, las estructuras de datos nativas del motor gráfico no están sujetas al recolector de basura automático de GML. Es estrictamente obligatorio liberar cada tipo de partícula y el sistema en sí cuando el objeto gestor sea destruido o el juego finalice.

// ====================================================================
// OBJETO: obj_fx_manager
// EVENTO: Clean Up
// PROPÓSITO: Destruir recursos de partículas para evitar fugas de memoria.
// ====================================================================

// Destrucción de tipos de partículas individuales
if (variable_global_exists("pt_dust_run")) {
    part_type_destroy(global.pt_dust_run);
}
if (variable_global_exists("pt_dust_jump")) {
    part_type_destroy(global.pt_dust_jump);
}
if (variable_global_exists("pt_dust_land")) {
    part_type_destroy(global.pt_dust_land);
}

// Destrucción del sistema que las contiene
if (variable_global_exists("part_sys")) {
    part_system_destroy(global.part_sys);
}

💡 Recuerda que el evento Clean Up se ejecuta automáticamente tanto si una instancia es destruida con instance_destroy() como si la sala (Room) cambia o el juego se cierra con game_end(). Es la ubicación más segura y moderna de GameMaker para la recolección manual de recursos de memoria.

Preparación del personaje: Variables de estado y temporización

Ahora que las definiciones físicas del polvo existen en memoria y están listas para ser disparadas en cualquier coordenada del juego, abriremos el objeto del personaje principal, `obj_player`. Para sincronizar la emisión de partículas con sus movimientos, necesitamos agregar dos variables de control en su evento `Create`:

// ====================================================================
// OBJETO: obj_player
// EVENTO: Create (Fragmento complementario para partículas)
// ====================================================================

// Variables de movimiento heredadas de módulos anteriores:
// hsp = 0;
// vsp = 0;
// grounded = false;

// Variables de control para efectos de partículas:
was_grounded = false;     // Almacena el estado grounded del fotograma anterior
dust_timer = 0;           // Temporizador para espaciar las partículas al correr
dust_interval = 8;        // Cantidad de fotogramas entre cada partícula de carrera

La variable `was_grounded` es fundamental: nos permite comparar si el jugador estaba en el aire en el fotograma anterior y tocó el suelo en el fotograma actual. La variable `dust_timer` evita un error común en principiantes: disparar partículas de carrera en cada fotograma sucesivo (lo que generaría 60 partículas por segundo), provocando una masa blanca densa y desagradable.

Implementación del disparo de partículas en el evento Step

En el evento `Step` de `obj_player`, después de haber calculado las colisiones horizontales y verticales (donde se actualiza el valor de `grounded`), insertaremos la lógica de interacción con las partículas. Usaremos la función nativa `part_particles_create`, que genera partículas directamente en una posición específica sin necesidad de instanciar emisores complejos.

// ====================================================================
// OBJETO: obj_player
// EVENTO: Step (Fragmento a insertar tras resolver movimiento y colisiones)
// ====================================================================

// Coordenada vertical exacta del suelo bajo los pies del personaje:
var _feet_y = bbox_bottom;

// --------------------------------------------------------------------
// 1. EFECTO DE CARRERA (MOVIMIENTO CONTINUO EN EL SUELO)
// --------------------------------------------------------------------
// Se activa si el personaje está tocando el suelo y tiene velocidad horizontal real
if (grounded && abs(hsp) > 0.5) {
    dust_timer++;
    
    if (dust_timer >= dust_interval) {
        // Reiniciamos el contador
        dust_timer = 0;
        
        // Calculamos un ligero retraso horizontal opuesto a la marcha
        // para que el polvo brote de los talones:
        var _dust_x = x - (sign(hsp) * 6);
        
        // Creamos una partícula de carrera
        part_particles_create(global.part_sys, _dust_x, _feet_y, global.pt_dust_run, 1);
    }
} else {
    // Si el jugador se detiene o salta, reiniciamos el temporizador
    dust_timer = 0;
}

// --------------------------------------------------------------------
// 2. EFECTO DE SALTO (IMPULSO INICIAL)
// --------------------------------------------------------------------
// Se dispara únicamente en el fotograma exacto en el que el jugador presiona
// el botón de salto estando aún apoyado en una superficie
if (key_jump && grounded) {
    // Lanzamos una pequeña ráfaga de 3 partículas hacia los costados
    part_particles_create(global.part_sys, x, _feet_y, global.pt_dust_jump, 3);
}

// --------------------------------------------------------------------
// 3. EFECTO DE IMPACTO AL ATERRIZAR (DETECCIÓN DE TRANSICIÓN)
// --------------------------------------------------------------------
// Ocurre si en el fotograma previo NO tocaba el suelo, pero en este SÍ lo hace
if (!was_grounded && grounded) {
    // Aterrizaje brusco: generamos una explosión lateral de 6 a 8 partículas
    // Para darle dinamismo, forzamos direcciones opuestas izquierda/derecha:
    
    // Chorro hacia la izquierda
    part_type_direction(global.pt_dust_land, 135, 175, 0, 0);
    part_particles_create(global.part_sys, x - 4, _feet_y, global.pt_dust_land, 4);
    
    // Chorro hacia la derecha
    part_type_direction(global.pt_dust_land, 5, 45, 0, 0);
    part_particles_create(global.part_sys, x + 4, _feet_y, global.pt_dust_land, 4);
}

// Actualizamos el registro de estado al final del fotograma para la siguiente vuelta
was_grounded = grounded;

Fíjate detenidamente en la variable `_feet_y`. Al utilizar la propiedad integrada `bbox_bottom`, GameMaker toma la posición exacta del píxel más bajo de la máscara de colisión del sprite del jugador. Esto garantiza que el polvo siempre aparezca rozando el suelo, independientemente de si el punto de origen (origen del sprite) fue configurado en el centro, arriba o en la cabeza.

Ajuste fino y personalización con sprites propios

Aunque las formas integradas de GameMaker como `pt_shape_smoke` o `pt_shape_pixel` son excelentes para prototipar con rapidez, muchos proyectos de estética pixel art lucen mejor con sprites de polvo dibujados a mano con formas cuadradas o nubes con contornos estilizados.

Para sustituir la forma procedural por un sprite propio en tu gestor de efectos, se utiliza la función `part_type_sprite`. Esta función vincula un recurso gráfico y permite configurar si el sprite debe reproducir una animación o seleccionar un fotograma aleatorio.

Parámetro de part_type_spriteTipo de datoPropósito e impacto en el juego
indAsset de SpriteEl índice del recurso gráfico creado en el navegador de assets (ej: `spr_dust_puff`).
animateBooleanoSi es `true`, la partícula reproduce los subfotogramas del sprite a lo largo de su vida útil.
stretchBooleanoSi es `true`, ajusta la velocidad de la animación para que coincida exactamente con la vida (`life`) de la partícula.
randomBooleanoSi es `true`, selecciona un subfotograma al azar al nacer en lugar de comenzar en el fotograma 0.

Si deseas un polvo estilo retro con un sprite de tres subfotogramas (que comienza como una esfera compacta, se expande y luego se dispersa en pequeñas bolitas), puedes crear el recurso `spr_dust_cloud` de 8x8 píxeles con el origen en el centro inferior (`Bottom-Centre`) y sustituir la línea de configuración en `obj_fx_manager`:

// Reemplazo de la forma integrada por un sprite animado propio:
part_type_sprite(global.pt_dust_land, spr_dust_cloud, true, true, false);

Escalando la intensidad del polvo según la fuerza del impacto

Un salto pequeño desde una caja no debería generar la misma cantidad de humo ni el mismo estruendo que una caída libre desde lo alto de una torre de tres niveles. Dado que ya gestionamos la velocidad vertical acumulada en la variable `vsp` antes de detener la caída contra el suelo, podemos utilizar esa cifra para modular la fuerza del impacto visual.

En el evento `Step` de `obj_player`, guardaremos la última velocidad vertical de descenso justo antes de ejecutar la colisión que la resetea a cero. Luego, al aterrizar, multiplicaremos la cantidad o el tamaño de las partículas generadas en función de esa caída previa:

// ====================================================================
// OBJETO: obj_player
// EVENTO: Step (Ajuste avanzado de impacto proporcional)
// ====================================================================

// Guardamos la velocidad vertical justo antes de colisionar contra el suelo
var _fall_speed = vsp;

// --- (Aquí se procesa la colisión vertical habitual del Módulo 5) ---
// if (place_meeting(x, y + vsp, obj_solid)) { ... vsp = 0; grounded = true; }

// Verificamos si acaba de ocurrir el impacto
if (!was_grounded && grounded) {
    // Solo creamos polvo abundante si la caída tuvo suficiente inercia
    if (_fall_speed > 3) {
        // Calculamos cuántas partículas disparar en base a la caída (entre 4 y 12)
        var _count = clamp(floor(_fall_speed * 1.5), 4, 12);
        
        // Disparo lateral con mayor velocidad si el impacto fue potente
        part_type_speed(global.pt_dust_land, _fall_speed * 0.2, _fall_speed * 0.4, -0.05, 0);
        part_particles_create(global.part_sys, x, bbox_bottom, global.pt_dust_land, _count);
    }
}

Con este simple ajuste, el sistema físico y el sistema visual se entrelazan armónicamente: el jugador percibe de manera inmediata e intuitiva la gravedad de su descenso sin necesidad de que la interfaz muestre números ni textos explicativos.

Ejercicios prácticos

Aplica los conceptos aprendidos completando las siguientes tres actividades en tu proyecto:

1. **Efecto de frenada o derrape:** Añade una nueva condición en el evento `Step` de `obj_player` que detecte cuándo el jugador cambia bruscamente de dirección mientras corre (por ejemplo, cuando `hsp > 2` pero el jugador está pulsando la tecla de dirección izquierda). Dispara en ese instante una ráfaga frontal de partículas `global.pt_dust_run` para simular el derrape de las suelas sobre la superficie. *Criterio de comprobación:* Al correr hacia la derecha y pulsar repentinamente la izquierda, debe brotar una densa estela de polvo justo frente al personaje antes de que empiece a desplazarse en reversa.

2. **Creación del sprite personalizado de polvo retro:** Dibuja un sprite de 8x8 píxeles llamado `spr_dust_pixel` compuesto por tres fotogramas: fotograma 0 con una cruz de píxeles compacta (estilo 8-bit), fotograma 1 con la cruz expandiéndose y hueca en el centro, y fotograma 2 con cuatro píxeles dispersos en las esquinas. Configúralo en `obj_fx_manager` mediante `part_type_sprite` y asígnalo al salto y al aterrizaje. *Criterio de comprobación:* El polvo en pantalla debe respetar la paleta cromática y la cuadrícula pixel art de los sprites de tu escenario sin interpolaciones de color lisas o borrosas.

3. **Polvo de doble salto o salto en pared:** Si tu personaje cuenta con salto doble (mecánica vista en el Módulo 4) o rebote en la pared, implementa un efecto de polvo aéreo. Para el salto doble, haz que el polvo sea circular y caiga en un anillo expansivo (360 grados). Para el salto en pared, el polvo debe salir expulsado de la pared vertical hacia el lado opuesto del impulso. *Criterio de comprobación:* Ejecutar un doble salto en medio del aire debe dejar una estela anular visible que marque la posición exacta en la que se produjo el segundo impulso en el espacio vacío.

Resumen

  • El polvo interactivo es un componente esencial del «game feel», dotando al personaje de sensación de masa, contacto físico y tracción en el entorno 2D.
  • Se deben identificar tres disparadores principales: el desplazamiento continuo (carrera), la liberación brusca de fuerza hacia arriba (salto) y la recepción de inercia vertical (aterrizaje).
  • Por orden de arquitectura y buenas prácticas, las partículas deben ser creadas y liberadas en un gestor centralizado (como `obj_fx_manager`) persistente para evitar fugas de memoria y desorden en el código del personaje.
  • El evento `Clean Up` es el único lugar adecuado para destruir los sistemas y tipos de partículas mediante `part_type_destroy()` y `part_system_destroy()`.
  • La variable de control `was_grounded` permite detectar de manera fiable la transición de estado entre estar en el aire y tocar el suelo, identificando el fotograma exacto del aterrizaje.
  • La emisión en la coordenada `bbox_bottom` asegura que el polvo se posicione fielmente en las suelas del personaje sin importar el punto de origen de su sprite.

Practica lo aprendido

Ejercicio 1 · 1 punto

Empareja cada momento clave de generación de polvo con el comportamiento visual que le corresponde según la lección:

  • La carrera constante
  • El impulso de salto
  • El impacto de aterrizaje
Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ejercicio 2 · 1 punto

¿Cuál es la función técnica de utilizar part_type_alpha3 al configurar las partículas de polvo en GameMaker?

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

Ejercicio 3 · 1 punto

En el evento Create de obj_player, la variable almacena el estado de contacto con el suelo del fotograma previo para detectar caídas, mientras que la variable determina el número de fotogramas que deben transcurrir entre cada partícula emitida durante la carrera.

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

Ingresa para guardar tu progreso — Ingresar