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:
134
src/game/enemy.c
134
src/game/enemy.c
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user