Skip to content

Setting Up a Sound Controller in Defold

I tried various approaches to managing sounds in Defold and learned what works. Here’s the distilled version.

One Game Object to Rule Them All

You might be tempted to group sounds with their respective entities, but don’t. Instead, put all sounds into a single sound GameObject. Use namespaced names and trigger sounds from an audio module.

Ensure the sound GameObject is loaded into the outermost collection (usually main.collection) to keep it accessible across different scenes and when the game is paused.

The simplest thing is having one audio.go with every sound resource in the game loaded into it and one audio.lua module.

Improving the Sound Gate Code from the Docs

If you haven't read the sound manual in the Defold docs, you're probably getting ahead of yourself by being here. The default sound gate code is a good place to start but it wasn't enough for my needs.

The TLDR is: When multiple sounds trigger frequently, you risk playing the same sound multiple times simultaneously, causing phase shifts and audio artifacts.

The basic gate implementation treats every sound individually. But in games where multiple sounds play frequently, you probably use sequences or random variations for each sound. In such cases, the default sound gate breaks down since it doesn’t know how to gate a category of sounds. It also fails to account for the fact that different sounds need different gate times.

Categories vs Sound Groups

Real quick before we get into the code: Categories in this script control sound gate timing (and sometimes custom logic for selecting a sound in a group of sounds). They are not the same as Defold’s "sound groups," which control volume adjustments for grouped sounds. Don't confuse the two.

-- We'll be using this to calculate if gate times are 
-- elapsed without the update function
local socket = require "socket"


local M = {}

M.gates = {}


-- You'll need to use the names for your collection and gameobject
local collection = "main"
local gameobject = "/audio"


-- define your gate times here with category names; 
-- they should match the sound keys in your /audio GameObject
local gate_times = {
    default         = 0.3,
    enemy_death     = 0.3,
    collect_exp     = 0.3,
    player_fire     = 0.1,
    collect_crystal = 4.0,
    crystal_drop    = 0.3,
}


-- helper function
local function gate(gate_key)

    local current_time = socket.gettime()

    if M.gates[gate_key] and (current_time < M.gates[gate_key]) then
        print("Sound " .. gate_key .. " is still gated.")
        return false
    end

    local gate_time = gate_times[gate_key] or gate_times["default"]

    M.gates[gate_key] = current_time + gate_time
    return true

end

-- this is the function we will call from scripts for general sounds
function M.play_sound(soundcomponent, gain, category)

    local gate_key = category or soundcomponent

    local play = gate(gate_key)

    if play then
        local url = msg.url(collection, gameobject, soundcomponent)
        local gain = gain or 1.0
        sound.play(url, { gain = gain })
    end

end

-- example of function that handles a category of sounds.
function M.play_enemy_death()

    math.randomseed(os.time())

    local index = math.random(1, 5)
    local sound_key = "death_" .. index
    local gain = 1.0
    local category = "enemy_death"

    M.play_sound(sound_key, gain, category)

end

-- note: we removed the update and on_message functions from docs example

return M

Triggering Sounds

You trigger sounds by importing the audio module at the top of scripts where you need it.

local audio = require "modules.audio"

Now you can easily do

audio.play_sound("some_sound")

Keep in mind that we get some_sound from the fragment: main:/audio#some_sound

If the sound_key used exists in the gate_times table, it will use that time, otherwise it will use the default.

Of course we can also call on more specific logic using:

audio.play_enemy_death()

But the play_sound is useful for sounds that don't require it.

Conclusion

Stick to one unified game object and sound controller in a lua module. Keep all things audio related organized there.


I'll update this article as I learn more about managing sound in Defold. If you have any comments, suggestions, or criticism about this leave a comment.

Thanks to Jay and Alex for tips to improve this article.