Add pixel art sprites for charger, spawner, and laser turret

Replace colored-rect placeholder rendering with proper 16x16
spritesheet art. Charger: orange armored rhino with horn, eyes,
walk cycle (row 7). Spawner: purple hive pod with glowing core
and tendrils (row 8). Laser turret: angular metallic housing
with red lens that brightens when firing (row 8). All three
retain colored-rect fallback when spritesheet is unavailable.
This commit is contained in:
Thomas
2026-03-02 20:45:21 +00:00
parent 7b1d39f522
commit 5f899f61c6
4 changed files with 461 additions and 65 deletions

View File

@@ -291,6 +291,8 @@ static void charger_update(Entity *self, float dt, const Tilemap *map) {
/* Death sequence */
if (self->flags & ENTITY_DEAD) {
animation_set(&self->anim, &anim_charger_death);
animation_update(&self->anim, dt);
cd->death_timer -= dt;
body->vel.x = 0;
if (cd->death_timer <= 0) {
@@ -394,43 +396,63 @@ static void charger_update(Entity *self, float dt, const Tilemap *map) {
}
}
/* Animation — use grunt anims as placeholder */
if (cd->state == CHARGER_CHARGE) {
animation_set(&self->anim, &anim_grunt_walk);
/* Animation */
if (cd->state == CHARGER_CHARGE || cd->state == CHARGER_PATROL) {
animation_set(&self->anim, &anim_charger_walk);
} else {
animation_set(&self->anim, &anim_grunt_idle);
animation_set(&self->anim, &anim_charger_idle);
}
animation_update(&self->anim, dt);
}
static void charger_render(Entity *self, const Camera *cam) {
(void)cam;
Body *body = &self->body;
ChargerData *cd = (ChargerData *)self->data;
/* Colored rect fallback — orange/amber color scheme */
SDL_Color color;
if (cd && cd->state == CHARGER_ALERT) {
/* Flash during telegraph */
float blink = sinf(cd->state_timer * 30.0f);
uint8_t r = (blink > 0) ? 255 : 200;
color = (SDL_Color){r, 160, 40, 255};
} else if (cd && cd->state == CHARGER_STUNNED) {
color = (SDL_Color){160, 160, 100, 255};
} else if (cd && cd->state == CHARGER_CHARGE) {
color = (SDL_Color){255, 120, 30, 255};
} else {
color = (SDL_Color){220, 140, 40, 255};
}
renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
if (g_spritesheet && self->anim.def) {
SDL_Rect src = animation_current_rect(&self->anim);
/* Direction indicator: small triangle in facing direction */
float cx = body->pos.x + body->size.x * 0.5f;
float cy = body->pos.y + 2.0f;
bool left = (self->flags & ENTITY_FACING_LEFT) != 0;
float arrow_x = left ? body->pos.x - 3.0f : body->pos.x + body->size.x + 1.0f;
renderer_draw_rect(vec2(arrow_x, cy), vec2(2, 3),
(SDL_Color){255, 200, 60, 255}, LAYER_ENTITIES, cam);
(void)cx;
/* Center the 16x16 sprite over the 14x16 hitbox */
Vec2 render_pos = vec2(
body->pos.x - 1.0f,
body->pos.y
);
/* Flash white during alert telegraph */
uint8_t alpha = 255;
if (cd && cd->state == CHARGER_ALERT) {
float blink = sinf(cd->state_timer * 30.0f);
alpha = (blink > 0) ? 180 : 255;
}
Sprite spr = {
.texture = g_spritesheet,
.src = src,
.pos = render_pos,
.size = vec2(SPRITE_CELL, SPRITE_CELL),
.flip_x = (self->flags & ENTITY_FACING_LEFT) != 0,
.flip_y = false,
.layer = LAYER_ENTITIES,
.alpha = alpha,
};
renderer_submit(&spr);
} else {
/* Colored rect fallback */
SDL_Color color;
if (cd && cd->state == CHARGER_ALERT) {
float blink = sinf(cd->state_timer * 30.0f);
uint8_t r = (blink > 0) ? 255 : 200;
color = (SDL_Color){r, 160, 40, 255};
} else if (cd && cd->state == CHARGER_STUNNED) {
color = (SDL_Color){160, 160, 100, 255};
} else if (cd && cd->state == CHARGER_CHARGE) {
color = (SDL_Color){255, 120, 30, 255};
} else {
color = (SDL_Color){220, 140, 40, 255};
}
renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
}
}
static void charger_destroy(Entity *self) {
@@ -494,6 +516,8 @@ static void spawner_update(Entity *self, float dt, const Tilemap *map) {
/* Death sequence */
if (self->flags & ENTITY_DEAD) {
animation_set(&self->anim, &anim_spawner_death);
animation_update(&self->anim, dt);
sd->death_timer -= dt;
if (sd->death_timer <= 0) {
particle_emit_death_puff(self->body.pos, (SDL_Color){180, 60, 180, 255});
@@ -511,6 +535,10 @@ static void spawner_update(Entity *self, float dt, const Tilemap *map) {
sd->pulse_timer = 0;
}
/* Animation */
animation_set(&self->anim, &anim_spawner_idle);
animation_update(&self->anim, dt);
/* Spawn a grunt when timer expires */
if (sd->spawn_timer <= 0) {
sd->spawn_timer = SPAWNER_INTERVAL;
@@ -530,26 +558,46 @@ static void spawner_update(Entity *self, float dt, const Tilemap *map) {
}
static void spawner_render(Entity *self, const Camera *cam) {
(void)cam;
Body *body = &self->body;
SpawnerData *sd = (SpawnerData *)self->data;
/* Pulsing purple color when about to spawn */
SDL_Color color;
if (sd && sd->pulse_timer > 0) {
float pulse = sinf(sd->pulse_timer * 12.0f) * 0.5f + 0.5f;
uint8_t r = (uint8_t)(140 + 80 * pulse);
uint8_t b = (uint8_t)(180 + 60 * pulse);
color = (SDL_Color){r, 50, b, 255};
} else {
color = (SDL_Color){140, 50, 160, 255};
}
renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
if (g_spritesheet && self->anim.def) {
SDL_Rect src = animation_current_rect(&self->anim);
/* Small inner dot to distinguish from other hazards */
float cx = body->pos.x + body->size.x * 0.5f - 2.0f;
float cy = body->pos.y + body->size.y * 0.5f - 2.0f;
SDL_Color dot = {255, 200, 255, 255};
renderer_draw_rect(vec2(cx, cy), vec2(4, 4), dot, LAYER_ENTITIES, cam);
Vec2 render_pos = vec2(body->pos.x, body->pos.y);
/* Pulse brightness when about to spawn */
uint8_t alpha = 255;
if (sd && sd->pulse_timer > 0) {
float pulse = sinf(sd->pulse_timer * 12.0f);
alpha = (pulse > 0) ? 200 : 255;
}
Sprite spr = {
.texture = g_spritesheet,
.src = src,
.pos = render_pos,
.size = vec2(SPRITE_CELL, SPRITE_CELL),
.flip_x = false,
.flip_y = false,
.layer = LAYER_ENTITIES,
.alpha = alpha,
};
renderer_submit(&spr);
} else {
/* Colored rect fallback */
SDL_Color color;
if (sd && sd->pulse_timer > 0) {
float pulse = sinf(sd->pulse_timer * 12.0f) * 0.5f + 0.5f;
uint8_t r = (uint8_t)(140 + 80 * pulse);
uint8_t b = (uint8_t)(180 + 60 * pulse);
color = (SDL_Color){r, 50, b, 255};
} else {
color = (SDL_Color){140, 50, 160, 255};
}
renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
}
}
static void spawner_destroy(Entity *self) {