Skip to content

Sound

KKE's sounds are mostly synthesized from impacts: a body with a sound material hits something, and the engine makes the right thump, clink or crack, louder for a harder hit, placed in 3D where it happened, muffled behind walls. A script's job is to say what things are made of.

Sound materials

One crate of every material, dropped in a row: listen to the difference. O drops them again, P plays a metal ping in front of you.

A row of crates, one per sound material

sounds.lua
-- sounds.lua: one crate of every sound material, dropped in a row. Each
-- hit plays the material's impact sound, louder the faster it hits; the
-- Contact hook prints who hit what. O drops them again.
-- (docs/cookbook/audio.md)

if not audio then
  print("sounds.lua: this game has no AudioModule")
  return
end

local names = {}
for name, id in pairs(audio.materials()) do table.insert(names, { name = name, id = id }) end
table.sort(names, function(a, b) return a.id < b.id end)   -- a stable order

local crates, nameOf = {}, {}
local function drop()
  for _, id in ipairs(crates) do physics.remove(id) end
  crates, nameOf = {}, {}
  for i, m in ipairs(names) do
    local id = physics.box { pos = Vec(-4 + i * 1.1, 2 + i * 0.4, 1), size = Vec(0.5, 0.5, 0.5), material = m.id,
                             color = Vec(0.3 + (i % 3) * 0.25, 0.5, 0.9 - (i % 4) * 0.15) }
    table.insert(crates, id)
    nameOf[id] = m.name
  end
end

-- The engine plays the sounds by itself; this only reports them.
hook.Add("Contact", "sounds.report", function(c)
  local who = nameOf[c.a] or nameOf[c.b]
  if who and c.speed > 1 then print(string.format("%s hit at %.1f m/s", who, c.speed)) end
end)

-- Your own sound, anywhere: audio.impact(position, material, loudness 0..1).
input.define("sounds.again", "Drop the crates again", "O")
input.define("sounds.ping", "Play a metal ping", "P")
hook.Add("Think", "sounds.keys", function()
  if input.pressed("sounds.again") then drop() end
  if input.pressed("sounds.ping") then audio.impact(camera.position() + camera.forward() * 2, "Metal", 0.8) end
end)
drop()

Download sounds.lua

  • audio.materials() is a table of name to id (Stone, Wood, Metal, Glass, ...). Pass the id as material when you make a body, and its hits make sound by themselves.
  • audio.impact(position, material, loudness) plays one on demand, for things that aren't collisions: a footstep, a pickup, a bell.
  • The Contact hook is how a script hears about hits too: c.speed is how hard, c.materialA and c.materialB what.

Where the sound comes from

Everything is positioned in 3D relative to the camera, so the crates on the left sound on the left. Sounds behind walls are quieter and duller (occlusion, by raycast), and a room's size changes its reverb. Nothing to set up: it's how AudioModule works. The settings players can change (volume, the sound visualizer for deaf and hard-of-hearing players, mono) are in the settings screen every starter game has.

Audio covers the synthesis, spatialization, occlusion and the optional Steam Audio backend; Tutorial 4 adds materials and a pickup chime to the tutorial game.

Next: multiplayer.