← Volver al listado de tecnologías

Audio y Música Dinámica

Por: Artiko
defoldaudiomusicasoundsfxdinamico

Audio y Música Dinámica

El audio es fundamental para crear experiencias inmersivas en los videojuegos. En esta lección aprenderás a usar el sistema de audio de Defold para crear música adaptativa, efectos de sonido dinámicos, y sistemas de audio optimizados que funcionen perfectamente en dispositivos móviles.

Sistema de Audio de Defold

Defold utiliza un sistema de audio moderno basado en grupos de sonidos que permite control granular sobre volumen, mezcla y efectos.

Arquitectura de Audio

-- Estructura típica del sistema de audio
audio_system/
├── music/
│   ├── background_music.ogg
│   ├── boss_battle.ogg
│   └── menu_theme.ogg
├── sfx/
│   ├── player_sounds/
│   ├── enemy_sounds/
│   ├── ui_sounds/
│   └── environment_sounds/
├── groups/
│   ├── master.group
│   ├── music.group
│   ├── sfx.group
│   └── voice.group
└── controllers/
    ├── audio_manager.script
    ├── music_controller.script
    └── sfx_controller.script

Configuración de Grupos de Audio

-- En game.project configurar grupos
[audio]
max_sound_buffers = 32
max_sound_instances = 256
max_sound_instances_per_component = 16

Proyecto Completo: Sistema de Audio Dinámico

Audio Manager Principal

-- controllers/audio_manager.script
local music_controller = require "controllers.music_controller"
local sfx_controller = require "controllers.sfx_controller"

local M = {}

function init(self)
    -- Configuración de grupos de audio
    self.audio_groups = {
        master = "master",
        music = "music",
        sfx = "sfx",
        voice = "voice",
        ui = "ui"
    }

    -- Configuración por defecto
    self.settings = {
        master_volume = 0.8,
        music_volume = 0.6,
        sfx_volume = 0.8,
        voice_volume = 1.0,
        ui_volume = 0.7
    }

    -- Estado actual
    self.current_music_state = "none"
    self.music_transition_active = false

    -- Inicializar subsistemas
    music_controller.init()
    sfx_controller.init()

    -- Aplicar configuración inicial
    apply_audio_settings(self)

    print("Audio Manager inicializado")
end

function apply_audio_settings(self)
    for group_name, volume in pairs(self.settings) do
        local group_hash = hash(group_name:gsub("_volume", ""))
        sound.set_group_gain(group_hash, volume)
        print("Configurado", group_name, "a", volume)
    end
end

function update(self, dt)
    -- Actualizar subsistemas
    music_controller.update(dt)
    sfx_controller.update(dt)
end

-- API pública de configuración
function M.set_master_volume(volume)
    M.settings.master_volume = math.max(0, math.min(1, volume))
    sound.set_group_gain(hash("master"), M.settings.master_volume)
end

function M.set_music_volume(volume)
    M.settings.music_volume = math.max(0, math.min(1, volume))
    sound.set_group_gain(hash("music"), M.settings.music_volume)
end

function M.set_sfx_volume(volume)
    M.settings.sfx_volume = math.max(0, math.min(1, volume))
    sound.set_group_gain(hash("sfx"), M.settings.sfx_volume)
end

function M.mute_all()
    for group_name, _ in pairs(M.audio_groups) do
        sound.set_group_gain(hash(group_name), 0)
    end
    M.audio_muted = true
end

function M.unmute_all()
    apply_audio_settings(M)
    M.audio_muted = false
end

-- Transiciones de música
function M.transition_to_music(new_music_state, transition_time)
    if M.current_music_state == new_music_state then
        return
    end

    print("Transición musical:", M.current_music_state, "→", new_music_state)
    music_controller.transition_to(new_music_state, transition_time or 2.0)
    M.current_music_state = new_music_state
end

-- Control de efectos de sonido
function M.play_sfx(sound_name, options)
    return sfx_controller.play(sound_name, options)
end

function M.stop_sfx(sound_id)
    sfx_controller.stop(sound_id)
end

-- Guardar/cargar configuración
function M.save_audio_settings()
    local save_data = {
        audio_settings = M.settings
    }

    local save_string = json.encode(save_data)
    local path = sys.get_save_file("audio", "settings.json")
    local file = io.open(path, "w")

    if file then
        file:write(save_string)
        file:close()
        print("Configuración de audio guardada")
    end
end

function M.load_audio_settings()
    local path = sys.get_save_file("audio", "settings.json")
    local file = io.open(path, "r")

    if file then
        local save_string = file:read("*a")
        file:close()

        local save_data = json.decode(save_string)
        if save_data and save_data.audio_settings then
            M.settings = save_data.audio_settings
            apply_audio_settings(M)
            print("Configuración de audio cargada")
        end
    end
end

return M

Parte 1: Sistema de Música Adaptativa

Music Controller

-- controllers/music_controller.script
local M = {}

-- Configuración de estados musicales
local MUSIC_STATES = {
    menu = {
        track = "/music/menu_theme.ogg",
        loop = true,
        volume = 0.8,
        crossfade_time = 1.0
    },
    exploration = {
        track = "/music/exploration.ogg",
        loop = true,
        volume = 0.6,
        crossfade_time = 2.0
    },
    action = {
        track = "/music/action_theme.ogg",
        loop = true,
        volume = 0.9,
        crossfade_time = 1.5
    },
    boss_battle = {
        track = "/music/boss_battle.ogg",
        loop = true,
        volume = 1.0,
        crossfade_time = 0.5
    },
    victory = {
        track = "/music/victory.ogg",
        loop = false,
        volume = 0.8,
        crossfade_time = 1.0
    },
    ambient = {
        tracks = {
            "/music/ambient_1.ogg",
            "/music/ambient_2.ogg",
            "/music/ambient_3.ogg"
        },
        loop = true,
        volume = 0.4,
        random_selection = true,
        crossfade_time = 3.0
    }
}

function M.init()
    M.current_track = nil
    M.current_state = "none"
    M.transition_timer = 0
    M.transitioning = false
    M.fade_in_track = nil
    M.fade_out_track = nil

    -- Configurar sound components
    M.track_a_url = msg.url(".", "music_track_a", "")
    M.track_b_url = msg.url(".", "music_track_b", "")

    print("Music Controller inicializado")
end

function M.update(dt)
    if M.transitioning then
        update_music_transition(dt)
    end

    -- Verificar si la música actual terminó (para tracks no looped)
    check_track_completion()
end

function M.transition_to(new_state, transition_time)
    if M.current_state == new_state then
        return
    end

    local state_config = MUSIC_STATES[new_state]
    if not state_config then
        print("Estado musical no encontrado:", new_state)
        return
    end

    -- Configurar transición
    M.transitioning = true
    M.transition_timer = transition_time or state_config.crossfade_time or 2.0
    M.transition_duration = M.transition_timer
    M.target_state = new_state

    -- Seleccionar track para estados con múltiples opciones
    local target_track = select_track_for_state(state_config)

    start_track_transition(target_track, state_config)
end

function select_track_for_state(state_config)
    if state_config.track then
        return state_config.track
    elseif state_config.tracks then
        if state_config.random_selection then
            local index = math.random(1, #state_config.tracks)
            return state_config.tracks[index]
        else
            -- Selección secuencial o basada en lógica del juego
            return state_config.tracks[1]
        end
    end
    return nil
end

function start_track_transition(new_track, state_config)
    -- Determinar qué track slot usar
    local target_url = M.current_track == M.track_a_url and M.track_b_url or M.track_a_url

    -- Configurar nuevo track
    sound.load(target_url, new_track)

    M.fade_out_track = M.current_track
    M.fade_in_track = target_url
    M.target_volume = state_config.volume

    -- Iniciar reproducción del nuevo track con volumen 0
    if M.fade_in_track then
        sound.set_gain(M.fade_in_track, 0)
        sound.play(M.fade_in_track, {delay = 0, gain = 0})
    end

    print("Iniciando transición musical a:", new_track)
end

function update_music_transition(dt)
    M.transition_timer = M.transition_timer - dt
    local progress = 1.0 - (M.transition_timer / M.transition_duration)

    if progress >= 1.0 then
        complete_music_transition()
        return
    end

    -- Aplicar curva de crossfade
    local fade_out_volume = (1.0 - progress) * 0.8
    local fade_in_volume = progress * M.target_volume

    -- Aplicar volúmenes
    if M.fade_out_track then
        sound.set_gain(M.fade_out_track, fade_out_volume)
    end

    if M.fade_in_track then
        sound.set_gain(M.fade_in_track, fade_in_volume)
    end
end

function complete_music_transition()
    -- Detener track anterior
    if M.fade_out_track then
        sound.stop(M.fade_out_track)
    end

    -- Configurar nuevo track como actual
    M.current_track = M.fade_in_track
    M.current_state = M.target_state

    -- Limpiar transición
    M.transitioning = false
    M.fade_in_track = nil
    M.fade_out_track = nil

    print("Transición musical completada a:", M.current_state)
end

function check_track_completion()
    if M.current_track and not M.transitioning then
        local state_config = MUSIC_STATES[M.current_state]

        if state_config and not state_config.loop then
            -- Verificar si el track terminó
            if not sound.is_playing(M.current_track) then
                handle_track_completion()
            end
        end
    end
end

function handle_track_completion()
    print("Track completado:", M.current_state)

    -- Lógica para después de completar un track
    if M.current_state == "victory" then
        M.transition_to("menu", 2.0)
    elseif M.current_state == "boss_battle" then
        -- Podría transicionar a exploration o action según contexto
        M.transition_to("exploration", 1.5)
    end
end

-- API para control dinámico
function M.pause()
    if M.current_track then
        sound.pause(M.current_track)
    end
end

function M.resume()
    if M.current_track then
        sound.resume(M.current_track)
    end
end

function M.stop()
    if M.current_track then
        sound.stop(M.current_track)
        M.current_track = nil
        M.current_state = "none"
    end
end

function M.set_volume(volume)
    if M.current_track then
        sound.set_gain(M.current_track, volume)
    end
end

-- Función para cambio dinámico basado en gameplay
function M.evaluate_music_state(game_context)
    local new_state = "exploration"  -- Default

    if game_context.in_combat then
        if game_context.boss_fight then
            new_state = "boss_battle"
        else
            new_state = "action"
        end
    elseif game_context.in_menu then
        new_state = "menu"
    elseif game_context.player_won then
        new_state = "victory"
    elseif game_context.ambient_area then
        new_state = "ambient"
    end

    if new_state ~= M.current_state then
        M.transition_to(new_state)
    end
end

return M

Parte 2: Sistema de Efectos de Sonido

SFX Controller con Variaciones

-- controllers/sfx_controller.script
local M = {}

-- Base de datos de efectos de sonido
local SFX_DATABASE = {
    player_jump = {
        files = {"/sfx/player/jump_1.ogg", "/sfx/player/jump_2.ogg", "/sfx/player/jump_3.ogg"},
        volume = 0.8,
        pitch_variance = 0.1,
        group = "sfx"
    },
    player_land = {
        files = {"/sfx/player/land_1.ogg", "/sfx/player/land_2.ogg"},
        volume = 0.6,
        pitch_variance = 0.15,
        group = "sfx"
    },
    enemy_hit = {
        files = {"/sfx/combat/hit_1.ogg", "/sfx/combat/hit_2.ogg", "/sfx/combat/hit_3.ogg"},
        volume = 0.9,
        pitch_variance = 0.2,
        group = "sfx"
    },
    coin_collect = {
        files = {"/sfx/items/coin_1.ogg", "/sfx/items/coin_2.ogg"},
        volume = 0.7,
        pitch_variance = 0.05,
        group = "sfx"
    },
    button_click = {
        files = {"/sfx/ui/click.ogg"},
        volume = 0.8,
        pitch_variance = 0,
        group = "ui"
    },
    explosion = {
        files = {"/sfx/effects/explosion_big.ogg"},
        volume = 1.0,
        pitch_variance = 0.1,
        group = "sfx",
        priority = "high"
    },
    footsteps = {
        files = {
            "/sfx/player/step_grass_1.ogg",
            "/sfx/player/step_grass_2.ogg",
            "/sfx/player/step_grass_3.ogg"
        },
        volume = 0.4,
        pitch_variance = 0.1,
        group = "sfx",
        surface_variants = {
            stone = {"/sfx/player/step_stone_1.ogg", "/sfx/player/step_stone_2.ogg"},
            water = {"/sfx/player/step_water_1.ogg", "/sfx/player/step_water_2.ogg"},
            metal = {"/sfx/player/step_metal_1.ogg", "/sfx/player/step_metal_2.ogg"}
        }
    }
}

function M.init()
    M.active_sounds = {}
    M.sound_pools = {}
    M.next_sound_id = 1

    -- Crear pools de sonidos para optimización
    create_sound_pools()

    print("SFX Controller inicializado")
end

function create_sound_pools()
    -- Pre-cargar sonidos frecuentemente usados
    for sfx_name, config in pairs(SFX_DATABASE) do
        if config.priority == "high" or sfx_name == "footsteps" or sfx_name == "player_jump" then
            M.sound_pools[sfx_name] = {}

            for i = 1, 3 do  -- Pool de 3 instancias por sonido
                local sound_id = factory.create("/sfx_factories#generic_sound_factory")
                table.insert(M.sound_pools[sfx_name], sound_id)
            end
        end
    end
end

function M.play(sfx_name, options)
    local config = SFX_DATABASE[sfx_name]
    if not config then
        print("SFX no encontrado:", sfx_name)
        return nil
    end

    options = options or {}

    -- Seleccionar archivo de sonido
    local sound_file = select_sound_file(config, options)

    -- Obtener sound component del pool o crear uno nuevo
    local sound_url = get_sound_from_pool(sfx_name) or create_new_sound()

    -- Configurar propiedades
    local final_volume = (options.volume or config.volume) * (options.volume_multiplier or 1.0)
    local pitch = 1.0 + (math.random() - 0.5) * config.pitch_variance

    -- Cargar y reproducir
    sound.load(sound_url, sound_file)
    sound.set_gain(sound_url, final_volume)

    local play_options = {
        delay = options.delay or 0,
        gain = final_volume,
        pan = options.pan or 0,
        speed = pitch
    }

    sound.play(sound_url, play_options)

    -- Trackear sonido activo
    local sound_id = M.next_sound_id
    M.next_sound_id = M.next_sound_id + 1

    M.active_sounds[sound_id] = {
        url = sound_url,
        sfx_name = sfx_name,
        start_time = socket.gettime(),
        duration = options.duration or estimate_sound_duration(sound_file)
    }

    print("Reproduciendo SFX:", sfx_name, "ID:", sound_id)
    return sound_id
end

function select_sound_file(config, options)
    local files = config.files

    -- Variantes de superficie para footsteps
    if config.surface_variants and options.surface then
        local variant_files = config.surface_variants[options.surface]
        if variant_files then
            files = variant_files
        end
    end

    -- Selección aleatoria o secuencial
    if options.file_index then
        return files[options.file_index] or files[1]
    else
        return files[math.random(1, #files)]
    end
end

function get_sound_from_pool(sfx_name)
    local pool = M.sound_pools[sfx_name]
    if pool and #pool > 0 then
        return table.remove(pool)
    end
    return nil
end

function create_new_sound()
    return factory.create("/sfx_factories#generic_sound_factory")
end

function M.stop(sound_id)
    local sound_data = M.active_sounds[sound_id]
    if sound_data then
        sound.stop(sound_data.url)
        return_sound_to_pool(sound_data.url, sound_data.sfx_name)
        M.active_sounds[sound_id] = nil
    end
end

function M.update(dt)
    -- Limpiar sonidos terminados
    local current_time = socket.gettime()

    for sound_id, sound_data in pairs(M.active_sounds) do
        if current_time - sound_data.start_time > sound_data.duration then
            return_sound_to_pool(sound_data.url, sound_data.sfx_name)
            M.active_sounds[sound_id] = nil
        end
    end
end

function return_sound_to_pool(sound_url, sfx_name)
    local pool = M.sound_pools[sfx_name]
    if pool and #pool < 5 then  -- Limitar tamaño del pool
        table.insert(pool, sound_url)
    else
        go.delete(sound_url)
    end
end

-- Funciones de conveniencia para sonidos específicos
function M.play_footstep(surface_type)
    return M.play("footsteps", {surface = surface_type or "grass"})
end

function M.play_ui_sound(ui_element)
    if ui_element == "button" then
        return M.play("button_click")
    elseif ui_element == "coin" then
        return M.play("coin_collect")
    end
end

function M.play_combat_sound(action, intensity)
    if action == "hit" then
        local volume_multiplier = intensity and (0.5 + intensity * 0.5) or 1.0
        return M.play("enemy_hit", {volume_multiplier = volume_multiplier})
    elseif action == "explosion" then
        return M.play("explosion")
    end
end

-- Control de grupos de sonidos
function M.stop_all_sfx()
    for sound_id, _ in pairs(M.active_sounds) do
        M.stop(sound_id)
    end
end

function M.pause_all_sfx()
    for _, sound_data in pairs(M.active_sounds) do
        sound.pause(sound_data.url)
    end
end

function M.resume_all_sfx()
    for _, sound_data in pairs(M.active_sounds) do
        sound.resume(sound_data.url)
    end
end

function estimate_sound_duration(sound_file)
    -- Estimación básica - en un proyecto real podrías tener una database con duraciones exactas
    return 2.0  -- Valor por defecto conservador
end

return M

Parte 3: Audio Espacial y 3D

3D Audio Controller

-- controllers/spatial_audio.script
local M = {}

function M.init()
    M.listener_position = vmath.vector3(0, 0, 0)
    M.spatial_sources = {}

    print("Spatial Audio Controller inicializado")
end

function M.set_listener_position(position)
    M.listener_position = position

    -- Actualizar todas las fuentes espaciales
    for source_id, source_data in pairs(M.spatial_sources) do
        update_spatial_audio(source_id, source_data)
    end
end

function M.play_spatial_sound(sfx_name, world_position, options)
    options = options or {}

    -- Calcular audio espacial
    local distance = vmath.length(world_position - M.listener_position)
    local max_distance = options.max_distance or 500
    local min_distance = options.min_distance or 50

    -- Atenuación por distancia
    local distance_factor = 1.0
    if distance > min_distance then
        distance_factor = math.max(0, 1.0 - (distance - min_distance) / (max_distance - min_distance))
    end

    -- Pan estéreo basado en posición horizontal
    local pan = 0
    if math.abs(world_position.x - M.listener_position.x) > 10 then
        pan = math.max(-1, math.min(1, (world_position.x - M.listener_position.x) / 200))
    end

    -- Reproducir con configuración espacial
    local spatial_options = {
        volume_multiplier = distance_factor,
        pan = pan,
        world_position = world_position
    }

    for key, value in pairs(options) do
        spatial_options[key] = value
    end

    local sound_id = require("controllers.sfx_controller").play(sfx_name, spatial_options)

    -- Trackear como fuente espacial
    if sound_id then
        M.spatial_sources[sound_id] = {
            position = world_position,
            max_distance = max_distance,
            min_distance = min_distance
        }
    end

    return sound_id
end

function update_spatial_audio(source_id, source_data)
    local distance = vmath.length(source_data.position - M.listener_position)

    -- Actualizar volumen basado en distancia
    local distance_factor = 1.0
    if distance > source_data.min_distance then
        distance_factor = math.max(0, 1.0 - (distance - source_data.min_distance) /
                                 (source_data.max_distance - source_data.min_distance))
    end

    -- Actualizar pan
    local pan = 0
    if math.abs(source_data.position.x - M.listener_position.x) > 10 then
        pan = math.max(-1, math.min(1, (source_data.position.x - M.listener_position.x) / 200))
    end

    -- Aplicar cambios al sonido activo
    local sfx_controller = require("controllers.sfx_controller")
    local sound_data = sfx_controller.active_sounds[source_id]

    if sound_data then
        sound.set_gain(sound_data.url, distance_factor * 0.8)  -- Factor base de volumen
        sound.set_pan(sound_data.url, pan)
    end
end

function M.remove_spatial_source(source_id)
    M.spatial_sources[source_id] = nil
end

return M

Parte 4: Sistema de Audio Reactivo

Audio que Responde al Gameplay

-- controllers/reactive_audio.script
local audio_manager = require "controllers.audio_manager"
local music_controller = require "controllers.music_controller"

local M = {}

function M.init()
    M.game_state = {
        health_percentage = 1.0,
        stress_level = 0.0,
        combat_intensity = 0.0,
        environment_type = "normal"
    }

    M.audio_effects = {
        low_health_filter = false,
        stress_reverb = 0.0,
        combat_mix = false
    }

    print("Reactive Audio Controller inicializado")
end

function M.update_game_state(new_state)
    local old_state = M.game_state
    M.game_state = new_state

    -- Reaccionar a cambios significativos
    check_health_changes(old_state.health_percentage, new_state.health_percentage)
    check_stress_changes(old_state.stress_level, new_state.stress_level)
    check_combat_changes(old_state.combat_intensity, new_state.combat_intensity)
    check_environment_changes(old_state.environment_type, new_state.environment_type)

    -- Actualizar efectos de audio
    update_audio_effects()
end

function check_health_changes(old_health, new_health)
    if new_health < 0.3 and old_health >= 0.3 then
        -- Activar filtro de salud baja
        M.audio_effects.low_health_filter = true
        start_heartbeat_audio()
    elseif new_health >= 0.3 and old_health < 0.3 then
        -- Desactivar filtro de salud baja
        M.audio_effects.low_health_filter = false
        stop_heartbeat_audio()
    end
end

function check_stress_changes(old_stress, new_stress)
    if math.abs(new_stress - old_stress) > 0.1 then
        M.audio_effects.stress_reverb = new_stress
        update_reverb_effect(new_stress)
    end
end

function check_combat_changes(old_intensity, new_intensity)
    if new_intensity > 0.5 and old_intensity <= 0.5 then
        M.audio_effects.combat_mix = true
        apply_combat_audio_mix()
    elseif new_intensity <= 0.5 and old_intensity > 0.5 then
        M.audio_effects.combat_mix = false
        remove_combat_audio_mix()
    end
end

function check_environment_changes(old_env, new_env)
    if old_env ~= new_env then
        transition_environment_audio(old_env, new_env)
    end
end

function start_heartbeat_audio()
    -- Reproducir latido cardíaco cuando la salud es baja
    timer.delay(0, true, function(self, handle, time_elapsed)
        if M.audio_effects.low_health_filter then
            audio_manager.play_sfx("heartbeat", {volume = 0.3})
        else
            timer.cancel(handle)
        end
    end)
end

function stop_heartbeat_audio()
    -- El heartbeat se detiene automáticamente con el timer
end

function update_reverb_effect(stress_level)
    -- Simular efecto de reverb basado en estrés
    -- En un proyecto real, esto se haría con effectos DSP
    local reverb_amount = stress_level * 0.5

    msg.post("@render:", "set_reverb", {amount = reverb_amount})
end

function apply_combat_audio_mix()
    -- Aumentar volumen de SFX y reducir música durante combate
    sound.set_group_gain(hash("sfx"), 1.0)
    sound.set_group_gain(hash("music"), 0.4)

    -- Filtro de paso alto para mayor intensidad
    msg.post("@render:", "set_highpass_filter", {enabled = true})
end

function remove_combat_audio_mix()
    -- Restaurar mix normal
    sound.set_group_gain(hash("sfx"), 0.8)
    sound.set_group_gain(hash("music"), 0.6)

    msg.post("@render:", "set_highpass_filter", {enabled = false})
end

function transition_environment_audio(old_env, new_env)
    -- Cambiar música ambiental basada en entorno
    local music_state_map = {
        forest = "ambient",
        cave = "exploration",
        boss_room = "boss_battle",
        town = "menu"
    }

    local new_music_state = music_state_map[new_env] or "exploration"
    music_controller.transition_to(new_music_state, 3.0)

    -- Ajustar reverb ambiental
    local reverb_map = {
        cave = 0.8,
        forest = 0.3,
        town = 0.1,
        boss_room = 0.6
    }

    local reverb_amount = reverb_map[new_env] or 0.2
    update_reverb_effect(reverb_amount)
end

-- API para el game manager
function M.on_player_hit(damage_amount)
    local intensity = math.min(1.0, damage_amount / 100)
    audio_manager.play_sfx("player_hit", {volume_multiplier = intensity})

    -- Screen shake audio cue
    audio_manager.play_sfx("impact", {pitch_variance = 0.3})
end

function M.on_enemy_killed(enemy_type)
    if enemy_type == "boss" then
        audio_manager.play_sfx("boss_death")
        music_controller.transition_to("victory", 1.0)
    else
        audio_manager.play_sfx("enemy_death")
    end
end

function M.on_level_complete()
    music_controller.transition_to("victory", 2.0)
    audio_manager.play_sfx("level_complete")
end

function M.on_powerup_collected(powerup_type)
    local sfx_map = {
        speed = "powerup_speed",
        health = "powerup_health",
        weapon = "powerup_weapon"
    }

    local sfx_name = sfx_map[powerup_type] or "powerup_generic"
    audio_manager.play_sfx(sfx_name)
end

return M

Ejercicios Avanzados

Ejercicio 1: Sistema de Diálogos

Crea un sistema de diálogos con:

Ejercicio 2: Audio Procedural

Implementa generación de audio en tiempo real:

Ejercicio 3: Sistema de Mixing Inteligente

Crea un mixer que se adapta automáticamente:

Próximos Pasos

En la lección final aprenderemos sobre publicación y monetización, preparando nuestros juegos para llegar al mercado.


⬅️ Anterior: Efectos y Partículas | Siguiente: Publicación y Monetización ➡️

¡Excelente! Ahora dominas el sistema de audio de Defold y puedes crear experiencias sonoras inmersivas que elevarán la calidad de tus juegos al siguiente nivel.