Units face eight directions and animate their actions
- five sprite views per kind and gear tier (front, front three-quarter, profile, back three-quarter, back), mirrored for the other three directions; chosen from the unit's facing and the camera - units face where they walk, dig, repair or fight - animations: imps stomp while claiming (squash on landing), swing while digging, hammer while repairing; lunges in combat, punches in training, nodding while eating, swaying while reading or praying, breathing when idle, leaning back with loads, lying in bed, chained creatures pulling at their chains, flyers flapping Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1016ee5d6c
commit
d06ce2edb5
3 files changed
+234
-21
No files matched your search
+119
-17
@@ -1095,7 +1095,7 @@ pub const SPRITE_KINDS: [EK; 17] = [
|
||||
];
|
||||
// width of the sprite texture in pixels
|
||||
pub const SPRITE_TEX_W: f32 = (17 * 12) as f32;
|
||||
pub const SPRITE_TEX_H: f32 = (2 * GEAR_TIERS * SPR_CELL) as f32;
|
||||
pub const SPRITE_TEX_H: f32 = (2 * VIEWS * GEAR_TIERS * SPR_CELL) as f32;
|
||||
|
||||
pub fn sprite_index(k: EK) -> u32 {
|
||||
SPRITE_KINDS.iter().position(|&s| s == k).unwrap_or(0) as u32
|
||||
@@ -1112,12 +1112,106 @@ fn is_hero_kind(k: EK) -> bool {
|
||||
matches!(k, EK::Knight | EK::Archer | EK::Priest | EK::Dwarf | EK::Paladin)
|
||||
}
|
||||
|
||||
// the 12x12 cell of a kind with its gear painted around and onto the silhouette
|
||||
fn sprite_with_gear(k: EK, tier: u32) -> [[Option<Color>; 12]; 12] {
|
||||
let mut g = [[None; 12]; 12];
|
||||
// Views of a sprite for eight directions (the others are mirrored): 0 front, 1 front
|
||||
// three-quarter, 2 side, 3 back three-quarter, 4 back. The drawn sprites face the viewer;
|
||||
// the turned views are derived from them and face to the right.
|
||||
pub const VIEWS: u32 = 5;
|
||||
|
||||
type Rows = [[u8; SPR]; SPR];
|
||||
|
||||
fn is_body(ch: u8) -> bool {
|
||||
!matches!(ch, b'.' | b'k' | b'e' | b'f')
|
||||
}
|
||||
|
||||
// the commonest body colour of a sprite (for painting over eyes and faces)
|
||||
fn body_char(r: &Rows) -> u8 {
|
||||
let mut n = [0usize; 128];
|
||||
for row in r.iter() {
|
||||
for &ch in row.iter() {
|
||||
if is_body(ch) && ch < 128 {
|
||||
n[ch as usize] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
(0..128u8).max_by_key(|&c| n[c as usize]).filter(|&c| n[c as usize] > 0).unwrap_or(b'k')
|
||||
}
|
||||
|
||||
// moves the eyes one pixel to the right (where there is face or body to move onto)
|
||||
fn eyes_right(r: &mut Rows) {
|
||||
for row in r.iter_mut() {
|
||||
for x in (0..SPR - 1).rev() {
|
||||
if row[x] == b'e' && row[x + 1] != b'k' && row[x + 1] != b'.' && row[x + 1] != b'e' {
|
||||
row[x] = row[x + 1];
|
||||
row[x + 1] = b'e';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removes the middle column of the silhouette: a narrower body, as seen from the side
|
||||
fn narrow(r: &mut Rows) {
|
||||
let cols: Vec<usize> = (0..SPR).filter(|&x| r.iter().any(|row| row[x] != b'.')).collect();
|
||||
let (Some(&l), Some(&rr)) = (cols.first(), cols.last()) else { return };
|
||||
let m = (l + rr) / 2;
|
||||
for row in r.iter_mut() {
|
||||
for x in (1..=m).rev() {
|
||||
row[x] = row[x - 1];
|
||||
}
|
||||
row[0] = b'.';
|
||||
}
|
||||
}
|
||||
|
||||
fn sprite_view(k: EK, view: u32) -> Rows {
|
||||
let mut r: Rows = [[b'.'; SPR]; SPR];
|
||||
for (y, row) in sprite_rows(k).iter().enumerate() {
|
||||
for (x, ch) in row.chars().enumerate().take(SPR) {
|
||||
g[y + 1][x + 1] = pal(ch);
|
||||
for (x, ch) in row.bytes().enumerate().take(SPR) {
|
||||
r[y][x] = ch;
|
||||
}
|
||||
}
|
||||
let body = body_char(&r);
|
||||
match view {
|
||||
1 => eyes_right(&mut r),
|
||||
2 => {
|
||||
narrow(&mut r);
|
||||
// in profile only the eye on the facing side is seen
|
||||
for row in r.iter_mut() {
|
||||
let eyes: Vec<usize> = (0..SPR).filter(|&x| row[x] == b'e').collect();
|
||||
if eyes.len() >= 2 {
|
||||
let x = eyes[0];
|
||||
row[x] = if x > 0 && is_body(row[x - 1]) { row[x - 1] } else if row[x + 1] == b'f' { b'f' } else { body };
|
||||
}
|
||||
}
|
||||
eyes_right(&mut r);
|
||||
}
|
||||
3 | 4 => {
|
||||
// from behind: mirrored (the weapon is in the other hand), no eyes, no face
|
||||
for row in r.iter_mut() {
|
||||
row.reverse();
|
||||
}
|
||||
for y in 0..SPR {
|
||||
for x in 0..SPR {
|
||||
let ch = r[y][x];
|
||||
if ch == b'e' || ch == b'f' {
|
||||
let above = if y > 0 { r[y - 1][x] } else { b'.' };
|
||||
r[y][x] = if is_body(above) { above } else { body };
|
||||
}
|
||||
}
|
||||
}
|
||||
if view == 3 {
|
||||
narrow(&mut r);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
// the 12x12 cell of a kind with its gear painted around and onto the silhouette
|
||||
fn sprite_with_gear(k: EK, tier: u32, view: u32) -> [[Option<Color>; 12]; 12] {
|
||||
let mut g = [[None; 12]; 12];
|
||||
for (y, row) in sprite_view(k, view).iter().enumerate() {
|
||||
for (x, &ch) in row.iter().enumerate() {
|
||||
g[y + 1][x + 1] = pal(ch as char);
|
||||
}
|
||||
}
|
||||
if tier == 0 {
|
||||
@@ -1196,19 +1290,22 @@ fn sprite_with_gear(k: EK, tier: u32) -> [[Option<Color>; 12]; 12] {
|
||||
g
|
||||
}
|
||||
|
||||
// rows 2*tier: sprites with the gear of the tier, rows 2*tier+1: white silhouettes (hit flash)
|
||||
// row (view * GEAR_TIERS + tier) * 2: sprites with the gear of the tier, the row after it:
|
||||
// white silhouettes (hit flash)
|
||||
fn gen_sprites() -> Image {
|
||||
let n = SPRITE_KINDS.len() as u32;
|
||||
let mut img = Image::gen_image_color((n * SPR_CELL) as u16, (2 * GEAR_TIERS * SPR_CELL) as u16, Color::new(0.0, 0.0, 0.0, 0.0));
|
||||
let mut img = Image::gen_image_color((n * SPR_CELL) as u16, (2 * VIEWS * GEAR_TIERS * SPR_CELL) as u16, Color::new(0.0, 0.0, 0.0, 0.0));
|
||||
for (i, &k) in SPRITE_KINDS.iter().enumerate() {
|
||||
for tier in 0..GEAR_TIERS {
|
||||
let g = sprite_with_gear(k, tier);
|
||||
for (y, row) in g.iter().enumerate() {
|
||||
for (x, px) in row.iter().enumerate() {
|
||||
if let Some(col) = px {
|
||||
let (px, py) = (i as u32 * SPR_CELL + x as u32, tier * 2 * SPR_CELL + y as u32);
|
||||
img.set_pixel(px, py, *col);
|
||||
img.set_pixel(px, py + SPR_CELL, WHITE);
|
||||
for view in 0..VIEWS {
|
||||
for tier in 0..GEAR_TIERS {
|
||||
let g = sprite_with_gear(k, tier, view);
|
||||
for (y, row) in g.iter().enumerate() {
|
||||
for (x, px) in row.iter().enumerate() {
|
||||
if let Some(col) = px {
|
||||
let (px, py) = (i as u32 * SPR_CELL + x as u32, (view * GEAR_TIERS + tier) * 2 * SPR_CELL + y as u32);
|
||||
img.set_pixel(px, py, *col);
|
||||
img.set_pixel(px, py + SPR_CELL, WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1351,8 +1448,13 @@ impl Gfx {
|
||||
|
||||
// the whole 12x12 cell with the gear of a level (billboards)
|
||||
pub fn sprite_cell(k: EK, white: bool, level: u32) -> Rect {
|
||||
Gfx::sprite_cell_view(k, white, level, 0)
|
||||
}
|
||||
|
||||
// the cell of one of the five views (see VIEWS)
|
||||
pub fn sprite_cell_view(k: EK, white: bool, level: u32, view: u32) -> Rect {
|
||||
let i = sprite_index(k);
|
||||
let row = gear_tier(level) * 2 + white as u32;
|
||||
let row = (view.min(VIEWS - 1) * GEAR_TIERS + gear_tier(level)) * 2 + white as u32;
|
||||
Rect::new((i * SPR_CELL) as f32, (row * SPR_CELL) as f32, SPR_CELL as f32, SPR_CELL as f32)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user