Detailed imp-style sprites for all 30 creatures in the high-res style; sprite texture in bands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
210f17773a
commit
01e5326721
132 files changed
+1295
-43
No files matched your search
+115
-29
@@ -1735,35 +1735,98 @@ const SPR_CELL: u32 = 12;
|
||||
const SPR_K: u32 = 3;
|
||||
pub const ACELL: u32 = SPR_CELL * SPR_K;
|
||||
|
||||
// Detailed sprites (generated, see art/generated/) in their own texture, used instead of the
|
||||
// 10x10 ones in the high-res graphics style (render3d::billboards). Two levels of detail from
|
||||
// the same pictures: 64x64 when the sprite gets enough screen pixels (close), 32x32 otherwise,
|
||||
// so neither loses pixels nor flickers. Sheets: transparent, the feet on the bottom row;
|
||||
// `idle*`: the five views side by side (VIEWS); `hack*`: the digging animation, HACK_FRAMES
|
||||
// frames side by side, one row per view. No gear yet.
|
||||
// Detailed sprites (generated, see art/generated/imp-sprite and art/generated/sprites) in their
|
||||
// own texture, used instead of the 10x10 ones in the high-res graphics style
|
||||
// (render3d::billboards). Two levels of detail from the same pictures: 64x64 when the sprite gets
|
||||
// enough screen pixels (close), 32x32 otherwise, so neither loses pixels nor flickers. Sheets:
|
||||
// transparent, the feet on the bottom row; idle: the five views side by side (VIEWS); hack (the
|
||||
// imp): the digging animation, HACK_FRAMES frames side by side, one row per view. No gear yet.
|
||||
struct HdSprite {
|
||||
kind: EK,
|
||||
idle: &'static [u8],
|
||||
hack: &'static [u8],
|
||||
idle_32: &'static [u8],
|
||||
hack_32: &'static [u8],
|
||||
hack: Option<(&'static [u8], &'static [u8])>, // 64x64 and 32x32
|
||||
}
|
||||
const HD_SPRITES: [HdSprite; 1] = [HdSprite {
|
||||
kind: EK::Worker,
|
||||
idle: include_bytes!("../assets/sprites/imp-64.png"),
|
||||
hack: include_bytes!("../assets/sprites/imp-hack-64.png"),
|
||||
idle_32: include_bytes!("../assets/sprites/imp-32.png"),
|
||||
hack_32: include_bytes!("../assets/sprites/imp-hack-32.png"),
|
||||
}];
|
||||
macro_rules! hd_sprite {
|
||||
($kind:expr, $name:literal) => {
|
||||
HdSprite {
|
||||
kind: $kind,
|
||||
idle: include_bytes!(concat!("../assets/sprites/", $name, "-64.png")),
|
||||
idle_32: include_bytes!(concat!("../assets/sprites/", $name, "-32.png")),
|
||||
hack: None,
|
||||
}
|
||||
};
|
||||
}
|
||||
const HD_SPRITES: [HdSprite; 31] = [
|
||||
HdSprite {
|
||||
kind: EK::Worker,
|
||||
idle: include_bytes!("../assets/sprites/imp-64.png"),
|
||||
idle_32: include_bytes!("../assets/sprites/imp-32.png"),
|
||||
hack: Some((include_bytes!("../assets/sprites/imp-hack-64.png"), include_bytes!("../assets/sprites/imp-hack-32.png"))),
|
||||
},
|
||||
hd_sprite!(EK::Kobold, "kobold"),
|
||||
hd_sprite!(EK::Spider, "spider"),
|
||||
hd_sprite!(EK::Mage, "mage"),
|
||||
hd_sprite!(EK::Ogre, "ogre"),
|
||||
hd_sprite!(EK::Knight, "knight"),
|
||||
hd_sprite!(EK::Archer, "archer"),
|
||||
hd_sprite!(EK::Priest, "priest"),
|
||||
hd_sprite!(EK::Dwarf, "dwarf"),
|
||||
hd_sprite!(EK::Paladin, "paladin"),
|
||||
hd_sprite!(EK::Jailer, "jailer"),
|
||||
hd_sprite!(EK::Priestess, "priestess"),
|
||||
hd_sprite!(EK::Chainbreaker, "chainbreaker"),
|
||||
hd_sprite!(EK::Bat, "bat"),
|
||||
hd_sprite!(EK::Gargoyle, "gargoyle"),
|
||||
hd_sprite!(EK::Owl, "owl"),
|
||||
hd_sprite!(EK::Wyrm, "wyrm"),
|
||||
hd_sprite!(EK::Cogling, "cogling"),
|
||||
hd_sprite!(EK::Revenant, "revenant"),
|
||||
hd_sprite!(EK::Giant, "giant"),
|
||||
hd_sprite!(EK::Fairy, "fairy"),
|
||||
hd_sprite!(EK::BattleMage, "battlemage"),
|
||||
hd_sprite!(EK::Ram, "ram"),
|
||||
hd_sprite!(EK::Catapult, "catapult"),
|
||||
hd_sprite!(EK::CaveBear, "cavebear"),
|
||||
hd_sprite!(EK::Beetle, "beetle"),
|
||||
hd_sprite!(EK::Trader, "trader"),
|
||||
hd_sprite!(EK::Thief, "thief"),
|
||||
hd_sprite!(EK::Monk, "monk"),
|
||||
hd_sprite!(EK::Ranger, "ranger"),
|
||||
hd_sprite!(EK::Inquisitor, "inquisitor"),
|
||||
];
|
||||
pub const HACK_FRAMES: u32 = 4;
|
||||
// a detailed sprite's cell: the same shape as a 10x10 sprite's 12x12 cell (6 texels per pixel),
|
||||
// so the billboard is the same; the 64x64 picture sits where the 10x10 sprite would
|
||||
pub const HD_CELL: u32 = SPR_CELL * 6;
|
||||
// columns per detailed sprite and detail level: idle and the hack frames, each without and with
|
||||
// the sack; then the same for the 32x32 level
|
||||
// columns of a sprite with the digging animation, per detail level: idle and the hack frames,
|
||||
// each without and with the sack; a sprite without it has one column per level
|
||||
const HD_COLS: u32 = (1 + HACK_FRAMES) * 2;
|
||||
pub const HD_TEX_W: f32 = (HD_SPRITES.len() as u32 * HD_COLS * 2 * HD_CELL) as f32;
|
||||
pub const HD_TEX_H: f32 = (VIEWS * 2 * HD_CELL) as f32;
|
||||
// the texture: the sprites' columns one after another (a sprite's own columns: the 64x64 level,
|
||||
// then the 32x32 one), in bands of HD_BAND columns (a sprite never straddles two bands), every
|
||||
// band VIEWS * 2 rows (a view, its white silhouette)
|
||||
const HD_BAND: u32 = 40;
|
||||
const fn hd_width(j: usize) -> u32 {
|
||||
if HD_SPRITES[j].hack.is_some() { HD_COLS * 2 } else { 2 }
|
||||
}
|
||||
const fn hd_start(j: usize) -> u32 {
|
||||
let mut p = 0;
|
||||
let mut i = 0;
|
||||
while i < j {
|
||||
p += hd_width(i);
|
||||
i += 1;
|
||||
}
|
||||
p
|
||||
}
|
||||
const HD_BANDS: u32 = (hd_start(HD_SPRITES.len()) + HD_BAND - 1) / HD_BAND;
|
||||
pub const HD_TEX_W: f32 = (HD_BAND * HD_CELL) as f32;
|
||||
pub const HD_TEX_H: f32 = (HD_BANDS * VIEWS * 2 * HD_CELL) as f32;
|
||||
|
||||
// where column `c` of sprite j lies: (texture column, first row of its band)
|
||||
fn hd_place(j: usize, c: u32) -> (u32, u32) {
|
||||
let p = hd_start(j) + c;
|
||||
(p % HD_BAND, p / HD_BAND * VIEWS * 2)
|
||||
}
|
||||
|
||||
// kinds that carry treasure in a sack on the back (instead of the coin sign over the head);
|
||||
// their 10x10 sprite with the sack gets an own atlas column after SPRITE_KINDS
|
||||
@@ -2583,18 +2646,23 @@ fn gen_hd_sprites() -> Image {
|
||||
for (j, hd) in HD_SPRITES.iter().enumerate() {
|
||||
// (idle, hack, picture size, cell grid, texels per pixel, sack, where the sack shows from the front)
|
||||
let levels = [
|
||||
(png(hd.idle), png(hd.hack), 64, HD_CELL, 1, &SACK_64[..], (0.7, -0.65)),
|
||||
(png(hd.idle_32), png(hd.hack_32), 32, HD_CELL / 2, 2, &SACK_32[..], (0.7, -0.65)),
|
||||
(png(hd.idle), hd.hack.map(|h| png(h.0)), 64, HD_CELL, 1, &SACK_64[..], (0.7, -0.65)),
|
||||
(png(hd.idle_32), hd.hack.map(|h| png(h.1)), 32, HD_CELL / 2, 2, &SACK_32[..], (0.7, -0.65)),
|
||||
];
|
||||
for (l, (idle, hack, n, size, k, sack, dx)) in levels.iter().enumerate() {
|
||||
for view in 0..VIEWS {
|
||||
let Some(hack) = hack else {
|
||||
let (col, row) = hd_place(j, l as u32);
|
||||
put_cell(&mut img, col, row + view * 2, &hd_cell(idle, view * n, 0, *n, *size), *k, HD_CELL);
|
||||
continue;
|
||||
};
|
||||
for f in 0..1 + HACK_FRAMES {
|
||||
let g = if f == 0 { hd_cell(idle, view * n, 0, *n, *size) } else { hd_cell(hack, (f - 1) * n, view * n, *n, *size) };
|
||||
let col = (j as u32 * 2 + l as u32) * HD_COLS + f * 2;
|
||||
put_cell(&mut img, col, view * 2, &g, *k, HD_CELL);
|
||||
let (col, row) = hd_place(j, l as u32 * HD_COLS + f * 2);
|
||||
put_cell(&mut img, col, row + view * 2, &g, *k, HD_CELL);
|
||||
let mut s = g;
|
||||
add_sack(&mut s, view, sack, *dx);
|
||||
put_cell(&mut img, col + 1, view * 2, &s, *k, HD_CELL);
|
||||
put_cell(&mut img, col + 1, row + view * 2, &s, *k, HD_CELL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2766,10 +2834,16 @@ impl Gfx {
|
||||
// (`hack` None) or a frame of the digging animation, with or without the sack; `fine`: the
|
||||
// 64x64 level, else the 32x32 one
|
||||
pub fn hd_cell_view(k: EK, white: bool, view: u32, hack: Option<u32>, sack: bool, fine: bool) -> Option<Rect> {
|
||||
let j = HD_SPRITES.iter().position(|h| h.kind == k)? as u32;
|
||||
let f = hack.map_or(0, |f| 1 + f % HACK_FRAMES);
|
||||
let col = (j * 2 + !fine as u32) * HD_COLS + f * 2 + sack as u32;
|
||||
let row = view.min(VIEWS - 1) * 2 + white as u32;
|
||||
let j = HD_SPRITES.iter().position(|h| h.kind == k)?;
|
||||
// sprites without the digging animation (and without a sack) show their idle picture
|
||||
let c = if HD_SPRITES[j].hack.is_some() {
|
||||
let f = hack.map_or(0, |f| 1 + f % HACK_FRAMES);
|
||||
(!fine as u32) * HD_COLS + f * 2 + sack as u32
|
||||
} else {
|
||||
!fine as u32
|
||||
};
|
||||
let (col, band) = hd_place(j, c);
|
||||
let row = band + view.min(VIEWS - 1) * 2 + white as u32;
|
||||
Some(Rect::new((col * HD_CELL) as f32, (row * HD_CELL) as f32, HD_CELL as f32, HD_CELL as f32))
|
||||
}
|
||||
|
||||
@@ -3190,7 +3264,19 @@ mod sprite_tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(Gfx::hd_cell_view(EK::Knight, false, 0, None, false, true).is_none());
|
||||
// every kind has a detailed sprite, none straddles two bands of the texture
|
||||
for &k in SPRITE_KINDS.iter() {
|
||||
for fine in [true, false] {
|
||||
for view in 0..VIEWS {
|
||||
let r = Gfx::hd_cell_view(k, false, view, None, false, fine).expect("a detailed sprite for every kind");
|
||||
assert!(opaque(&img, r) > 100, "{k:?} view {view} {fine}");
|
||||
assert!(r.x + r.w <= HD_TEX_W && r.y + r.h <= HD_TEX_H);
|
||||
}
|
||||
}
|
||||
}
|
||||
for j in 0..HD_SPRITES.len() {
|
||||
assert_eq!(hd_start(j) / HD_BAND, (hd_start(j) + hd_width(j) - 1) / HD_BAND, "sprite {j} straddles two bands");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user