Nine agent skills for building Makepad/Rust games, ported from majidmanzarpour/threejs-game-skills. Same director-routed workflow and premium bar; runtime rewritten for this fork's game crates. - makepad-game-director entrypoint, routing, continuity, asset probe - makepad-gameplay-systems loop, movers vs rigid bodies, input, camera, netplay - makepad-aaa-graphics-builder lighting, shaders, budget, visual scorecard - makepad-game-ui-designer HUD, menus, touch and XR UI - makepad-debug-profiler defect bisection and profiling - makepad-qa-release verification ladder, evidence, packaging - makepad-3d-generator CC0 model search, casts, procedural geometry - makepad-image-generator texgen textures, palettes, sky, icons - makepad-audio-generator sample bank, mixer, material impacts, 3D audio Written against the real APIs in libs/game/*, libs/sim and apps/arcade: the game.* verb table, GameRenderer adaptive quality, the packed 6-float GameMeshVertex layout, script_mod! splash styling, makepad-test driving, and the BUDGETS.md numbers. No paid generation API is required - the CC0 library plus seeded makepad-game-gen replaces them. Includes install.sh (Codex/Claude), validate-skills.sh and a repo-aware probe_assets.sh; both scripts verified against this checkout.
102 lines
4.4 KiB
Markdown
102 lines
4.4 KiB
Markdown
# Audio workflows
|
||
|
||
## Loading
|
||
|
||
```rust
|
||
let pcm = makepad_game_audio::decode(&bytes)?; // WAV or Ogg Vorbis -> Pcm
|
||
let id: SampleId = bank.add(pcm);
|
||
```
|
||
|
||
`SampleBank` owns loaded samples; `SampleId` is the handle you keep. Load at startup or during level load — decoding during play is a stutter source, and Vorbis decode is the most expensive audio operation in the stack.
|
||
|
||
`AudioError` distinguishes decode failures. Handle it: a missing or corrupt sound should log and continue, never panic mid-frame.
|
||
|
||
## Playback
|
||
|
||
```rust
|
||
let handle: VoiceHandle = mixer.play(VoiceSpec {
|
||
sample: id,
|
||
gain,
|
||
pitch,
|
||
priority: Priority::/* .. */,
|
||
// placement for positional voices
|
||
});
|
||
```
|
||
|
||
Keep `VoiceHandle`s for anything you must stop or modulate later — looping engine notes, ambience beds. Fire-and-forget for one-shots.
|
||
|
||
## Event-driven emission
|
||
|
||
```rust
|
||
for impact in dynamics.take_mover_impacts() {
|
||
let pair = MaterialPair::new(impact.a_material, impact.b_material);
|
||
director.emit(SoundEvent {
|
||
category: Category::Impact,
|
||
placement: Placement::At(impact.pos),
|
||
// gain shaped by ImpactCurve from impact energy
|
||
});
|
||
}
|
||
```
|
||
|
||
`take_mover_impacts()` is the single source of truth for collisions. Re-detecting collisions in gameplay code to trigger audio is how you get double sounds that drift out of sync with the visual effect.
|
||
|
||
## Material table
|
||
|
||
`Material`, `MaterialPair`, `ImpactCurve`.
|
||
|
||
Map **every** pair a scene can produce; an unmapped pair either falls back to a generic thud (annotated-sounding) or is silent (feels broken). Enumerate the materials in the scene and fill the matrix — it is small, and it is the difference between a world that sounds physical and one that sounds labelled.
|
||
|
||
`ImpactCurve` maps impact energy to gain and brightness: a light tap and a heavy crash should not be the same sample at the same level. Energy comes from the impact, not from a fixed constant per event type.
|
||
|
||
## Variation
|
||
|
||
```rust
|
||
let pitch = 1.0 + rng.range(-0.08, 0.08);
|
||
let gain = base * (1.0 + rng.range(-0.10, 0.10));
|
||
let sample = variants[rng.next_index(variants.len())];
|
||
```
|
||
|
||
`LocalRng`, seeded — never thread RNG, or networked clients hear different things and any replay diverges.
|
||
|
||
For sample choice, shuffle a bag rather than picking uniformly at random: pure random repeats the same variant back-to-back often enough to be audible, which defeats the purpose.
|
||
|
||
## Voice budget
|
||
|
||
| Category | Suggested cap |
|
||
| --- | --- |
|
||
| Player feedback | always plays, never evicted |
|
||
| UI | 4 |
|
||
| Nearby impacts | 8 |
|
||
| Footsteps | 6, coalesced |
|
||
| Ambience | 2–4 loops |
|
||
| Music | 1 |
|
||
|
||
Enforce with `Priority`. Cull by distance **before** mixing. Coalesce identical events within a frame into one louder voice. Debounce repeats of the same sample within ~30 ms — overlapping identical copies phase-cancel into a metallic artefact that sounds like a bug, because it is one.
|
||
|
||
## 3D
|
||
|
||
```rust
|
||
let listener = Listener { pos: camera.pos, forward: camera.forward, up: camera.up };
|
||
```
|
||
|
||
On the **camera**, not the player. Attenuate roughly inverse-square with a near clamp, cull beyond a max range, pan by the listener's right vector. Keep UI and player-feedback sounds non-positional so they never get quiet or pan away at the moment they matter.
|
||
|
||
## Ambience
|
||
|
||
One or two long loops, quiet, continuous, crossfaded on area change. Layer sparse one-shots (a bird, a distant creak) at randomised intervals — this is what stops a loop being recognisable as a loop, and it is much cheaper than a longer bed.
|
||
|
||
## Music and ducking
|
||
|
||
Music sits under everything. Duck it (and ambience) by a few dB with a fast attack and slow release when player feedback fires; this is what keeps important sounds audible without raising their level to the point of harshness.
|
||
|
||
The Kenney jingle pack covers win/lose/level-complete stings. Short stings beat looping tracks for most game-jam-scope games — cheaper, and they never outstay their welcome.
|
||
|
||
## Testing
|
||
|
||
```bash
|
||
cargo test -p makepad-game-audio
|
||
```
|
||
|
||
Cx-free, so it all tests headlessly. Assert: decode round-trips (rate, channels, length); the mixer honours its voice cap under a flood of events; priority evicts the lowest-priority voice, never the player feedback; every `MaterialPair` a scene can produce resolves; no emission path grows a `Vec` without bound.
|
||
|
||
Then listen for several minutes of real play. Numbers cannot tell you that a footstep is repeating audibly, that the mix collapses in a busy wave, or that a critical event has no sound at all.
|