forked from tas/major_tom
Add per-level wind atmosphere property
WIND directive in .lvl files sets a constant horizontal force (px/s^2) that pushes entities, projectiles, and particles. Positive is rightward. Wind is applied as acceleration in physics_update() (halved on ground), directly to projectile and particle velocities, and as a gentle position drift on flyers. Entities with gravity_scale=0 (drones, spacecraft) are unaffected. Levels default to no wind when the directive is absent.
This commit is contained in:
10
DESIGN.md
10
DESIGN.md
@@ -48,7 +48,7 @@ Each level defines its own atmosphere, affecting gameplay feel:
|
||||
| `MUSIC` | Level music track | assets/music/level1.ogg |
|
||||
| `PALETTE` | Color mood (warm, cold, toxic, void) | tint/filter values |
|
||||
|
||||
Already implemented: `GRAVITY`, `BG_COLOR`, `MUSIC`, `PARALLAX_FAR`, `PARALLAX_NEAR` (all per-level). Parallax backgrounds are procedurally generated (starfield + nebula) when no image path is specified.
|
||||
Already implemented: `GRAVITY`, `WIND`, `BG_COLOR`, `MUSIC`, `PARALLAX_FAR`, `PARALLAX_NEAR` (all per-level). Parallax backgrounds are procedurally generated (starfield + nebula) when no image path is specified.
|
||||
|
||||
---
|
||||
|
||||
@@ -118,11 +118,10 @@ adding a new def. See `src/game/projectile.h` for the full definition.
|
||||
## Levels
|
||||
|
||||
### Format (.lvl)
|
||||
Current directives: `TILESET`, `SIZE`, `SPAWN`, `GRAVITY`, `BG_COLOR`, `MUSIC`, `PARALLAX_FAR`, `PARALLAX_NEAR`, `TILEDEF`, `ENTITY`, `LAYER`
|
||||
Current directives: `TILESET`, `SIZE`, `SPAWN`, `GRAVITY`, `WIND`, `BG_COLOR`, `MUSIC`, `PARALLAX_FAR`, `PARALLAX_NEAR`, `TILEDEF`, `ENTITY`, `EXIT`, `LAYER`
|
||||
|
||||
**Needed additions:**
|
||||
- `EXIT <tile_x> <tile_y> <next_level>` — Level exit zone
|
||||
- `WIND`, `STORM`, `DRAG` — Atmosphere settings
|
||||
- `STORM`, `DRAG` — Remaining atmosphere settings
|
||||
|
||||
### Level Ideas
|
||||
|
||||
@@ -166,7 +165,8 @@ Current directives: `TILESET`, `SIZE`, `SPAWN`, `GRAVITY`, `BG_COLOR`, `MUSIC`,
|
||||
|
||||
### Medium Priority
|
||||
- [x] In-game level editor (tile/entity placement, save/load, test play)
|
||||
- [ ] Wind / drag atmosphere properties
|
||||
- [x] Wind atmosphere property (`WIND` directive, affects all entities/particles/projectiles)
|
||||
- [ ] Drag atmosphere property
|
||||
- [x] Parallax scrolling backgrounds (procedural stars + nebula, or from image files)
|
||||
- [x] Per-level background color (`BG_COLOR` directive)
|
||||
- [x] Music playback per level (`MUSIC` directive)
|
||||
|
||||
@@ -82,6 +82,7 @@ void particle_emit(const ParticleBurst *b) {
|
||||
|
||||
void particle_update(float dt) {
|
||||
float gravity = physics_get_gravity();
|
||||
float wind = physics_get_wind();
|
||||
|
||||
for (int i = 0; i < MAX_PARTICLES; i++) {
|
||||
Particle *p = &s_particles[i];
|
||||
@@ -96,6 +97,11 @@ void particle_update(float dt) {
|
||||
/* Apply gravity */
|
||||
p->vel.y += gravity * p->gravity_scale * dt;
|
||||
|
||||
/* Apply wind (reuse gravity_scale as environmental-force scale) */
|
||||
if (wind != 0.0f && p->gravity_scale > 0.0f) {
|
||||
p->vel.x += wind * p->gravity_scale * dt;
|
||||
}
|
||||
|
||||
/* Apply drag */
|
||||
if (p->drag > 0) {
|
||||
float factor = 1.0f - p->drag * dt;
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
#include <math.h>
|
||||
|
||||
static float s_gravity = DEFAULT_GRAVITY;
|
||||
static float s_wind = 0.0f;
|
||||
|
||||
void physics_init(void) {
|
||||
s_gravity = DEFAULT_GRAVITY;
|
||||
s_wind = 0.0f;
|
||||
}
|
||||
|
||||
void physics_set_gravity(float gravity) {
|
||||
@@ -17,6 +19,14 @@ float physics_get_gravity(void) {
|
||||
return s_gravity;
|
||||
}
|
||||
|
||||
void physics_set_wind(float wind) {
|
||||
s_wind = wind;
|
||||
}
|
||||
|
||||
float physics_get_wind(void) {
|
||||
return s_wind;
|
||||
}
|
||||
|
||||
static void resolve_tilemap_x(Body *body, const Tilemap *map) {
|
||||
if (!map) return;
|
||||
|
||||
@@ -102,6 +112,12 @@ void physics_update(Body *body, float dt, const Tilemap *map) {
|
||||
/* Apply gravity */
|
||||
body->vel.y += s_gravity * body->gravity_scale * dt;
|
||||
|
||||
/* Apply wind — halved on ground (friction counteracts) */
|
||||
if (s_wind != 0.0f && body->gravity_scale > 0.0f) {
|
||||
float wind_factor = body->on_ground ? 0.5f : 1.0f;
|
||||
body->vel.x += s_wind * body->gravity_scale * wind_factor * dt;
|
||||
}
|
||||
|
||||
/* Clamp fall speed */
|
||||
if (body->vel.y > MAX_FALL_SPEED) {
|
||||
body->vel.y = MAX_FALL_SPEED;
|
||||
|
||||
@@ -25,6 +25,10 @@ void physics_init(void);
|
||||
void physics_set_gravity(float gravity);
|
||||
float physics_get_gravity(void);
|
||||
|
||||
/* Set / get the current horizontal wind force (pixels/s^2, +=right) */
|
||||
void physics_set_wind(float wind);
|
||||
float physics_get_wind(void);
|
||||
|
||||
/* Move body, apply gravity, resolve against tilemap */
|
||||
void physics_update(Body *body, float dt, const Tilemap *map);
|
||||
|
||||
|
||||
@@ -85,6 +85,8 @@ bool tilemap_load(Tilemap *map, const char *path, SDL_Renderer *renderer) {
|
||||
map->player_spawn = vec2(sx * TILE_SIZE, sy * TILE_SIZE);
|
||||
} else if (strncmp(line, "GRAVITY ", 8) == 0) {
|
||||
sscanf(line + 8, "%f", &map->gravity);
|
||||
} else if (strncmp(line, "WIND ", 5) == 0) {
|
||||
sscanf(line + 5, "%f", &map->wind);
|
||||
} else if (strncmp(line, "TILEDEF ", 8) == 0) {
|
||||
int id, tx, ty;
|
||||
uint32_t flags;
|
||||
|
||||
@@ -50,6 +50,7 @@ typedef struct Tilemap {
|
||||
char tileset_path[ASSET_PATH_MAX]; /* tileset file path */
|
||||
Vec2 player_spawn;
|
||||
float gravity; /* level gravity (px/s^2), 0 = use default */
|
||||
float wind; /* horizontal wind force (px/s^2), +=right */
|
||||
char music_path[ASSET_PATH_MAX]; /* level music file path */
|
||||
SDL_Color bg_color; /* background clear color */
|
||||
bool has_bg_color; /* true if BG_COLOR was set */
|
||||
|
||||
@@ -450,6 +450,8 @@ static bool save_tilemap(const Tilemap *map, const char *path) {
|
||||
|
||||
if (map->gravity > 0)
|
||||
fprintf(f, "GRAVITY %.0f\n", map->gravity);
|
||||
if (map->wind != 0.0f)
|
||||
fprintf(f, "WIND %.0f\n", map->wind);
|
||||
if (map->has_bg_color)
|
||||
fprintf(f, "BG_COLOR %d %d %d\n", map->bg_color.r, map->bg_color.g, map->bg_color.b);
|
||||
if (map->music_path[0])
|
||||
|
||||
@@ -170,6 +170,13 @@ static void flyer_update(Entity *self, float dt, const Tilemap *map) {
|
||||
float bob_offset = sinf(fd->bob_timer * FLYER_BOB_SPD) * FLYER_BOB_AMP;
|
||||
body->pos.y = fd->base_y + bob_offset;
|
||||
|
||||
/* Wind drifts flyers (they bypass physics_update).
|
||||
* Apply as gentle position offset matching first-frame physics. */
|
||||
float wind = physics_get_wind();
|
||||
if (wind != 0.0f) {
|
||||
body->pos.x += wind * dt * dt;
|
||||
}
|
||||
|
||||
/* Chase player if in range */
|
||||
Entity *player = find_player(s_flyer_em);
|
||||
if (player && player->active && !(player->flags & ENTITY_DEAD)) {
|
||||
|
||||
@@ -57,6 +57,9 @@ static bool level_setup(Level *level) {
|
||||
physics_set_gravity(DEFAULT_GRAVITY);
|
||||
}
|
||||
|
||||
/* Apply level wind (0 = no wind) */
|
||||
physics_set_wind(level->map.wind);
|
||||
|
||||
/* Apply level background color */
|
||||
if (level->map.has_bg_color) {
|
||||
renderer_set_clear_color(level->map.bg_color);
|
||||
|
||||
@@ -1631,6 +1631,10 @@ bool levelgen_dump_lvl(const Tilemap *map, const char *path) {
|
||||
fprintf(f, "GRAVITY %.0f\n", map->gravity);
|
||||
}
|
||||
|
||||
if (map->wind != 0.0f) {
|
||||
fprintf(f, "WIND %.0f\n", map->wind);
|
||||
}
|
||||
|
||||
if (map->has_bg_color) {
|
||||
fprintf(f, "BG_COLOR %d %d %d\n",
|
||||
map->bg_color.r, map->bg_color.g, map->bg_color.b);
|
||||
|
||||
@@ -204,6 +204,12 @@ static void projectile_update(Entity *self, float dt, const Tilemap *map) {
|
||||
if (body->vel.y > MAX_FALL_SPEED) body->vel.y = MAX_FALL_SPEED;
|
||||
}
|
||||
|
||||
/* ── Apply wind ─────────────────────────── */
|
||||
float wind = physics_get_wind();
|
||||
if (wind != 0.0f) {
|
||||
body->vel.x += wind * dt;
|
||||
}
|
||||
|
||||
/* ── Move ────────────────────────────────── */
|
||||
body->pos.x += body->vel.x * dt;
|
||||
body->pos.y += body->vel.y * dt;
|
||||
|
||||
Reference in New Issue
Block a user