Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 9: Música, efectos sonoros y partículas.
Control de volumen y ajustes de audio
Clase 45 de 60 · 50 min
También disponible en: EN
Control de volumen y ajustes de audio en GameMaker
En las clases anteriores aprendiste a importar e invocar efectos de sonido mediante funciones como audio_play_sound, así como a estructurar bucles musicales y generar partículas dinámicas para enriquecer la estética retro de nuestro juego de plataformas 2D. Sin embargo, un apartado sonoro de calidad comercial exige algo más que la simple reproducción de pistas: requiere que el usuario final tenga el control total sobre los niveles de intensidad acústica de su experiencia. No todas las pantallas, auriculares o preferencias de los jugadores son idénticas, y la ausencia de controles de volumen es una de las deficiencias más señaladas en las pruebas de experiencia de usuario.
El objetivo de esta lección es dotar a nuestro proyecto de una arquitectura de audio robusta, modular y personalizable. Exploraremos la jerarquía del sonido en GameMaker, aprenderemos a manipular la ganancia general (volumen maestro) y a segmentar la mezcla entre música de fondo y efectos especiales. Finalmente, conectaremos este sistema lógico con elementos visuales e interactivos en el evento Draw GUI, permitiendo al jugador modificar el volumen mediante barras deslizantes y selectores intuitivos.
El concepto de ganancia (Gain) y la jerarquía de mezcla
En el desarrollo de software y la ingeniería de audio digital, el término técnico para designar el volumen relativo de una señal es «ganancia» (gain). En GameMaker, la ganancia no se expresa en decibelios ni en valores porcentuales directos del 0 al 100, sino como un número en punto flotante que suele oscilar entre 0.0 y 1.0:
| Valor de ganancia (Float) | Equivalente porcentual | Efecto perceptivo en el motor |
|---|---|---|
| 0.0 | 0% | Silencio absoluto (Mute). El sonido se reproduce internamente pero no produce señal de salida audible. |
| 0.5 | 50% | Mitad de la intensidad de la señal original digitalizada en el recurso. |
| 1.0 | 100% | Ganancia unitaria nominal. Se reproduce al nivel estándar con el que fue exportado el archivo de audio. |
| > 1.0 | > 100% | Amplificación por software. Puede provocar distorsión digital (clipping) si la onda supera el límite admisible. |
Para gestionar una mezcla equilibrada en un videojuego comercial, nunca alteramos cada archivo de forma aislada sin un marco común. Establecemos una jerarquía que procesa la señal de audio a través de diferentes etapas antes de enviarla a los altavoces físicos del dispositivo del usuario.

Tal como ilustra el esquema, el volumen final que percibe el oído humano es el resultado de multiplicar las ganancias consecutivas. Si el sonido de un salto (`snd_salto`) se emite con ganancia 0.8, el canal de efectos (SFX) está calibrado al 0.5 y el volumen maestro general se encuentra al 0.5, la ganancia final real de ese salto será 0.8 * 0.5 * 0.5 = 0.2 (es decir, el 20% de su volumen original). Comprender esta multiplicación en cascada evita errores comunes de saturación o silenciamientos involuntarios.
Funciones nativas de control de ganancia en GML
GameMaker ofrece un conjunto de funciones especializadas para intervenir en cada nivel de esta jerarquía. A continuación, revisamos las herramientas esenciales que utilizaremos en la programación del sistema:
1. **Control maestro:** La función `audio_master_gain(gain)` determina el techo absoluto de volumen de toda la aplicación. Acepta un valor real entre 0.0 y 1.0. Al modificarla, todo sonido generado por cualquier función de GameMaker se ajustará de manera inmediata y proporcional.
2. **Control por recurso o instancia:** La función `audio_sound_gain(index, gain, time)` permite cambiar el volumen de un recurso específico (`snd_salto`) o de una instancia de sonido en ejecución (el identificador numérico devuelto por `audio_play_sound`). Su tercer parámetro, `time`, define el tiempo de interpolación en milisegundos, permitiendo transiciones suaves (fades) sin saltos bruscos.
3. **Control mediante Grupos de Audio (Audio Groups):** En proyectos medianos y avanzados, GameMaker permite agrupar sonidos en el IDE bajo 'Audio Groups' independientes (por ejemplo, `audiogroup_default`, `audiogroup_music`, etc.). Mediante `audio_group_set_gain(group, gain, time_ms)`, es posible alterar simultáneamente todos los sonidos asignados a ese grupo con una sola línea de código.
💡 Consejo técnico: Aunque los Audio Groups representan una excelente práctica para proyectos extensos con streaming en disco, en esta lección implementaremos un gestor híbrido basado en variables globales y control por instancias de audio. Esto garantiza total compatibilidad educativa sin requerir configuraciones complejas de precarga de memoria en el IDE para alumnos que dan sus primeros pasos.
Diseño del objeto gestor: obj_controlador_audio
Para asegurar que las preferencias sonoras del jugador persistan entre los diferentes niveles de nuestro juego de plataformas (desde la pantalla de título hasta la cueva subterránea), crearemos un objeto controlador persistente. Este objeto centralizará el estado de los canales y servirá de intermediario para cualquier petición sonora del juego.
Sigue estos pasos en tu proyecto:
1. En el **Asset Browser**, haz clic derecho sobre la carpeta **Objects** y selecciona **Create -> Object**. 2. Asigna al objeto el nombre exacto `obj_controlador_audio`. 3. Marca la casilla de verificación **Persistent** (Persistente). Esto garantiza que el objeto no se destruya al cambiar de room con `room_goto()`.
A continuación, inicializaremos las variables de configuración en el evento **Create** de `obj_controlador_audio`. Usaremos variables globales para que cualquier otro objeto del juego (como el jugador al saltar o un enemigo al recibir daño) pueda consultar el estado del audio en caso de necesitarlo.
// Evento Create de obj_controlador_audio
// 1. Evitar duplicados si volvemos a la room inicial
if (instance_number(obj_controlador_audio) > 1) {
instance_destroy();
exit;
}
// 2. Variables maestras de ganancia (rango 0.0 a 1.0)
global.volumen_maestro = 0.8;
global.volumen_musica = 0.7;
global.volumen_sfx = 1.0;
// Variable para rastrear la música ambiental activa actualmente
global.musica_actual_instancia = noone;
global.musica_actual_asset = noone;
// Aplicar inmediatamente la ganancia maestra al motor
audio_master_gain(global.volumen_maestro);
// Variables de interfaz para interacción con sliders en pantalla
slider_seleccionado = -1; // -1: ninguno, 0: maestro, 1: musica, 2: sfx
// Dimensiones y márgenes de los controles visuales en GUI
gui_slider_ancho = 200;
gui_slider_alto = 16;El control de duplicados mediante `instance_number(obj_controlador_audio) > 1` es una técnica indispensable. Al configurar una instancia como persistente, si el jugador reinicia la partida o vuelve al menú principal donde se colocó originalmente el objeto, GameMaker crearía una segunda copia idéntica. Destruir de inmediato cualquier instancia secundaria previene solapamientos acústicos y fallos en la memoria.
Creación de funciones de reproducción seguras y modulares
Para que el volumen de los efectos sonoros y de la música responda fielmente a las variables que acabamos de declarar, no debemos llamar directamente a `audio_play_sound` de forma dispersa con valores arbitrarios. Es una excelente práctica de desarrollo crear funciones personalizadas (scripts) encargadas de aplicar la ganancia adecuada en el instante exacto de la reproducción.
Crea un nuevo Script en el Asset Browser y nómbralo `scr_audio_sistema`. Escribe el siguiente código modular en su interior:
/// @function reproducir_sfx(sound_asset, prioridad)
/// @description Reproduce un efecto sonoro aplicando la ganancia global de SFX.
/// @param {Asset.GMSound} _asset Identificador del sonido a reproducir
/// @param {Real} _prioridad Prioridad de reproducción (ej. 1 a 10)
function reproducir_sfx(_asset, _prioridad) {
var _instancia = audio_play_sound(_asset, _prioridad, false);
// Aplicar la ganancia calculada inmediatamente a la instancia recién creada
audio_sound_gain(_instancia, global.volumen_sfx, 0);
return _instancia;
}
/// @function reproducir_musica(sound_asset, tiempo_fade_ms)
/// @description Gestiona el cambio de música de fondo con soporte para fundido.
/// @param {Asset.GMSound} _asset Recurso musical a reproducir en bucle
/// @param {Real} _tiempo_fade_ms Tiempo de transición en milisegundos
function reproducir_musica(_asset, _tiempo_fade_ms) {
// Si la música solicitada ya está sonando, no hacemos nada
if (global.musica_actual_asset == _asset && audio_is_playing(global.musica_actual_instancia)) {
return;
}
// Si hay una música previa sonando, detenemos o hacemos fade out
if (audio_is_playing(global.musica_actual_instancia)) {
audio_sound_gain(global.musica_actual_instancia, 0, _tiempo_fade_ms);
// Nota: El motor detendrá la pista gradualmente
}
// Iniciar la nueva pista con volumen 0 para entrar suavemente
global.musica_actual_asset = _asset;
global.musica_actual_instancia = audio_play_sound(_asset, 100, true);
audio_sound_gain(global.musica_actual_instancia, 0, 0);
// Transición al volumen de música configurado
audio_sound_gain(global.musica_actual_instancia, global.volumen_musica, _tiempo_fade_ms);
}
/// @function actualizar_volumenes_en_tiempo_real()
/// @description Actualiza el volumen maestro y los sonidos continuos activos.
function actualizar_volumenes_en_tiempo_real() {
// 1. Actualizar el volumen general del motor
audio_master_gain(global.volumen_maestro);
// 2. Si hay música activa en bucle, actualizar su ganancia sin esperar a una nueva pista
if (audio_is_playing(global.musica_actual_instancia)) {
audio_sound_gain(global.musica_actual_instancia, global.volumen_musica, 0);
}
}Con estas funciones preparadas, actualizar nuestro código de clases pasadas se vuelve una tarea trivial y segura:
• En lugar de escribir `audio_play_sound(snd_salto, 1, false);` en el jugador, invocaremos `reproducir_sfx(snd_salto, 1);`. • Al recolectar una moneda o derrotar a un enemigo, utilizaremos `reproducir_sfx(snd_moneda, 2);`. • Al cargar un nivel o pantalla de juego, iniciaremos la atmósfera mediante `reproducir_musica(snd_musica_nivel_1, 1000);` para lograr una entrada suave de un segundo.
Construcción de la interfaz de usuario en Draw GUI
Para que los ajustes sean accesibles al jugador, necesitamos una interfaz gráfica que visualice los tres canales (Maestro, Música y SFX) y permita regularlos tanto con el ratón como con entradas digitales. En el Módulo 8 aprendimos que el evento **Draw GUI** dibuja de forma estática sobre la pantalla, independientemente de los desplazamientos de la cámara o las dimensiones de la room del juego.
Implementaremos barras de ajuste interactivo (sliders) directamente mediante código vectorial y primitivas de GameMaker (`draw_rectangle`, `draw_set_color`), prescindiendo de la necesidad inmediata de crear sprites complejos. Esto permite probar el funcionamiento lógico con agilidad matemática pura.
Abramos de nuevo el objeto `obj_controlador_audio` y añadamos el evento **Draw GUI**:
// Evento Draw GUI de obj_controlador_audio
// Coordenadas base en pantalla
var _base_x = 100;
var _base_y = 120;
var _espaciado_y = 60;
// Configuración visual de textos
draw_set_font(-1); // Usar la tipografía predeterminada
draw_set_halign(fa_left);
draw_set_valign(fa_middle);
// Título del panel de configuración de audio
draw_set_color(c_white);
draw_text(_base_x, _base_y - 40, "AJUSTES DE AUDIO (Arrastra con el raton):");
// Estructura de datos temporal para procesar los tres canales en un bucle limpio
var _canales = [
{ nombre: "Volumen Maestro: ", valor: global.volumen_maestro },
{ nombre: "Musica BGM: ", valor: global.volumen_musica },
{ nombre: "Efectos SFX: ", valor: global.volumen_sfx }
];
for (var i = 0; i < array_length(_canales); i++) {
var _y_pos = _base_y + (i * _espaciado_y);
var _nombre = _canales[i].nombre;
var _val = _canales[i].valor;
var _porcentaje_texto = string(round(_val * 100)) + "%";
// 1. Dibujar etiqueta identificativa y porcentaje numérico
draw_set_color(c_yellow);
draw_text(_base_x, _y_pos - 15, _nombre + _porcentaje_texto);
// 2. Dibujar fondo de la barra del slider (Gris oscuro)
draw_set_color(c_dkgray);
draw_rectangle(_base_x, _y_pos, _base_x + gui_slider_ancho, _y_pos + gui_slider_alto, false);
// 3. Dibujar porción llena de la barra según el volumen actual (Verde cian)
var _ancho_relleno = gui_slider_ancho * _val;
draw_set_color(c_aqua);
draw_rectangle(_base_x, _y_pos, _base_x + _ancho_relleno, _y_pos + gui_slider_alto, false);
// 4. Dibujar borde exterior de la barra (Blanco)
draw_set_color(c_white);
draw_rectangle(_base_x, _y_pos, _base_x + gui_slider_ancho, _y_pos + gui_slider_alto, true);
// 5. Dibujar tirador móvil (Knob indicador)
var _tirador_x = _base_x + _ancho_relleno;
draw_set_color(c_white);
draw_rectangle(_tirador_x - 3, _y_pos - 4, _tirador_x + 3, _y_pos + gui_slider_alto + 4, false);
}
// Restaurar color por defecto al terminar el renderizado
draw_set_color(c_white);Detección de interacción con el cursor (Evento Step)
Ya tenemos dibujadas en pantalla las tres barras con sus respectivos porcentajes. Ahora debemos dotarlas de interactividad. Cuando el jugador haga clic sobre una barra y mueva el puntero del ratón horizontalmente, calcularemos matemáticamente la posición relativa respecto al ancho total para fijar el nuevo valor normalizado (0.0 a 1.0).
Dado que estamos interactuando con elementos posicionados en la capa visual GUI, no debemos usar las coordenadas globales `mouse_x` y `mouse_y` (que dependen de la posición de la cámara en la room física). En su lugar, recurrimos rigurosamente a `device_mouse_x_to_gui(0)` y `device_mouse_y_to_gui(0)`.
Agrega el siguiente código en el evento **Step** de `obj_controlador_audio`:
// Evento Step de obj_controlador_audio
// Obtener coordenadas del cursor en el lienzo GUI
var _gui_mouse_x = device_mouse_x_to_gui(0);
var _gui_mouse_y = device_mouse_y_to_gui(0);
var _base_x = 100;
var _base_y = 120;
var _espaciado_y = 60;
// 1. Detectar si el jugador presiona el clic izquierdo (inicio del arrastre)
if (mouse_check_button_pressed(mb_left)) {
for (var i = 0; i < 3; i++) {
var _y_pos = _base_y + (i * _espaciado_y);
// Comprobar colisión de punto entre el cursor y el área interactiva del slider
if (point_in_rectangle(_gui_mouse_x, _gui_mouse_y, _base_x, _y_pos - 4, _base_x + gui_slider_ancho, _y_pos + gui_slider_alto + 4)) {
slider_seleccionado = i;
break;
}
}
}
// 2. Si el botón izquierdo está liberado, dejamos de arrastrar
if (mouse_check_button_released(mb_left)) {
// Si soltamos el control de SFX, podemos reproducir un breve sonido testigo para confirmar el volumen
if (slider_seleccionado == 2) {
reproducir_sfx(snd_moneda, 10);
}
slider_seleccionado = -1;
}
// 3. Modificación del valor en tiempo real durante el arrastre
if (slider_seleccionado != -1 && mouse_check_button(mb_left)) {
// Normalizar la posición del ratón dentro del rango [0.0, 1.0]
var _nuevo_volumen = (_gui_mouse_x - _base_x) / gui_slider_ancho;
// Asegurar que el valor no exceda los límites mínimos ni máximos
_nuevo_volumen = clamp(_nuevo_volumen, 0.0, 1.0);
// Asignar el nuevo valor al canal que corresponda
switch (slider_seleccionado) {
case 0:
global.volumen_maestro = _nuevo_volumen;
break;
case 1:
global.volumen_musica = _nuevo_volumen;
break;
case 2:
global.volumen_sfx = _nuevo_volumen;
break;
}
// Aplicar los cambios a las salidas de sonido del motor
actualizar_volumenes_en_tiempo_real();
}Analicemos la lógica que acabamos de programar:
• `point_in_rectangle(px, py, x1, y1, x2, y2)` comprueba de forma sumamente eficiente si el puntero se halla sobre el rectángulo de colisión de cada una de las tres barras. • `(_gui_mouse_x - _base_x) / gui_slider_ancho` es la fórmula matemática de normalización: cuando el cursor está en el inicio exacto de la barra (`_base_x`), el numerador es 0, dando como resultado 0.0. Cuando se desplaza hasta el extremo derecho (`_base_x + gui_slider_ancho`), el resultado se convierte en 1.0. • `clamp(valor, min, max)` blinda nuestro sistema contra desbordamientos. Si el jugador hace clic y mueve el ratón bruscamente más allá de los márgenes laterales de la pantalla, la ganancia nunca caerá por debajo de 0.0 ni subirá por encima de 1.0.
Control alternativo por teclado: Accesibilidad para Gamepads y teclas
En un videojuego retro de plataformas concebido para teclado o mando de control, el usuario no siempre cuenta con un ratón disponible, especialmente si está sumergido en un menú de pausa durante una partida de acción trepidante. Enriqueceremos nuestro gestor para permitir cambiar el volumen también con las flechas o teclas de dirección.
Persistencia de la configuración en disco mediante archivos INI
Todo ajuste realizado por el jugador pierde sentido si, al cerrar el juego y volver a abrirlo al día siguiente, los valores regresan a los parámetros de fábrica. Aunque en el Módulo 10 y Módulo 12 abordaremos el guardado de progreso y datos persistentes en profundidad, GameMaker nos permite almacenar estas preferencias de audio de manera instantánea mediante el sistema nativo de ficheros de configuración `.ini`.
Para implementar la carga y guardado, añadiremos dos métodos esenciales en el objeto controlador.
En el evento **Create** de `obj_controlador_audio`, modificaremos la inicialización para que busque si existe una configuración previa guardada:
// Al final del evento Create de obj_controlador_audio:
// Intentar cargar preferencias previas desde disco
ini_open("configuracion_audio.ini");
global.volumen_maestro = ini_read_real("Audio", "master", 0.8);
global.volumen_musica = ini_read_real("Audio", "musica", 0.7);
global.volumen_sfx = ini_read_real("Audio", "sfx", 1.0);
ini_close();
// Sincronizar el motor de audio tras la lectura
actualizar_volumenes_en_tiempo_real();Luego, en el evento **Clean Up** del mismo objeto (evento que se ejecuta automáticamente cuando el objeto se retira de memoria o la aplicación se cierra limpiamente), guardamos los valores vigentes:
// Evento Clean Up de obj_controlador_audio
// Guardar los ajustes actuales en el fichero de configuración local
ini_open("configuracion_audio.ini");
ini_write_real("Audio", "master", global.volumen_maestro);
ini_write_real("Audio", "musica", global.volumen_musica);
ini_write_real("Audio", "sfx", global.volumen_sfx);
ini_close();💡 Buenas prácticas multiplataforma: El sistema de archivos INI de GameMaker es completamente transparente y seguro para el almacenamiento de preferencias de usuario sencillas. Funciona de manera idéntica en Windows, macOS, Linux, navegadores HTML5 y consolas, guardando los datos en la zona de almacenamiento local (Sandbox) reservada para tu juego.
Prueba e integración completa en el proyecto retro
Para comprobar de forma tangible la solidez de todo lo construido a lo largo de este Módulo 9, realicemos una prueba integrada en nuestra room de nivel:
1. Abre la room inicial de tu proyecto (por ejemplo `rm_menu_inicio` o `rm_nivel_1`). 2. Crea una instancia de `obj_controlador_audio` colocándola en una capa no conflictiva (como `Instances`). Al ser un objeto persistente, una sola instancia creada al inicio bastará para toda la ejecución del juego. 3. Asegúrate de tener asignado en el evento de inicio una llamada para ambientar la pantalla: `reproducir_musica(snd_musica_nivel_1, 1500);`. 4. Inicia la partida ejecutando el juego en modo de prueba (F5). 5. Desplaza los sliders en pantalla con el ratón. Comprobarás que al arrastrar la barra de música hacia la izquierda, el bucle instrumental disminuye su potencia de forma lineal sin entrecortarse. Al saltar o disparar con el jugador, el efecto de sonido mantendrá su volumen proporcional o se atenuará en tiempo real si modificas la barra de SFX.
Ejercicios prácticos
Para afianzar los conceptos técnicos y lógicos explicados en esta lección, completa las siguientes tres actividades prácticas:
1. **Selector de volumen por pasos escalonados (Discreto):** En lugar de un arrastre continuo en coma flotante, modifica la lectura del slider para que el volumen solo pueda ajustarse en tramos cerrados del 25% (0%, 25%, 50%, 75%, 100%). *Criterio de comprobación:* El valor numérico mostrado en el texto del evento Draw GUI solo debe exhibir múltiplos de 25, y la barra debe saltar de manera precisa a esas cuatro posiciones visuales al hacer clic.
2. **Botón conmutador de silencio general (Mute Toggle):** Añade al objeto `obj_controlador_audio` la tecla 'M' (o un botón interactivo dibujado en el GUI) para alternar entre el estado enmudecido (volumen maestro a 0.0) y el restablecimiento del volumen previo antes del corte. *Criterio de comprobación:* Al pulsar 'M', el sonido general del juego debe cortarse a 0. Al volver a pulsar 'M', el volumen maestro debe volver exactamente al nivel que tenía antes de pulsar la tecla, sin perderse la posición del slider.
3. **Atenuación automática durante la pausa (Audio Ducking):** Conecta el sistema de audio con el menú de pausa que construimos en el Módulo 8. Cuando el juego entre en estado de pausa, la música debe atenuar su volumen a la mitad de su valor configurado (`global.volumen_musica * 0.5`) de forma gradual mediante un fade de 500 milisegundos, y regresar a su nivel regular al reanudar la partida. *Criterio de comprobación:* Al pausar el juego, la música de fondo se percibe más tenue permitiendo destacar el menú, y al continuar la aventura regresa a su presencia original sin saltos auditivos.
Resumen
- La ganancia (gain) en GameMaker es una medida normalizada en coma flotante entre 0.0 (silencio total) y 1.0 (volumen máximo nominal).
- La función audio_master_gain gobierna la intensidad global de toda la señal sonora de la aplicación.
- Separar la música y los efectos sonoros mediante funciones modulares (reproducir_sfx, reproducir_musica) simplifica la calibración y el balance acústico del proyecto.
- El evento Draw GUI junto con device_mouse_x_to_gui y device_mouse_y_to_gui proporciona un entorno exacto para diseñar sliders interactivos inmunes al movimiento de la cámara.
- El uso de clamp asegura que los valores de entrada del usuario permanezcan siempre en rangos físicos y seguros para el hardware de reproducción.
- El almacenamiento en archivos INI (ini_read_real / ini_write_real) garantiza que las preferencias de sonido persistan automáticamente entre diferentes sesiones de juego.
Practica lo aprendido
Ejercicio 1 · 1 punto
Relaciona cada función nativa de GameMaker con su respectiva responsabilidad en el control de ganancia de audio:
- audio_master_gain(gain)
- audio_sound_gain(index, gain, time)
- audio_group_set_gain(group, gain, time_ms)
Ejercicio 2 · 1 punto
En GameMaker, el volumen final es el resultado de multiplicar las ganancias consecutivas en cascada. Si un sonido individual (snd_salto) se emite con ganancia 0.8, el canal de efectos (SFX) está en 0.5 y el volumen maestro general se encuentra en 0.5, ¿cuál es la ganancia final real de ese sonido? (Introduce el valor en formato decimal con punto).
Ejercicio 3 · 2 puntos
En el script del sistema de audio, completa la función reproducir_sfx utilizando audio_sound_gain para aplicar inmediatamente (en 0 milisegundos de transición) la ganancia almacenada en global.volumen_sfx a la variable local _instancia.
Tu código no se ejecuta: se comprueban requisitos concretos sobre el texto.
Ingresa para guardar tu progreso — Ingresar