Detailed sprites: frames for walk and attack animations, gear laid over them, hit flash in the shader, 32x32 level in its own texture
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
01e5326721
commit
ffad92b641
2 files changed
+366
-130
No files matched your search
+109
-17
@@ -16,6 +16,8 @@ pub const MAX_LIGHTS: usize = 24;
|
||||
const ATLAS_W: f32 = 256.0;
|
||||
const ATLAS_H: f32 = (crate::gfx::ATLAS_ROWS * 16) as f32;
|
||||
const DISP_AMP: f32 = 0.11; // wall surface irregularity
|
||||
// vertex alpha of a detailed sprite flashing white after a hit (see the lit shader)
|
||||
const FLASH_A: u8 = 204;
|
||||
|
||||
// ------------------------------------------------------------------ camera
|
||||
|
||||
@@ -294,6 +296,12 @@ void main() {
|
||||
wave = dot(slope, vec2(0.45, 0.35)) * 0.6 + ripple * 0.4;
|
||||
}
|
||||
vec4 tex = texture2D(Texture, tuv);
|
||||
// the hit flash of the detailed sprites (vertex alpha FLASH_A): the picture in white
|
||||
if (color.a > 0.77 && color.a < 0.83) {
|
||||
if (tex.a < 0.5) discard;
|
||||
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
return;
|
||||
}
|
||||
if (liquid && HdLiquid > 0.5) {
|
||||
// a second, larger layer drifting slower and sideways: the surface churns instead of
|
||||
// only sliding along
|
||||
@@ -1654,8 +1662,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);
|
||||
let (small, detailed) = self.billboards(g, gfx, cam, t, world_rows);
|
||||
for mesh in [small, detailed].iter().flatten() {
|
||||
for mesh in self.billboards(g, gfx, cam, t, world_rows).iter() {
|
||||
draw_mesh(mesh);
|
||||
}
|
||||
// solid unlit objects (default material, depth tested)
|
||||
@@ -1732,8 +1739,8 @@ impl R3d {
|
||||
self.draw_2d(g, gfx, cam, &m, t);
|
||||
}
|
||||
|
||||
// the units as billboards: the 10x10 sprites and the detailed ones (two textures)
|
||||
fn billboards(&self, g: &Game, gfx: &Gfx, cam: &Cam, t: f32, world_rows: f32) -> (Option<Mesh>, Option<Mesh>) {
|
||||
// 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();
|
||||
let toward = cam.eye_dir();
|
||||
let amb = |x: f32, z: f32| {
|
||||
@@ -1752,6 +1759,8 @@ impl R3d {
|
||||
let tg = vec2(toward.x, toward.z).normalize_or_zero();
|
||||
let mut mb = MB::new();
|
||||
let mut mb_hd = MB::new();
|
||||
let mut mb_hd32 = MB::new();
|
||||
let mut mb_gear = MB::new();
|
||||
let sw = SPRITE_TEX_W;
|
||||
let sh = SPRITE_TEX_H;
|
||||
// detailed sprites only in the high-res style, and only where a sprite's cell (1.2 sizes
|
||||
@@ -1865,18 +1874,40 @@ impl R3d {
|
||||
// treasure in a sack on the back; digging as a drawn animation (detailed sprites),
|
||||
// frame by frame with the same rhythm as the swing below
|
||||
let sack = e.carry > 0 && Gfx::has_sack(e.kind);
|
||||
// drawn animations (detailed sprites): digging, walking, attacking, frame by frame in
|
||||
// the rhythm of the swings below (two steps per walk cycle, as the bobbing)
|
||||
let cycle = |p: f32| ((p / std::f32::consts::TAU).rem_euclid(1.0) * crate::gfx::ANIM_FRAMES as f32) as u32;
|
||||
let hacking = e.working && !e.moving && !sleeping && matches!(e.job, Job::Dig(_) | Job::TearBridge(_));
|
||||
let frame = ((ph * 1.3 / std::f32::consts::TAU).rem_euclid(1.0) * crate::gfx::HACK_FRAMES as f32) as u32;
|
||||
let hd = if detailed { Gfx::hd_cell_view(e.kind, white, v, hacking.then_some(frame), sack, fine) } else { None };
|
||||
if hd.is_some() && hacking {
|
||||
// the frames swing the pickaxe: no tilting of the whole figure
|
||||
rot = 0.0;
|
||||
lunge = 0.0;
|
||||
let attacking = e.working && !e.moving && !sleeping && matches!(e.job, Job::Attack(_) | Job::AttackTower(_) | Job::AttackCore | Job::Torment(_));
|
||||
let anim = if e.captive != 0 || sleeping {
|
||||
None
|
||||
} else if hacking {
|
||||
Some((crate::gfx::HdAnim::Hack, cycle(ph * 1.3)))
|
||||
} else if e.moving {
|
||||
Some((crate::gfx::HdAnim::Walk, cycle(e.anim * 12.0)))
|
||||
} else if attacking {
|
||||
Some((crate::gfx::HdAnim::Attack, cycle(ph * 0.9)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let pic = if detailed { Gfx::hd_picture(e.kind, v, anim, sack) } else { None };
|
||||
if let (Some(_), Some((a, _))) = (pic, anim) {
|
||||
if Gfx::hd_has_anim(e.kind, a) {
|
||||
// the frames move the figure: no tilting, lunging or bobbing of the whole
|
||||
rot = 0.0;
|
||||
lunge = 0.0;
|
||||
bob = bob.min(0.02);
|
||||
}
|
||||
}
|
||||
let src = hd.or_else(|| sack.then(|| Gfx::sprite_cell_sack(e.kind, white, e.level, v)).flatten()).unwrap_or_else(|| Gfx::sprite_cell_view(e.kind, white, e.level, v));
|
||||
let (sw, sh) = if hd.is_some() { (crate::gfx::HD_TEX_W, crate::gfx::HD_TEX_H) } else { (sw, sh) };
|
||||
let (mut u0, v0) = (src.x / sw, src.y / sh);
|
||||
let (mut u1, v1) = ((src.x + src.w) / sw, (src.y + src.h) / sh);
|
||||
let src = match pic {
|
||||
Some(p) => Gfx::hd_uv(p, fine),
|
||||
None => {
|
||||
let r = sack.then(|| Gfx::sprite_cell_sack(e.kind, white, e.level, v)).flatten().unwrap_or_else(|| Gfx::sprite_cell_view(e.kind, white, e.level, v));
|
||||
Rect::new(r.x / sw, r.y / sh, r.w / sw, r.h / sh)
|
||||
}
|
||||
};
|
||||
let (mut u0, v0) = (src.x, src.y);
|
||||
let (mut u1, v1) = (src.x + src.w, src.y + src.h);
|
||||
if mirror {
|
||||
std::mem::swap(&mut u0, &mut u1);
|
||||
}
|
||||
@@ -1884,7 +1915,9 @@ impl R3d {
|
||||
// spell looks: stone grey, plague green, see-through when invisible
|
||||
let alpha = if e.invis > 0.0 { 0.35 } else { 1.0 };
|
||||
let c = if white {
|
||||
[255, 255, 255, 255]
|
||||
// the detailed sprites flash white in the shader (FLASH_A), the 10x10 ones have
|
||||
// white silhouettes
|
||||
[255, 255, 255, if pic.is_some() { FLASH_A } else { 255 }]
|
||||
} else if e.captive == 3 {
|
||||
col(vec3(a * 1.2, a * 0.55, a * 0.55), 1.0)
|
||||
} else if e.shield > 0.0 {
|
||||
@@ -1914,7 +1947,66 @@ impl R3d {
|
||||
};
|
||||
// the 12x12 cell (sprite with a one pixel border for the gear): the sprite's feet
|
||||
// (bottom of its 11th pixel row) stay on the ground
|
||||
let mb = if hd.is_some() { &mut mb_hd } else { &mut mb };
|
||||
// the gear of the detailed sprites: parts laid over the picture where its anchors say
|
||||
// (shoulder pieces from level 3, a helmet from 5, a cape from 7, gold and a crown
|
||||
// from 9, like the 10x10 sprites' gear)
|
||||
if let Some(p) = pic {
|
||||
let tier = crate::gfx::gear_tier(e.level);
|
||||
if tier > 0 && !white && e.captive == 0 && !sleeping {
|
||||
let an = Gfx::hd_anchor(p);
|
||||
let shade = |rgb: (f32, f32, f32)| {
|
||||
let k = a * 1.25;
|
||||
[(rgb.0 * k).min(255.0) as u8, (rgb.1 * k).min(255.0) as u8, (rgb.2 * k).min(255.0) as u8, 255u8]
|
||||
};
|
||||
let metal = shade(match tier {
|
||||
1 => (196.0, 126.0, 60.0),
|
||||
4 => (250.0, 206.0, 72.0),
|
||||
_ => (208.0, 214.0, 228.0),
|
||||
});
|
||||
// a point of the picture's cell (shares of it) in the world
|
||||
let at = |fx: f32, fy: f32, depth: f32| {
|
||||
let fx = if mirror { 1.0 - fx } else { fx };
|
||||
corner((fx - 0.5) * 1.2 * w, (1.0 - fy) * 1.2 * h - 0.1 * h) + toward * depth
|
||||
};
|
||||
let gt = crate::gfx::GEAR_TEX as f32;
|
||||
let mut part = |x0: f32, y0: f32, x1: f32, y1: f32, uv: (u32, u32, u32, u32), c: [u8; 4], depth: f32, flip: bool| {
|
||||
let (mut a0, b0) = (uv.0 as f32 / gt, uv.1 as f32 / gt);
|
||||
let (mut a1, b1) = ((uv.0 + uv.2) as f32 / gt, (uv.1 + uv.3) as f32 / gt);
|
||||
if mirror != flip {
|
||||
std::mem::swap(&mut a0, &mut a1);
|
||||
}
|
||||
mb_gear.quad([at(x0, y0, depth), at(x1, y0, depth), at(x1, y1, depth), at(x0, y1, depth)], [vec2(a0, b0), vec2(a1, b0), vec2(a1, b1), vec2(a0, b1)], [c; 4], [toward; 4]);
|
||||
};
|
||||
let mid = (an.head.0 + an.head.1) * 0.5;
|
||||
let neck = an.top + (an.shoulder - an.top) * 0.45;
|
||||
if tier >= 3 {
|
||||
// the cape hangs behind the figure (seen from behind: over its back)
|
||||
let cape = shade(if e.hero { (44.0, 74.0, 156.0) } else { (128.0, 22.0, 34.0) });
|
||||
let depth = if v >= 3 { 0.004 } else { -0.004 };
|
||||
part(an.sh.0 - 0.03, an.shoulder - 0.02, an.sh.1 + 0.03, an.bottom - 0.1, crate::gfx::GEAR_CAPE, cape, depth, false);
|
||||
}
|
||||
if tier >= 2 {
|
||||
part(an.head.0 - 0.02, an.top - 0.03, an.head.1 + 0.02, neck, crate::gfx::GEAR_HELMET, metal, 0.006, false);
|
||||
if tier >= 4 {
|
||||
part(mid - 0.1, an.top - 0.11, mid + 0.1, an.top + 0.01, crate::gfx::GEAR_CROWN, shade((255.0, 220.0, 90.0)), 0.008, false);
|
||||
} else if e.hero {
|
||||
part(mid - 0.035, an.top - 0.13, mid + 0.035, an.top + 0.01, crate::gfx::GEAR_PLUME, shade((210.0, 40.0, 40.0)), 0.008, false);
|
||||
} else {
|
||||
let bone = shade((232.0, 222.0, 200.0));
|
||||
part(an.head.0 - 0.06, an.top - 0.11, an.head.0 + 0.02, an.top + 0.02, crate::gfx::GEAR_HORN, bone, 0.008, false);
|
||||
part(an.head.1 - 0.02, an.top - 0.11, an.head.1 + 0.06, an.top + 0.02, crate::gfx::GEAR_HORN, bone, 0.008, true);
|
||||
}
|
||||
}
|
||||
for (x, flip) in [(an.sh.0 + 0.03, false), (an.sh.1 - 0.03, true)] {
|
||||
part(x - 0.075, an.shoulder - 0.05, x + 0.075, an.shoulder + 0.06, crate::gfx::GEAR_PAULDRON, metal, 0.007, flip);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mb = match pic {
|
||||
Some(_) if fine => &mut mb_hd,
|
||||
Some(_) => &mut mb_hd32,
|
||||
None => &mut mb,
|
||||
};
|
||||
mb.quad(
|
||||
[corner(-w * 0.6, h * 1.1), corner(w * 0.6, h * 1.1), corner(w * 0.6, -h * 0.1), corner(-w * 0.6, -h * 0.1)],
|
||||
[vec2(u0, v0), vec2(u1, v0), vec2(u1, v1), vec2(u0, v1)],
|
||||
@@ -1923,7 +2015,7 @@ impl R3d {
|
||||
);
|
||||
}
|
||||
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, &gfx.sprites), mesh(mb_hd, &gfx.hd_sprites), mesh(mb_hd32, &gfx.hd_sprites32), mesh(mb_gear, &gfx.gear)].into_iter().flatten().collect()
|
||||
}
|
||||
|
||||
fn draw_2d(&self, g: &Game, gfx: &Gfx, cam: &Cam, m: &Mat4, t: f32) {
|
||||
|
||||
Reference in new issue
Block a user