For the complete documentation index, see llms.txt. This page is also available as Markdown.

Sound and Haptics

Play sound effects and local impact feedback.

Use the SFX API to play short sound effects (UI clicks, footsteps, impacts) and simple looping audio (ambient loops, music).

Quick start (play a sound)

click := get_asset(SFX_Asset, "sfx/click.wav");

desc := SFX.default_sfx_desc();
SFX.play(click, desc);

SFX API reference

SFX_Asset :: class : Asset {}

SFX_Channel :: enum {
    SFX;
    MUSIC;
}

SFX_Desc :: struct {
    specific_to_player: Player;
    positional:       bool;
    position:         v2 #read_only;
    delay:            float;
    volume:           float;
    speed:            float;
    volume_perturb:   float;
    speed_perturb:    float;
    range_multiplier: float;
    loop_timeout:     float;
    entity_to_follow: u64;
    loop:             bool;
    channel:          SFX_Channel;

    set_position :: method(p: v2);
}

SFX :: struct {
    play              :: proc(asset: SFX_Asset, desc: SFX_Desc) -> u64;
    stop              :: proc(id: u64);
    fade_out_and_stop :: proc(id: u64, fade_time: float);
    default_sfx_desc :: proc() -> SFX_Desc;
}

Getting an SFX_Asset

Sound effects live in your game’s res folder (often under res/sfx/).

  • Drag sounds in from the Asset Catalog (editor downloads them into res/)

  • Or add your own .wav file into res/ from your local machine

Then reference them by path:

Some built-in platform assets use $AO/... paths. For example: get_asset(SFX_Asset, "$AO/sfx/FUI Hologram Ping Tone Echoed.wav").

Positional (3D-ish) sounds

By default, SFX plays as a non-positional “2D” sound. To make it positional, set a position:

Following an entity (moving sound source)

If a sound should move with an entity (engine hum, buzzing projectile, etc), set entity_to_follow:

If entity_to_follow is set, you usually don’t need to also call set_position (it’s fine if you do).

Player-targeted sounds

Set specific_to_player when the server should track the sound but only one client should actually hear it:

Looping + stopping

SFX.play returns an ID you can stop later:

If you play the same sound frequently (footsteps, pickups), add subtle variation:

Keep volume_perturb / speed_perturb subtle. Values above ~0.3 usually sound wrong.

Prediction and targeting

Call gameplay sounds from the same shared predicted path as the event that caused them. The sound system reconciles a client's predicted sound with the server result, so do not wrap SFX.play in Game.is_server() or player.is_local().

For a sound that only one player should hear, set desc.specific_to_player. This keeps the event in shared gameplay code while targeting playback to that player's client.

Haptics

Haptics.play_impact triggers impact feedback on the local device. Call it only for the local player:

Choose .LIGHT, .MEDIUM, or .HEAVY. Haptics currently work on iOS. Calls on Android, web, Windows, and servers do nothing.

Haptics are not synchronized, targeted, or deduplicated. Trigger them at the local interaction point rather than from scene-wide gameplay code.

Last updated