Add spatial audio, spacecraft entity, and level intro sequence

Distance-based sound effects with stereo panning for explosions, impacts,
and pickups. Spacecraft entity with full state machine (fly-in, land, take
off, fly-out), engine/synth sound loops, thruster particles, and PNG
spritesheet. Moon level intro defers player spawn until ship lands.

Also untrack build/ objects that were committed by mistake.
This commit is contained in:
Thomas
2026-03-01 11:00:51 +00:00
parent dbb507bfd2
commit 49ed2d6f7b
33 changed files with 1026 additions and 61 deletions

View File

@@ -2,9 +2,17 @@
#include "config.h"
#include <SDL2/SDL_mixer.h>
#include <stdio.h>
#include <math.h>
static bool s_initialized = false;
/* ── Listener state for spatial audio ────────────── */
static Vec2 s_listener_pos = {0};
static bool s_listener_set = false;
/* Default max audible distance in pixels (~40 tiles) */
#define AUDIO_DEFAULT_MAX_DIST 640.0f
bool audio_init(void) {
/* Initialize MP3 (and OGG) decoder support */
int mix_flags = MIX_INIT_MP3 | MIX_INIT_OGG;
@@ -66,9 +74,25 @@ void audio_play_sound(Sound s, int volume) {
int ch = Mix_PlayChannel(-1, (Mix_Chunk *)s.chunk, 0);
if (ch >= 0) {
Mix_Volume(ch, volume);
Mix_SetPanning(ch, 255, 255); /* reset to center (no panning) */
}
}
int audio_play_sound_loop(Sound s, int volume) {
if (!s_initialized || !s.chunk) return -1;
int ch = Mix_PlayChannel(-1, (Mix_Chunk *)s.chunk, -1); /* -1 = loop forever */
if (ch >= 0) {
Mix_Volume(ch, volume);
Mix_SetPanning(ch, 255, 255); /* reset to center */
}
return ch;
}
void audio_stop_channel(int ch) {
if (!s_initialized || ch < 0) return;
Mix_HaltChannel(ch);
}
void audio_play_music(Music m, bool loop) {
if (!s_initialized) {
fprintf(stderr, "audio_play_music: audio not initialized\n");
@@ -99,11 +123,60 @@ void audio_set_music_volume(int volume) {
Mix_VolumeMusic(volume);
}
/* ── Positional / spatial audio ──────────────────── */
void audio_set_listener(Vec2 pos) {
s_listener_pos = pos;
s_listener_set = true;
}
void audio_play_sound_at(Sound s, int base_volume, Vec2 world_pos, float max_dist) {
if (!s_initialized || !s.chunk) return;
if (max_dist <= 0.0f) max_dist = AUDIO_DEFAULT_MAX_DIST;
/* Fall back to non-positional if listener was never set */
if (!s_listener_set) {
audio_play_sound(s, base_volume);
return;
}
float dx = world_pos.x - s_listener_pos.x;
float dy = world_pos.y - s_listener_pos.y;
float dist = sqrtf(dx * dx + dy * dy);
/* Inaudible beyond max distance */
if (dist >= max_dist) return;
/* Linear attenuation: full volume at dist=0, silent at max_dist */
float atten = 1.0f - (dist / max_dist);
int vol = (int)(base_volume * atten);
if (vol <= 0) return;
if (vol > 128) vol = 128;
int ch = Mix_PlayChannel(-1, (Mix_Chunk *)s.chunk, 0);
if (ch < 0) return;
Mix_Volume(ch, vol);
/* Stereo panning: center = 127, hard-left = 0, hard-right = 254.
* Pan linearly based on horizontal offset relative to half the
* viewport width (~half of SCREEN_WIDTH). */
float half_screen = (float)SCREEN_WIDTH * 0.5f;
float pan_ratio = dx / half_screen; /* -1..+1 roughly */
if (pan_ratio < -1.0f) pan_ratio = -1.0f;
if (pan_ratio > 1.0f) pan_ratio = 1.0f;
Uint8 pan_right = (Uint8)(127 + (int)(pan_ratio * 127.0f));
Uint8 pan_left = 254 - pan_right;
Mix_SetPanning(ch, pan_left, pan_right);
}
void audio_shutdown(void) {
if (!s_initialized) return;
Mix_HaltMusic();
Mix_CloseAudio();
Mix_Quit();
s_initialized = false;
s_listener_set = false;
printf("Audio shut down.\n");
}

View File

@@ -2,6 +2,7 @@
#define JNR_AUDIO_H
#include <stdbool.h>
#include "util/vec2.h"
typedef struct Sound {
void *chunk; /* Mix_Chunk* */
@@ -21,4 +22,23 @@ void audio_free_music(Music *m);
void audio_set_music_volume(int volume); /* 0-128 */
void audio_shutdown(void);
/* ── Looping sounds ────────────────────────────── */
/* Play a sound on loop, returns the channel number (or -1 on failure).
* Use audio_stop_channel() to stop it later. */
int audio_play_sound_loop(Sound s, int volume);
/* Stop a specific channel (from audio_play_sound_loop). No-op if ch < 0. */
void audio_stop_channel(int ch);
/* ── Positional / spatial audio ────────────────── */
/* Set the listener position (call once per frame, typically from camera center) */
void audio_set_listener(Vec2 pos);
/* Play a sound at a world position. Volume is attenuated by distance from the
* listener and stereo-panned based on horizontal offset. Sounds beyond
* max_dist are inaudible. Pass max_dist <= 0 for a sensible default. */
void audio_play_sound_at(Sound s, int base_volume, Vec2 world_pos, float max_dist);
#endif /* JNR_AUDIO_H */

View File

@@ -27,6 +27,7 @@ typedef enum EntityType {
ENT_POWERUP,
ENT_DRONE,
ENT_ASTEROID,
ENT_SPACECRAFT,
ENT_TYPE_COUNT
} EntityType;