Appearance
Sound and music
Where a sound comes from
| Verb | Plays at | Loops? |
|---|---|---|
self:play_sound | this object, following it | no |
self:sound | this object, following it | yes |
world:play_sound | a fixed point in the region | no |
Use self: for anything that belongs to a thing. Use world: for something whose source does not survive it — an impact, an explosion.
Naming a sound
luau
self:play_sound("hammer") -- inside this object's Contents
self:play_sound("world/sounds/hammer") -- the world's shared library
self:play_sound("ui_click") -- a built-in cue, no upload neededThe slash is the whole difference. A bare name lives inside the object, so it travels with it — sell it, drop it in another world, and the sound is still there. A world/… name lives in the world you are standing in: one copy fifty objects share, swappable in one place, and nothing an object carries away.
A name that is none of those is refused on that line, rather than becoming silence on somebody else's machine.
A one-off noise
luau
self:play_sound("hammer", {
volume = 0.9,
pitch = { 0.95, 1.05 },
range = 40,
near = 3,
})| Option | What it is for |
|---|---|
volume | 0 – 4. 1 is the recorded level |
pitch | 0.25 – 4. A {low, high} pair picks one value per play |
range | metres past which nobody hears it |
near | metres inside which it stops getting louder |
falloff | "inverse" (real-world) or "linear" (a definite edge) |
air | 0 – 1, how much top end distance eats |
distant | another sound that takes over further out |
fade | seconds to fade in. 0 is a cut |
The three that matter most
pitch as a pair. Four identical samples a second sound like a machine. A little jitter and the pattern disappears. The region picks the value, so everybody in earshot hears the same one.
near. Without it, a sound you are standing on is infinitely loud.
range is a cost, not just a taste. It is also the radius the server uses to decide who is even told about the sound.
Making distance sound like distance
luau
self:play_sound("shot", {
range = 220,
near = 6,
air = 0.7,
distant = "shot_far",
})air eats the top end as distance grows, which is what makes far away sound muffled rather than merely quiet. Distance that is only quietness reads as small, not as far.
distant goes further: it crosses to a different recording. Something 300 metres away is not its own close-up turned down; it is a different sound.
Several variations
luau
local SHOTS = { "shot_01", "shot_02", "shot_03", "shot_04" }
self:play_sound(SHOTS[math.random(#SHOTS)], { pitch = { 0.96, 1.04 } })Four samples plus a little pitch jitter is enough that the ear stops finding the loop.
A continuous sound
luau
self:sound("motor", { volume = 0.4, fade = 1.5, channel = "motor" })
-- later
self:stop_sound("motor", 0.8)A looping emitter is a property of the object, not an event: it follows the object, and people who arrive later hear it too.
channel names it so one object can run several — a motor and a radio at once. Using the same channel twice replaces rather than stacking, which is what makes a script safe to re-run.
stop_sound with no channel stops the one started without naming one.
Let it fade. stop_sound defaults to a short ramp rather than to zero: a motor that stops dead sounds like a bug, and a cut in a waveform is an audible click.
A sound where something happened
luau
local world = require("world")
local hit = world:raycast({ from = shot.eye, dir = shot.dir })
if hit then
world:play_sound("impact", {
pos = hit.pos,
volume = 0.7,
pitch = { 0.9, 1.15 },
range = 45,
})
endpos is required, and it is pos rather than at because at in this API is always a socket on a body.
world:play_sound cannot loop, and the refusal says so: emitters are addressed by the object they sit on, and this one sits on nothing, so a loop could never be stopped.
Ambience in a zone
luau
--!strict
local self = require("self")
local inside = 0
self:on_region({
radius = 25,
near = function()
inside += 1
if inside == 1 then
self:sound("wind", { volume = 0.25, fade = 3, channel = "amb" })
end
end,
far = function()
inside -= 1
if inside <= 0 then
inside = 0
self:stop_sound("amb", 3)
end
end,
})Starting it only when the first person arrives means an empty region is silent and costs nothing.
Music
Music is a looping emitter like any other. Give it a wide range and a large near so it does not pan around as the player walks:
luau
self:sound("theme", {
volume = 0.35,
range = 200,
near = 190,
fade = 4,
channel = "music",
})With near almost as large as range, the sound stays even across the whole area instead of appearing to come from a point.
