> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/core-engine-concepts/sound-effects.md).

# Sound Effects

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

{% hint style="warning" %}
Currently only **PCM-encoded `.wav`** files are supported. If you have a `.wav` that won’t import/play, convert it to PCM using Audacity (or a similar tool).
{% endhint %}

### Quick start (play a sound)

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

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

### SFX API reference

```go
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:

```go
pickup := get_asset(SFX_Asset, "sfx/pickup.wav");
```

{% hint style="warning" %}
Make sure the path is valid. If you call `get_asset` with a bad path and then play it, you can crash.
{% endhint %}

{% hint style="warning" %}
`SFX.play` throws if the sound asset is null.
{% endhint %}

{% hint style="info" %}
Some built-in platform assets are referenced via `$AO/...` paths. For example: `get_asset(SFX_Asset, "$AO/click.wav")`.
{% endhint %}

### Positional (3D-ish) sounds

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

```go
play_hit_sfx :: proc(world_pos: v2) {
    hit := get_asset(SFX_Asset, "sfx/hit.wav");

    desc := SFX.default_sfx_desc();
    desc.set_position(world_pos);
    desc.range_multiplier = 1.5;

    SFX.play(hit, desc);
}
```

### Following an entity (moving sound source)

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

```go
start_engine_loop :: proc(entity: Entity) -> u64 {
    engine := get_asset(SFX_Asset, "sfx/engine_loop.wav");

    desc := SFX.default_sfx_desc();
    desc.entity_to_follow = entity.id;
    desc.loop = true;
    desc.channel = .MUSIC; // optional: treat as music/ambient instead of SFX

    // Returns an ID that can be used to update or stop the sound.
    return SFX.play(engine, desc);
}
```

{% hint style="info" %}
If `entity_to_follow` is set, you usually don’t need to also call `set_position` (it’s fine if you do).
{% endhint %}

### Player-targeted sounds

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

```go
play_private_ping :: proc(player: Player) {
    click := get_asset(SFX_Asset, "sfx/click.wav");

    desc := SFX.default_sfx_desc();
    desc.specific_to_player = player;
    desc.volume = 0.7;

    SFX.play(click, desc);
}
```

### Looping + stopping

`SFX.play` returns an ID you can stop later:

```go
sound_id := 0.(u64);

start_loop :: proc() {
    loop := get_asset(SFX_Asset, "sfx/ambience.wav");

    desc := SFX.default_sfx_desc();
    desc.loop = true;
    desc.volume = 0.6;
    desc.loop_timeout = 60; // safety: auto-stop after ~60s if you forget

    sound_id = SFX.play(loop, desc);
}

stop_loop :: proc() {
    if sound_id != 0 {
        SFX.stop(sound_id);
        sound_id = 0;
    }
}
```

### Variation (recommended for spammy SFX)

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

```go
desc := SFX.default_sfx_desc();
desc.volume_perturb = 0.2;
desc.speed_perturb  = 0.15;
SFX.play(get_asset(SFX_Asset, "sfx/footstep.wav"), desc);
```

{% hint style="info" %}
Keep `volume_perturb` / `speed_perturb` subtle. Values above \~`0.3` usually sound wrong.
{% endhint %}

### Server vs client (networking + prediction)

SFX can be played from either side, depending on intent:

* **Authoritative gameplay events** (everyone should hear it): play on the **server**
* **Local/UI feedback** (only the local player): play on the **client**

Example: authoritative positional SFX:

```go
play_round_victory :: proc(pos: v2) {
    if Game.is_server() {
        desc := SFX.default_sfx_desc();
        desc.set_position(pos);
        SFX.play(get_asset(SFX_Asset, "sfx/round_victory.wav"), desc);
    }
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.allout.game/core-engine-concepts/sound-effects.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
