Introducción al Desarrollo de Videojuegos con GameMaker · Module 9: Music, Sound Effects, and Particles
Dust Effects on Jumping and Running
Lesson 44 of 60 · 60 min
Also available in: ES
Dust Effects on Jumping and Running: Adding Weight and Visual Feedback
One of the key elements separating a stiff prototype from a game that feels great in your hands is game feel—the visual, auditory, and tactile feedback of player actions. When a character runs across dirt or stone ground, the lack of any visible physical reaction makes them look like they are floating or sliding across the terrain. Dust is the classic visual tool in 2D platformers to ground the character in the environment, communicating weight, acceleration, and momentum.
In the previous lesson, you explored the fundamentals of particle systems in GameMaker: emitters, particle types, and system lifecycle management to prevent memory leaks. In this lesson, we will put that theory into practice by linking particle generation directly to our player character's state machine and movement variables (`obj_player`), built during our collision and movement modules.
When Should Dust Appear? Three Key Moments
To ensure dust feels natural and provides clear gameplay information rather than cluttering the screen with visual noise, it must respond to specific mechanical triggers within the game loop. There are three critical moments where dust serves an essential expressive purpose:
1. **Continuous Running (Footsteps):** Small flecks of dust or subtle puffs kicked up periodically from the character's heels while moving along the ground at speed. 2. **Jump Takeoff:** A concentrated downward and outward burst spawned at the base of the character on the exact frame they leave the ground via a jump input. 3. **Landing Impact:** A horizontal splash dispersing outward to both sides (left and right) the moment the character stops falling and hits a solid surface again, conveying the absorption of downward momentum.

Recommended Architecture: The Global Effects Manager
In GameMaker projects, it is tempting to instantiate particle systems inside the `Create` event of the player character (`obj_player`). However, in a structured, scalable project, this introduces unwanted coupling: if the player dies and the instance is destroyed, active particles in the air could abruptly vanish, or recreating the system on each respawn could cause memory leaks.
The best practice is to delegate the creation and maintenance of particle systems to a persistent controller object—such as `obj_fx_manager`—or centralize them via safe global references. In this module, we will use a persistent manager object that initializes all particle types during room startup and keeps them ready for any game entity to emit when needed.
Create a new object in your project and name it `obj_fx_manager`. In its object properties, check the **Persistent** box to ensure it persists across room transitions without reinitializing.
// ====================================================================
// OBJECT: obj_fx_manager
// EVENT: Create
// PURPOSE: Initialize the global particle system and configure
// dust particle types for the player.
// ====================================================================
// 1. Create the global particle system on a deep visual layer
global.part_sys = part_system_create();
part_system_depth(global.part_sys, -100); // Drawn in front of the background but behind the HUD
// 2. Define dust particle for RUNNING
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); // Starts small and shrinks
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); // Starts semi-opaque and fades out
part_type_speed(global.pt_dust_run, 0.2, 0.6, -0.01, 0); // Gentle drift speed
part_type_direction(global.pt_dust_run, 70, 110, 0, 0); // Drifts slightly upward
part_type_gravity(global.pt_dust_run, 0.01, 270); // Very gentle falling resistance
part_type_life(global.pt_dust_run, 15, 25); // Short lifetime (15 to 25 steps)
// 3. Define dust particle for JUMPING
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); // Spreads outward to the sides
part_type_life(global.pt_dust_jump, 20, 30);
// 4. Define dust particle for LANDING
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); // Flattened horizontally
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); // High-speed outward burst
part_type_life(global.pt_dust_land, 20, 35);Let's break down the technical choices in this setup. The function `part_type_alpha3` is an essential GameMaker tool: it defines three stages of particle opacity (birth, mid-life, and death). By passing `0.6` at spawn, `0.4` at mid-life, and `0.0` at expiration, we guarantee dust never pops out of existence abruptly with an awkward flicker, dissolving smoothly into the air instead.
Likewise, the negative factor in the third argument of `part_type_size` (the *size increase* parameter) controls size changes per frame. A value like `-0.005` causes the particle to shrink organically until it fades completely away.
Mandatory Memory Cleanup (Clean Up Event)
As emphasized in our introduction to particle systems, low-level data structures allocated by the graphics engine are not managed by GML's automated garbage collector. You must explicitly free each particle type and the particle system itself when the manager object is destroyed or the game shuts down.
// ====================================================================
// OBJECT: obj_fx_manager
// EVENT: Clean Up
// PURPOSE: Destroy particle resources to prevent memory leaks.
// ====================================================================
// Destroy individual particle types
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);
}
// Destroy the parent particle system
if (variable_global_exists("part_sys")) {
part_system_destroy(global.part_sys);
}💡 Remember that the Clean Up event executes automatically whether an instance is destroyed via instance_destroy(), a room transition occurs, or the game exits via game_end(). It is GameMaker's safest and most standard location for manual resource cleanup.
Player Setup: State Tracking and Timing Variables
Now that the dust particle definitions are loaded in memory and ready to spawn at any coordinate in the game, open the player object, `obj_player`. To synchronize particle emission with character movement, add two tracking variables to its `Create` event:
// ====================================================================
// OBJECT: obj_player
// EVENT: Create (Supplemental snippet for particle management)
// ====================================================================
// Movement variables inherited from earlier modules:
// hsp = 0;
// vsp = 0;
// grounded = false;
// Control variables for particle effects:
was_grounded = false; // Stores grounded status from the previous frame
dust_timer = 0; // Timer to space out run particles
dust_interval = 8; // Frames between each running dust puffThe `was_grounded` variable is critical: it lets us compare whether the player was airborne on the previous frame and made contact with the floor on the current frame. The `dust_timer` variable prevents a common beginner mistake: spawning run particles on every single frame (which would emit 60 particles per second), producing an overly dense, unreadable cloud.
Triggering Particles in the Step Event
In `obj_player`'s `Step` event, right after resolving horizontal and vertical collisions (where `grounded` is updated), insert the particle trigger logic. We will use the built-in function `part_particles_create`, which spawns particles directly at a specified position without needing complex emitter instances.
// ====================================================================
// OBJECT: obj_player
// EVENT: Step (Insert after resolving movement and collisions)
// ====================================================================
// Exact vertical coordinate of the floor beneath the player's feet:
var _feet_y = bbox_bottom;
// --------------------------------------------------------------------
// 1. RUNNING EFFECT (CONTINUOUS GROUND MOVEMENT)
// --------------------------------------------------------------------
// Triggers if the player is grounded and has meaningful horizontal speed
if (grounded && abs(hsp) > 0.5) {
dust_timer++;
if (dust_timer >= dust_interval) {
// Reset timer
dust_timer = 0;
// Calculate a slight horizontal offset opposite to movement direction
// so dust puffs appear behind the heels:
var _dust_x = x - (sign(hsp) * 6);
// Spawn a single run particle
part_particles_create(global.part_sys, _dust_x, _feet_y, global.pt_dust_run, 1);
}
} else {
// Reset timer when standing still or airborne
dust_timer = 0;
}
// --------------------------------------------------------------------
// 2. JUMP EFFECT (TAKEOFF IMPULSE)
// --------------------------------------------------------------------
// Triggers only on the exact frame the player presses jump while grounded
if (key_jump && grounded) {
// Spawn a small burst of 3 particles dispersing outward
part_particles_create(global.part_sys, x, _feet_y, global.pt_dust_jump, 3);
}
// --------------------------------------------------------------------
// 3. LANDING IMPACT EFFECT (TRANSITION DETECTION)
// --------------------------------------------------------------------
// Triggers if airborne on the previous frame, but grounded on this frame
if (!was_grounded && grounded) {
// Hard landing: spawn a lateral blast of 6 to 8 particles
// Set opposing left/right directions for a dynamic burst:
// Leftward burst
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);
// Rightward burst
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);
}
// Update state tracking at the very end of the frame for the next step
was_grounded = grounded;Pay close attention to `_feet_y`. Using the built-in variable `bbox_bottom` ensures GameMaker references the bottom-most pixel row of the player sprite's collision mask. This guarantees dust spawns right at ground level, regardless of whether the sprite origin is set to top-left, center, or head level.
Fine-Tuning and Custom Sprites
While GameMaker's built-in shapes like `pt_shape_smoke` or `pt_shape_pixel` work well for rapid prototyping, pixel art games often look significantly more polished with hand-drawn dust sprites featuring blocky silhouettes or stylized cloud contours.
To replace the procedural shape with a custom sprite in your effects manager, use `part_type_sprite`. This function links a sprite asset and lets you configure whether the particle plays an animation or selects a random frame.
| part_type_sprite Parameter | Data Type | Purpose and In-Game Impact |
|---|---|---|
| ind | Sprite Asset | The resource index of the sprite created in the Asset Browser (e.g., `spr_dust_puff`). |
| animate | Boolean | If `true`, the particle cycles through sprite sub-images across its lifespan. |
| stretch | Boolean | If `true`, scales the animation playback speed so it matches the particle's `life` exactly. |
| random | Boolean | If `true`, picks a random sub-image at spawn rather than always starting on frame 0. |
To create retro-style dust using a three-frame sprite (starting as a solid puff, expanding, and then breaking into smaller particles), create an 8x8 pixel asset named `spr_dust_cloud` with its origin set to `Bottom-Centre`, and replace the shape line in `obj_fx_manager`:
// Replace procedural shape with a custom animated sprite:
part_type_sprite(global.pt_dust_land, spr_dust_cloud, true, true, false);Scaling Dust Intensity Based on Fall Velocity
A light hop off a small crate shouldn't produce the same cloud of dust as plummeting from the top of a tall tower. Because vertical speed is already tracked in `vsp` prior to floor collision, we can use that value to scale the visual impact.
In `obj_player`'s `Step` event, record the vertical fall speed immediately before executing the collision logic that resets it to zero. When landing, multiply the particle count or speed based on that recorded downward velocity:
// ====================================================================
// OBJECT: obj_player
// EVENT: Step (Advanced proportional impact adjustment)
// ====================================================================
// Cache vertical speed immediately before ground collision resolution
var _fall_speed = vsp;
// --- (Standard vertical collision resolution from Module 5 runs here) ---
// if (place_meeting(x, y + vsp, obj_solid)) { ... vsp = 0; grounded = true; }
// Check if landing impact occurred on this frame
if (!was_grounded && grounded) {
// Only spawn heavy dust if the fall had sufficient momentum
if (_fall_speed > 3) {
// Calculate particle count scaled to fall speed (clamped between 4 and 12)
var _count = clamp(floor(_fall_speed * 1.5), 4, 12);
// Boost lateral burst speed for hard landings
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);
}
}With this small addition, physics and visual feedback work hand in hand: players instinctively perceive the weight and velocity of their landing without relying on onscreen gauges or text.
Practical Exercises
Put what you've learned into practice by completing these three exercises in your project:
1. **Skid or Turning Dust:** Add a condition in `obj_player`'s `Step` event that checks when the player sharply reverses direction while running (for instance, when `hsp > 2` but the player is pressing the left movement key). Spawn a forward burst of `global.pt_dust_run` particles to simulate shoes skidding across the ground. *Verification criteria:* Running right and suddenly pressing left should kick up a distinct forward dust burst in front of the character before they start moving in reverse.
2. **Custom Retro Dust Sprite:** Draw an 8x8 pixel sprite named `spr_dust_pixel` with three frames: frame 0 as a solid pixel cross, frame 1 as an expanded hollow cross, and frame 2 as four separated corner pixels. Hook it up in `obj_fx_manager` using `part_type_sprite` for both jumping and landing. *Verification criteria:* Onscreen dust should conform to your game's pixel grid and color palette without blurry interpolation or smooth anti-aliased edges.
3. **Double Jump or Wall Jump Dust:** If your character features a double jump or wall kick mechanic, implement airborne dust effects. For a double jump, spawn an expanding 360-degree circular ring. For a wall jump, emit dust outward from the vertical wall surface opposite the kick direction. *Verification criteria:* Performing an aerial double jump should leave a clearly visible ring indicating the exact coordinate where the second leap occurred in mid-air.
Summary
- Interactive dust is a core pillar of 2D game feel, lending the character mass, physical presence, and traction in the environment.
- Dust should tie into three primary gameplay triggers: continuous running (footsteps), explosive upward thrust (jumping), and downward momentum absorption (landing).
- To maintain clean architecture, particle systems and types should be created and managed in a centralized, persistent manager (such as `obj_fx_manager`) to prevent leaks and decoupled bugs.
- The `Clean Up` event is the proper place to release particle systems and types using `part_type_destroy()` and `part_system_destroy()`.
- Tracking previous-frame state with `was_grounded` reliably identifies the exact frame the character transitions from airborne to grounded.
- Using `bbox_bottom` ensures particles spawn accurately at ground level beneath the character's feet regardless of sprite origin settings.
Practise what you learned
Exercise 1 · 1 point
Match each key dust generation moment with its corresponding visual behavior according to the lesson:
- Continuous running
- Jump takeoff
- Landing impact
Exercise 2 · 1 point
What is the technical purpose of using part_type_alpha3 when configuring dust particles in GameMaker?
Exercise 3 · 1 point
In the Create event of obj_player, the variable stores whether the player was touching the ground on the previous frame to detect landings, while the variable determines how many frames must pass between each particle spawned while running.
Sign in to save your progress — Sign in