Walk and attack animations for knight and kobold; the dead topple, sink and fade (death animations where drawn)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
16e299d1f1
commit
fb71009110
29 files changed
+697
-3
No files matched your search
+88
-1
@@ -1264,8 +1264,25 @@ pub struct R3d {
|
||||
lava: Vec<f32>, // lava light per tile (see lava_field)
|
||||
chunks_hd: bool, // the chunks were built with the high-res atlas
|
||||
chunks_holiday: u8, // ... and with this holiday's decorations
|
||||
dying: Vec<Dying>, // units shown dying (see update_dying)
|
||||
seen: Vec<Dying>, // the units of the last frame (their last looks)
|
||||
seen_time: f32, // the game time of the last frame
|
||||
}
|
||||
|
||||
// a unit that has just died, shown falling for DEATH_T seconds of game time (only the renderer
|
||||
// knows: the game removes the dead at once)
|
||||
#[derive(Clone, Copy)]
|
||||
struct Dying {
|
||||
id: u32,
|
||||
kind: EK,
|
||||
x: f32,
|
||||
z: f32,
|
||||
dir: f32,
|
||||
level: u32,
|
||||
t0: f32,
|
||||
}
|
||||
const DEATH_T: f32 = 1.4;
|
||||
|
||||
// The 3D resolution setting refers to a window 1080 device pixels high: smaller windows use a
|
||||
// proportionally smaller divisor, so the 3D pixels keep their size relative to the window.
|
||||
// 1 (full resolution) stays 1.
|
||||
@@ -1314,7 +1331,7 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 {
|
||||
|
||||
impl R3d {
|
||||
pub fn new() -> R3d {
|
||||
R3d { lit: lit_material(), glow: glow_material(), opaque: opaque_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, pixel_rows: crate::settings::PIXEL_ART_ROWS as u32, target: None, lava: Vec::new(), chunks_hd: false, chunks_holiday: 0 }
|
||||
R3d { lit: lit_material(), glow: glow_material(), opaque: opaque_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, pixel_rows: crate::settings::PIXEL_ART_ROWS as u32, target: None, lava: Vec::new(), chunks_hd: false, chunks_holiday: 0, dying: Vec::new(), seen: Vec::new(), seen_time: 0.0 }
|
||||
}
|
||||
|
||||
// Changing the wall detail rebuilds all chunks
|
||||
@@ -1662,6 +1679,7 @@ impl R3d {
|
||||
// sprites as billboards
|
||||
// the world's pixel rows (detailed sprites only where they get enough pixels)
|
||||
let world_rows = pix.map_or(screen_height() * screen_dpi_scale(), |(_, th, _)| th as f32);
|
||||
self.update_dying(g);
|
||||
for mesh in self.billboards(g, gfx, cam, t, world_rows).iter() {
|
||||
draw_mesh(mesh);
|
||||
}
|
||||
@@ -1739,6 +1757,29 @@ impl R3d {
|
||||
self.draw_2d(g, gfx, cam, &m, t);
|
||||
}
|
||||
|
||||
// units that vanished since the last frame died (or left): they are shown falling for a
|
||||
// moment; a new game, a loaded one or a jump in time starts afresh
|
||||
fn update_dying(&mut self, g: &Game) {
|
||||
let now: Vec<Dying> = g
|
||||
.ents
|
||||
.iter()
|
||||
.filter(|e| e.alive)
|
||||
.map(|e| Dying { id: e.id, kind: e.kind, x: e.disp_x, z: e.disp_y, dir: e.dir, level: e.level, t0: g.time })
|
||||
.collect();
|
||||
if g.time < self.seen_time || g.time > self.seen_time + 2.0 {
|
||||
self.dying.clear();
|
||||
} else {
|
||||
let gone: Vec<Dying> = self.seen.iter().filter(|d| !now.iter().any(|e| e.id == d.id)).copied().collect();
|
||||
// many at once: not deaths but a change of the map
|
||||
if gone.len() <= 24 {
|
||||
self.dying.extend(gone.into_iter().map(|d| Dying { t0: g.time, ..d }));
|
||||
}
|
||||
}
|
||||
self.dying.retain(|d| g.time - d.t0 < DEATH_T);
|
||||
self.seen = now;
|
||||
self.seen_time = g.time;
|
||||
}
|
||||
|
||||
// the units as billboards: the 10x10 sprites, the detailed ones (two levels) and their gear
|
||||
fn billboards(&self, g: &Game, gfx: &Gfx, cam: &Cam, t: f32, world_rows: f32) -> Vec<Mesh> {
|
||||
let (_, r, _) = cam.basis();
|
||||
@@ -2014,6 +2055,52 @@ impl R3d {
|
||||
[toward; 4],
|
||||
);
|
||||
}
|
||||
// the dying: their death animation (detailed sprites that have one), else they topple
|
||||
// over; then they sink into the floor and fade
|
||||
for d in &self.dying {
|
||||
let el = g.time - d.t0;
|
||||
let w = sprite_size(d.kind);
|
||||
let dv = vec2(d.dir.cos(), d.dir.sin());
|
||||
let ang = dv.dot(rg).atan2(dv.dot(tg));
|
||||
let view = ((ang / std::f32::consts::FRAC_PI_4).round().abs() as u32).min(4);
|
||||
let mirror = ang < 0.0;
|
||||
let fwd = if mirror { -1.0 } else { 1.0 };
|
||||
let texel = w * 1.2 * px_per_tile / crate::gfx::HD_CELL as f32;
|
||||
let fine = texel >= 0.85;
|
||||
let die = Gfx::hd_has_anim(d.kind, crate::gfx::HdAnim::Die);
|
||||
let frame = ((el / 0.22) as u32).min(crate::gfx::ANIM_FRAMES - 1);
|
||||
let pic = if hd && texel * 2.0 >= 0.85 { Gfx::hd_picture(d.kind, view, die.then_some((crate::gfx::HdAnim::Die, frame)), false) } else { None };
|
||||
let rot = if die && pic.is_some() { 0.0 } else { (el / 0.35).min(1.0) * std::f32::consts::FRAC_PI_2 * 0.95 * fwd };
|
||||
let sink = ((el - DEATH_T * 0.55) / (DEATH_T * 0.45)).clamp(0.0, 1.0);
|
||||
let src = match pic {
|
||||
Some(p) => Gfx::hd_uv(p, fine),
|
||||
None => {
|
||||
let r = Gfx::sprite_cell_view(d.kind, false, d.level, view);
|
||||
Rect::new(r.x / sw, r.y / sh, r.w / sw, r.h / sh)
|
||||
}
|
||||
};
|
||||
let (mut u0, v0, mut u1, v1) = (src.x, src.y, src.x + src.w, src.y + src.h);
|
||||
if mirror {
|
||||
std::mem::swap(&mut u0, &mut u1);
|
||||
}
|
||||
// fading: the see-through look of invisible units (every other pixel) at the end
|
||||
let a = amb(d.x, d.z) * (1.0 - 0.4 * sink);
|
||||
let c = col(Vec3::splat(a), if sink > 0.5 { 0.35 } else { 1.0 });
|
||||
let base = vec3(d.x, -sink * 0.25 * w, d.z) + toward * 0.45;
|
||||
let (sr, cr) = rot.sin_cos();
|
||||
let corner = |dx: f32, dy: f32| base + r * (dx * cr - dy * sr) + Vec3::Y * (dx * sr + dy * cr);
|
||||
let mb = match pic {
|
||||
Some(_) if fine => &mut mb_hd,
|
||||
Some(_) => &mut mb_hd32,
|
||||
None => &mut mb,
|
||||
};
|
||||
mb.quad(
|
||||
[corner(-w * 0.6, w * 1.1), corner(w * 0.6, w * 1.1), corner(w * 0.6, -w * 0.1), corner(-w * 0.6, -w * 0.1)],
|
||||
[vec2(u0, v0), vec2(u1, v0), vec2(u1, v1), vec2(u0, v1)],
|
||||
[c; 4],
|
||||
[toward; 4],
|
||||
);
|
||||
}
|
||||
let mesh = |mb: MB, tex: &Texture2D| if mb.v.is_empty() { None } else { Some(Mesh { vertices: mb.v, indices: mb.i, texture: Some(tex.clone()) }) };
|
||||
[mesh(mb, &gfx.sprites), mesh(mb_hd, &gfx.hd_sprites), mesh(mb_hd32, &gfx.hd_sprites32), mesh(mb_gear, &gfx.gear)].into_iter().flatten().collect()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user