Introducción al Desarrollo de Videojuegos con GameMaker · Module 9: Music, Sound Effects, and Particles

Background Music Management and Loops

Lesson 42 of 60 · 50 min

Also available in: ES

Introduction: Background Music in Game Design

Background music (often abbreviated as BGM) is one of the most impactful elements shaping a player's experience. While sound effects (SFX) communicate immediate actions like shots, jumps, or impacts, music establishes the emotional tone, dramatic tension, and overall pacing of a level. In our retro 2D platformer, an upbeat, rhythmic track conveys fluidity and momentum during gameplay, while a calmer, ambient piece on the main menu sets the stage before starting a run.

From a software development perspective, playing music presents very different challenges than triggering a half-second sound effect. Music tracks are long audio files, typically lasting anywhere from one to three minutes. If configured incorrectly, they can consume excessive amounts of system RAM or introduce noticeable stutters when looping. Achieving an infinite, seamless loop without silent gaps or abrupt cutoffs when switching rooms or restarting a level requires a solid understanding of GameMaker's audio engine and a centralized management system.

Sound Asset Configuration: Streaming vs. Preloading

In GameMaker, both short sound effects and full musical compositions are imported into the Asset Browser inside the Sounds folder. However, their internal settings must be handled completely differently. When importing an audio file (typically in .ogg or .mp3 format for music), the Sound Editor allows you to configure its compression mode and memory loading behavior.

For sound effects, the standard approach is to decompress the entire audio file into RAM when the game boots or when loading a room, ensuring zero-latency playback upon triggering. If you applied that same strategy to a three-minute uncompressed stereo track, it would consume tens of megabytes of RAM per song. For background music, streaming is the preferred solution. With streaming enabled, GameMaker keeps only a small temporary buffer in memory and decodes the compressed file on the fly from disk or storage as playback progresses.

SettingSound Effect (SFX)Background Music (BGM)
Recommended FormatWAV (uncompressed raw audio)OGG Vorbis (or cleanly looped MP3)
Type AttributeUncompressed / CompressedCompressed - Not Streamed or Streamed
Playback ModeFully decompressed in RAMContinuous streaming from disk
Audio ChannelsMono (standard for 2D SFX) or StereoStereo (for spatial width and richness)
Resource ConsumptionHigher RAM usage, negligible CPUMinimal RAM usage, slight CPU decoding

To configure your background track in GameMaker, double-click the sound asset (for example, snd_musica_nivel1). In the Inspector or Sound Editor window, locate the 'Type' dropdown and select 'Compressed - Streamed'. Set the compression bitrate between 128 kbps and 192 kbps, which provides an ideal balance between acoustic fidelity and file size for a 2D platformer.

Looping Playback with audio_play_sound

The core function for playing any sound in GML Code is audio_play_sound(). In modern versions of GameMaker, this function accepts both basic positional parameters and extended arguments for gain, offset, and pitch. Its traditional signature is structured as follows:

audio_play_sound(index, priority, loop, [gain], [offset], [pitch], [listener_mask]);

Let's break down each essential parameter involved in playing background music: 1. index: The identifier of the audio asset created in the Asset Browser, such as snd_musica_menu or snd_musica_nivel1. 2. priority: An integer value (typically between 0 and 100). If the game reaches the hardware channel limit for simultaneous voices, GameMaker drops lower-priority sounds in favor of higher-priority ones. Background music should have a high priority (such as 10 or 50), whereas footsteps or coin pickup sounds generally carry low priority (1 or 2). 3. loop: A boolean value (true or false). Passing true instructs the audio engine to immediately restart the track once it reaches the end, looping indefinitely until your code explicitly stops it.

It is crucial to understand what this function returns: audio_play_sound does not return a boolean indicating success; instead, it returns a Sound Instance ID (a unique handle for that specific playing voice). GameMaker differentiates between the Sound Asset (the blueprint in the Asset Browser) and the Sound Instance (the active playback stream). Storing this ID in a variable allows you to adjust the volume of that specific track, pause it, or stop it later without affecting any other sound in the game.

Conceptual diagram illustrating the music management architecture in GameMaker: a persistent controller object manages continuous playback and track transitions across levels.
Figure 1: Continuous music control flow using a persistent controller object. · AI-generated image

The Duplication Problem and the Audio Controller Object

A common beginner mistake in game development is placing the audio_play_sound call directly inside the player object's Create event (obj_player) or in the Room Creation Code. If music playback is tied to the player, every time the player loses a life and the level reloads via room_restart(), a new player instance is created, triggering playback again. The result is an unbearable cacophony where two or more out-of-sync copies of the same song play simultaneously.

The standard architectural solution is to centralize audio management within a single dedicated object: obj_audio_controller. This object is marked as Persistent (by checking the 'Persistent' box in its Object Properties). Persistent objects survive room transitions; once spawned in the game's initial room, they persist in the background across all subsequent levels without being destroyed or re-created.

Technical Implementation of obj_audio_controller

Next, we will implement a complete music management system in GML Code. Create the obj_audio_controller object, enable its Persistent property, and set up two events: the Create event (where we initialize tracking variables) and the Room Start event (which executes automatically whenever entering a new room).

// ========================================================
// OBJECT: obj_audio_controller
// EVENT: Create
// ========================================================

// Variable to store the sound instance ID of active music
musica_actual_instancia = -1;

// Variable to track which audio asset is currently playing
musica_actual_asset = noone;

// Default baseline music volume (range from 0.0 to 1.0)
volumen_musica = 0.8;

In the Create event, we set musica_actual_instancia to -1 (indicating no active instance) and musica_actual_asset to noone (a GML keyword representing the absence of an asset or instance). Next, we will use the Room Start event to dynamically determine which track should play based on the active room, avoiding restarting the melody if the incoming room shares the same music.

// ========================================================
// OBJECT: obj_audio_controller
// EVENT: Other -> Room Start
// ========================================================

// Local variable to determine which track belongs to this room
var _musica_a_reproducir = noone;

// Evaluate which room is currently active
switch (room)
{
    case rm_menu:
        _musica_a_reproducir = snd_musica_menu;
        break;
        
    case rm_nivel_1:
    case rm_nivel_2:
        // Both gameplay rooms can share the same action theme
        _musica_a_reproducir = snd_musica_nivel1;
        break;
        
    case rm_game_over:
        _musica_a_reproducir = snd_musica_derrota;
        break;
        
    default:
        _musica_a_reproducir = noone;
        break;
}

// Check if the requested music differs from what is already playing
if (_musica_a_reproducir != musica_actual_asset)
{
    // 1. If music is already playing, stop it
    if (audio_is_playing(musica_actual_instancia))
    {
        audio_stop_sound(musica_actual_instancia);
    }
    
    // 2. If the new room requires music, start it in a continuous loop
    if (_musica_a_reproducir != noone)
    {
        // Priority 10 ensures playback persistence, loop = true
        musica_actual_instancia = audio_play_sound(_musica_a_reproducir, 10, true);
        
        // Apply the desired volume to the newly spawned instance
        audio_sound_gain(musica_actual_instancia, volumen_musica, 0);
        
        // Update tracked asset record
        musica_actual_asset = _musica_a_reproducir;
    }
    else
    {
        // If the room has no assigned music, reset variables
        musica_actual_instancia = -1;
        musica_actual_asset = noone;
    }
}

Take note of the conditional check `if (_musica_a_reproducir != musica_actual_asset)`. When the player transitions from `rm_nivel_1` to `rm_nivel_2`, both rooms request `snd_musica_nivel1`. Because the requested asset matches the one already running, the block does not interrupt playback. The track continues playing uninterrupted across the room transition without resetting its tempo, delivering a polished and immersive player experience.

Smooth Transitions: Volume Fades (Fade In and Fade Out)

Stopping a music track abruptly with audio_stop_sound() produces a harsh cut that sounds unnatural to players. In commercial productions, track changes are executed with progressive volume fades known as Fade Out (gradually lowering volume to zero) and Fade In (gradually ramping volume up from silence to full volume).

GameMaker provides a built-in function to handle this interpolation automatically without requiring manual timers in the Step event: audio_sound_gain().

audio_sound_gain(index, volume, time);

The `time` parameter is measured in milliseconds (1000 milliseconds equal 1 second). Calling `audio_sound_gain(musica_actual_instancia, 0, 1500)` instructs the audio engine to ramp volume down to zero linearly over 1.5 seconds. Once the volume reaches zero, you can safely stop the sound instance and free up the channel.

Pausing and Resuming Music

In Module 8, we built an interactive pause system. When pausing a game, sound designers typically choose between two stylistic options: 1. Keep background music playing at normal volume or at a reduced volume (audio ducking). 2. Pause the music at its exact playback position and resume it smoothly when unpausing.

GameMaker provides dedicated functions for this: audio_pause_sound() and audio_resume_sound(). Unlike audio_stop_sound() (which rewinds the playback playhead to second 0), audio_pause_sound() freezes the playhead in place. Calling audio_resume_sound() picks up playback from the exact beat where the player paused.

// When triggering pause in obj_pause_manager:
if (audio_is_playing(obj_audio_controller.musica_actual_instancia))
{
    // Option A: Hard pause
    // audio_pause_sound(obj_audio_controller.musica_actual_instancia);
    
    // Option B: Duck volume to 25% for pause menu
    audio_sound_gain(obj_audio_controller.musica_actual_instancia, 0.25, 300);
}

// When unpausing the game:
if (audio_is_playing(obj_audio_controller.musica_actual_instancia))
{
    // Restore controller base volume over 300 milliseconds
    audio_sound_gain(obj_audio_controller.musica_actual_instancia, obj_audio_controller.volumen_musica, 300);
}

GML Visual Perspective

For learners following along with GML Visual, GameMaker includes equivalent visual blocks under the Audio category. The 'Play Audio' action allows you to pick the sound asset, check the 'Loop' box, and assign initial gain. For time-based volume automation, the 'Set Audio Volume' action lets you specify the target volume level and fade duration in milliseconds. That said, in mid-sized projects such as our platformer, managing playback conditionals through GML Code offers far greater architectural clarity and precision.

💡 Audio Editing Best Practice: Ensure your imported audio file does not contain dead silence at the beginning or end of its waveform. If an .ogg file has 0.2 seconds of trailing silence before looping, the playback will exhibit an awkward rhythmic gap every cycle. Trim your audio loops in an audio editor (such as Audacity) and align your loop cut points at zero-crossings to eliminate audible clicks or pops.

Hands-On Exercises

Put these concepts into practice by completing the following three exercises in your platformer project:

Exercise 1: Importing and Streaming Audio Assets. Import at least two distinct music tracks into your project: one for the main menu (snd_musica_menu) and another for gameplay (snd_musica_nivel1). In the Inspector, set both assets to 'Compressed - Streamed'. Verify that your audio format is properly supported (.ogg recommended) and preview the loop in the editor to make sure there are no silent gaps at the loop point.

Exercise 2: Building the Persistent Controller. Create an object named obj_audio_controller and check its 'Persistent' property. Place a single instance of it in your game's first room (such as rm_menu or a startup splash room rm_init). Implement the Create and Room Start event code detailed in this lesson. Verify that navigating from the menu into Level 1 changes the music correctly, and verify that restarting Level 1 via room_restart() does not duplicate or layer the audio track.

Exercise 3: Faded Game Over Transition. Import a third track for your game over screen (snd_musica_derrota). Instead of cutting the level music instantly when the player dies, configure your controller logic to fade out the active track over 1000 milliseconds before transitioning into rm_game_over. Listen carefully to confirm that the transition is seamless and free of clicks or pops.

Summary

  • Background music assets should be configured as 'Compressed - Streamed' to prevent high RAM consumption and enable efficient real-time decoding.
  • Calling audio_play_sound(index, priority, loop) with loop set to true guarantees infinite playback and returns a unique Sound Instance ID used to manipulate that specific playing voice.
  • Never trigger looping music from transient objects like the player or directly inside Room Creation Code; always manage playback through a persistent controller object (obj_audio_controller).
  • The Room Start event enables switch(room) checks to determine the active room's track while checking against the running asset to maintain unbroken playback across shared levels.
  • The audio_sound_gain(instance_id, volume, time_ms) function automates linear fade-ins and fade-outs, eliminating abrupt cutoffs and giving your game a professional audio finish.

Practise what you learned

Exercise 1 · 1 point

When configuring a background music (BGM) track in the GameMaker Sound Editor to optimize RAM usage, which technical option should be selected from the 'Type' dropdown?

You are practising without an account: nothing is saved. Sign up to keep your progress.

Exercise 2 · 1 point

The audio_play_sound() function returns a boolean value (true or false) indicating whether the audio track was successfully played.

You are practising without an account: nothing is saved. Sign up to keep your progress.

Exercise 3 · 2 points

Complete the function call to play the audio track snd_musica_nivel1 in an infinite continuous loop with a priority of 10, storing the returned Sound Instance ID in the variable musica_actual_instancia.

Your code is not executed: specific requirements are checked on the text.

You are practising without an account: nothing is saved. Sign up to keep your progress.

Sign in to save your progress — Sign in