Log inGet started
World

Blockbuster 767

Branchmain
Branchesmain
Commit8e9b87fcce

README.md

SoundDirector

The sound of BLOCKBUSTER 767 — the whole of it, in one component on mount_sound.

It plays a music bed and a playroom ambience that loop end to end, rides the wind-up motor and the air over the airframe off Game.telemetry, fires the one-shot library from the event bus, sets the mixer levels, and gives the Kid a voice with the words on screen.

What it needs

  • game/audio/clips/*.soundClip — baked by game/audio/bake.luau from game.audio.recipes. Pure synthesis; costs no credits, cannot fail.
  • game/audio/voice/*.soundClip — bought ONCE from audio_gen.speech by game/audio/voicebake.luau, one clip per line in game.audio.lines.
  • An AudioListener somewhere. The scene puts it on the CameraRig, which is what makes a spatial hit at a block come from where the camera is watching.

A clip that is missing is not an error: the sound is skipped and, for a voice line, the caption is still shown.

Playing a sound from another slice

local sound = entity.find("mount_sound").component.get("SoundDirector")
sound:play("hit_heavy", { position = pos, volume = 0.8, pitch = 1.1 })
sound:say("topple")            -- the Kid, from the pool for that cue

play(name, opts) takes any name in game.audio.recipes: launch, whoosh, windup, hit_light, hit_heavy, collapse, clatter, snap, wreck_sting, score_pop, ui_tick, ui_confirm, ui_back, star_chime. With a position the sound is spatial; without one it is flat. Each family carries a cooldown, so forty blocks landing at once is a collapse rather than forty overlapping ticks.

Nothing HAS to call either of these: the bus already drives them. launch, impact, partLost, blockDisplaced, blockOff, towerToppled, score, wreck, sessionEnd, pause and state each have a sound, so a slice that emits its events correctly is heard without doing anything else.

The captions

Every spoken line is also written down, through game.audio.caption:

local Caption = require("game.audio.caption")
local now = Caption.current()               -- { id, text, remaining, duration } or nil
local off = Caption.on(function(c) ... end) -- fires on every new line

SoundDirector draws the words itself, in a band above the bottom control band, proportional to ui.screenSize(). The presentation slice can take that job — carry the line inside a composed card instead:

Caption.claim("bb_screens")   -- SoundDirector's band stands down
Caption.release()             -- hand it back

The band is registered at UI layer 10, below every authored screen, so it never takes a tap that belongs to a card or a HUD button.

The mix

Channels: music, ambient, sfx, engine, voice, ui. The public fields set each one; change one and call :refreshMix(). Every clip was baked with headroom, so nothing in the library reaches the mix above about 0.77 peak on its own.

:report() is the read: the master levels, how many sources are audible, the engine gain and pitch, the caption, and the mixer state.