Skip to content

@modoki/engine / index / AudioSource

Variable: AudioSource

const AudioSource: Trait<{ autoplay: boolean; bus: "ui" | "sfx" | "master" | "music"; clip: string; clips: string; crossfadeSec: number; loop: boolean; maxDistance: number; pitch: number; playing: boolean; playOnCue: string; refDistance: number; rolloff: number; spatial: boolean; volume: number; }>

AudioSource — an entity that emits sound.

clip is a GUID resolved via the asset manifest (like every other asset ref). Non-spatial by default (THREE-free 2D UI/SFX/music); flip spatial for 3D positional audio, which attenuates by distance from the AudioListener entity — position comes from the entity's world Transform, so no extra fields.

Playback is driven by audioSystem (presentation tier): autoplay starts on play; playOnCue fires a one-shot when a matching cue is raised (cueSound) — the cue bus is a dedicated channel, NOT the verification journal. Buffer-vs- stream is an ASSET-level .meta.json setting (getAudioLoadType), not a field here — the same file shouldn't decode one way for one source and another for a second.

clips is a NAMED CLIP BANK: a JSON-string [{ "key", "ref" }, …] (Unity's "an AudioSource + an array of AudioClips indexed by name"). It lets a source own several playable sounds and lets UI/game actions reference them by a STABLE STRING KEY instead of a raw GUID — so audio.setClip { key } / audio.playOneShot { key } are decoupled from asset identity (re-import a file → its GUID changes in ONE place, the bank, not in every button). Each ref is a real audio GUID: the resource collector parses this string and collects the refs, so every banked clip ships + survives an editor save — which the old "GUID buried in UIAction params" approach did NOT. clip stays the currently-loaded/playing GUID the reconcile reads; the bank is just the key→ref table. A source that only plays one thing skips the bank.

Why a JSON STRING (not an inline {key,ref}[] array): a non-scalar trait field is a known bug source — opaque to serialize / prefab-override diffing / undo, and must be deep-cloned at every boundary (see tests/runtime/traitScalarFields.test.ts). A JSON-string SCALAR (exactly like Collider2D.points) sidesteps that class — it's copied verbatim everywhere — so this trait stays a plain SoA trait({…}). Parse via parseClipBank (guarded → [] on invalid); never JSON.parse inline.

Built with Modoki.