Hungrit Docs

Sound and music#

A click when something is touched, a hum that keeps going, an explosion where something hit, a mood in one corner of the world, and music.

Where a sound comes from#

VerbPlays atLoops?
self:play_soundthis object, following itno
self:soundthis object, following ityes
world:play_sounda fixed point in the regionno

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 Contentsself:play_sound("world/sounds/hammer")    -- the world's shared libraryself:play_sound("ui_click")               -- a built-in cue, no upload needed

Tip

The 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,})
OptionWhat it is for
volume0 to 4. 1 is the recorded level
pitch0.25 to 4. A {low, high} pair picks one value per play
rangemetres past which nobody hears it
nearmetres inside which it stops getting louder
falloff"inverse" (real-world) or "linear" (a definite edge)
air0 to 1, how much top end distance eats
distantanother sound that takes over further out
fadeseconds 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" }) -- laterself: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.

Tip

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,    })end

pos 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
--!strictlocal 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.

See also#

Updated 17 October 2026