Read this manual in English

声音

Defold 支持声音但是不那么强大. 要注意两个概念:

声音组件
实际包含声音播放声音的组件.
声音组
每个声音组件在设计上都属于一个 . 组比单独控制声音组件更方便. 比如, 建立一个 “sound_fx” 组可以调用一次函数关闭其中所有声音.

创建声音组件

声音组件只能在游戏对象里创建. 创建好游戏对象, 在其上面右键点击选择 Add Component ▸ Sound 然后点击 OK.

Select component

声音组件有一系列属性可以设置:

Select component

Sound
从项目中选择一个声音文件. 文件需要 Wave 或者 Ogg Vorbis 格式. Defold 支持 16bit 位深和 44100 采样率的声音文件.
Looping
开启此选项声音会循环播放除非手动停止.
Group
声音属于的组. 如果置空, 此声音默认归属 “master” 组.
Gain
声音增益. 可以在这里直接设置声音增益而不用从声音软件再导出. 增益算法见下文.
Pan
声音生像. 范围从 -1 (左 -45 度) 到 1 (右 45 度).
Speed
声音速度. 一般速度为 1.0, 0.5 是半倍速 2.0 是二倍速.

播放声音

设置好声音属性之后, 可以通过调用 sound.play() 函数播放声音:

sound.play("go#sound", {delay = 1, gain = 0.5, pan = -1.0, speed = 1.25})

::: 注意 即使声音组件所在游戏对象被删除了, 声音也会继续播放. 可以通过调用 sound.stop() 函数停止播放 (见下文). ::: 只要发送消息到声音组件都会造成新声音实例的播放, 直到声音缓存满溢引擎报错. 所以建议自己实现一个控制或者分组机制.

停止播放声音

可以通过调用 sound.stop() 函数停止播放声音:

sound.stop("go#sound")

增益

Gain

声音系统有 4 级增益:

  • 声音组件增益属性设置.
  • 调用 sound.play() 函数或者直接调用 sound.set_gain() 函数设置的增益.
  • 调用 sound.set_group_gain() 函数设置的增益.
  • “master” 组上设置的增益. 可以通过调用 sound.set_group_gain(hash("master")) 函数修改设置.

结果是4级增益的乘积. 默认每个增益都是 1.0 (0 dB).

声音组

在声音组件上设置组名就把这个声音归到了那个组里. 如果不设置组名默认归到 “master” 组里. 设置组名为 “master” 效果相同.

有一系列函数用于获得声音组, 声音名, 获得/设置增益, 均方根 (见 http://en.wikipedia.org/wiki/Root_mean_square) 和峰值. 还有一个函数用于检测设备播放器是否正在运行:

-- 如果 iPhone/Android 设备的播放器正在播放引用, 则所有游戏声音静音
if sound.is_music_playing() then
    for i, group_hash in ipairs(sound.get_groups()) do
        sound.set_group_gain(group_hash, 0)
    end
end

组名是个哈希值. 其字符串值可以使用 sound.get_group_name() 获得, 可以用来显示音轨名称.

Sound group mixer

::: 注意 代码里不要依赖字符串组名因为编译后不保存字符串组名. :::

所有值都是线性 0 到 1.0 (0 dB) 的范围. 要转换为分贝数, 可使用标准转换公式:

\[db = 20 \times \log \left( gain \right)\]
for i, group_hash in ipairs(sound.get_groups()) do
    -- 字符串组名只在调试时可用. 发布后会变成 "unknown_*".
    local name = sound.get_group_name(group_hash)
    local gain = sound.get_group_gain(group_hash)

    -- 转换为分贝.
    local db = 20 * math.log10(gain)

    -- 得到 RMS (增益均方根). 左右声道分开计算.
    local left_rms, right_rms = sound.get_rms(group_hash, 2048 / 65536.0)
    left_rmsdb = 20 * math.log10(left_rms)
    right_rmsdb = 20 * math.log10(right_rms)

    -- 得到峰值. 左右声道分开计算.
    left_peak, right_peak = sound.get_peak(group_hash, 2048 * 10 / 65536.0)
    left_peakdb = 20 * math.log10(left_peak)
    right_peakdb = 20 * math.log10(right_peak)
end

-- 设置主声道增益为 +6 dB (math.pow(10, 6/20)).
sound.set_group_gain("master", 1.995)

控制声音

如果用什么事件来触发声音播放, 就有可能造成同时播放2个或多个重复的声音. 这样的话, 声音会产生 偏移重叠 现象效果非常不好.

Phase shift

最简单的办法是设置一个过滤时间段对最近播放过的声音进行过滤:

-- 在一定 "过滤时间段" 内不允许再次播放同一声音.
local gate_time = 0.3

function init(self)
    -- 把计时器保存到一个表里然后每帧计时递减
    -- 直到过了 "过滤时间段". 再删除它.
    self.sounds = {}
end

function update(self, dt)
    -- 计时器递减
    for k,_ in pairs(self.sounds) do
        self.sounds[k] = self.sounds[k] - dt
        if self.sounds[k] < 0 then
            self.sounds[k] = nil
        end
    end
end

function on_message(self, message_id, message, sender)
    if message_id == hash("play_gated_sound") then
        -- 表里没有才能播放.
        if self.sounds[message.soundcomponent] == nil then
            -- 保存计时器
            self.sounds[message.soundcomponent] = gate_time
            -- 播放声音
            sound.play(message.soundcomponent, { gain = message.gain })
        else
            -- 过滤表里有的声音
            print("gated " .. message.soundcomponent)
        end
    end
end

这样, 只要把 play_gated_sound 消息连同增益发送给声音组件. 使用 sound.play() 播放函数前就会对声音进行过滤:

msg.post("/sound_gate#script", "play_gated_sound", { soundcomponent = "/sounds#explosion1", gain = 1.0 })

::: 注意 对于 play_sound 消息没法过滤因为该消息由 Defold 引擎内部保留. 如果使用引擎保留消息名会造成运行不正确. :::


Did you spot an error or do you have a suggestion? Please let us know on GitHub!

GITHUB