Skip to content

Sound

Sounds are project assets. Drag an .mp3, .wav, .ogg or .webm into the Project panel and it is imported — the file's bytes travel inside the map, so a published game carries its audio with it and there is nothing extra to host.

Playing a sound

From a script, by the name shown in the Project panel:

ts
class Chest extends Behaviour {
  onInteract() {
    audio.play('ChestOpen')
  }
}

Pass a position to make it 3D — it attenuates with distance from the listener:

ts
audio.play('Explosion', this.transform.position)

Naming a sound that does not exist logs a warning saying so, and lists what is imported. It will not fail silently.

For sound that belongs to an object rather than a moment — an engine hum, a torch crackle — add an audioSource component instead. It follows the object, loops if you ask it to, and can start on its own with Play On Awake.

A script can start and stop one:

ts
audio.playOn('Generator')   // the entity's audioSource starts
audio.stopOn('Generator')   // and stops

Sound in multiplayer

Your scripts run on the server, which has no speakers. So a sound is never played there — it is sent, and each player's own device plays it. That is what makes a positional sound arrive correctly for everyone: the same explosion is loud on the left for a player standing east of it and faint for one across the map, without you doing anything.

Two kinds of sound, and the difference matters:

Behaviour
audio.play(...)A moment. Everyone in the room who is there to hear it does. Someone who joins a second later has missed it, the same way they missed the explosion.
An entity's audioSourceA state. It keeps playing, and a player who joins ten minutes later arrives to hear it already going — a generator that has been humming since the round started is humming when they walk in.

So reach for playOn when the sound describes how the world IS, and play when it describes something that HAPPENED.

To play something for one player and nobody else — a pickup chime, a warning only they should hear:

ts
audio.play('Coin', undefined, { player: playerId })

You can also set the volume, the pitch and the group of a one-shot:

ts
audio.play('Step', this.transform.position, { volume: 0.4, pitch: 0.9 + Math.random() * 0.2 })

Varying the pitch slightly is worth doing on anything that repeats often — twenty identical footsteps sound like one sample played twenty times, and a little spread makes them sound like footsteps.

Mixer groups

Every sound routes through a group: music, sfx (the default) or ui. Set a group's volume to build a settings menu, or duck the music under dialogue:

ts
audio.setGroupVolume('music', 0.3)

master scales everything. In multiplayer this applies to everyone in the room; pass a player id as a third argument to change the mix for just one of them.

The sound Inspector

Right-click a sound in the Project panel and choose Inspect (or double-click it) to open it in the Inspector.

Press ▶ to hear it without entering Play. The panel also reports what the file actually is — format, duration, channels, sample rate and size — rather than what it was named.

Compression

Sounds are usually the largest thing in a map, and an uncompressed one is larger than it needs to be. The Compression block re-encodes the sound in place, showing the size you would get BEFORE you commit to it:

SettingWhat it does
Format — WAVUncompressed PCM. Instant, and lossless at whatever rate it keeps.
Format — OpusReal compression, often 10-20× smaller. Encodes in real time, so a 3-minute track takes 3 minutes.
Sample rateLower is smaller. 22050 Hz is plenty for most effects; 44100 is CD quality.
Bit depth (WAV)8-bit halves the size of 16-bit and sounds noticeably grainier.
Bitrate (Opus)64 kbps is a good default.
Force monoExactly half the size of a stereo sound, and most effects are not stereo anyway.

Two things worth knowing:

An already-compressed file (an mp3, say) gets BIGGER if you turn it into WAV. The panel says so in red rather than letting you find out afterwards. Opus is the option that can still shrink it.

Compressing replaces the stored audio. The editor keeps what the sound was just before its first compression, so Restore Original puts it back — but that copy is deliberately not saved into the map (carrying both would defeat the point), so a sound compressed in an earlier session comes back with Restore greyed out.