Add bitmap font rendering via PNG tilesheet
Replace egui text rendering with a BitmapFont system: - Glyph.tile: u32 replaces ch: char; top-left pixel of PNG is background sentinel - BitmapFont loads/preprocesses PNG to opaque-white + transparent, tinted at render time - Default font: assets/vga-font-8x16.png (CP437, 8×16 tiles); placeholder generated if missing - Boards can specify a per-board font via [font] in their .toml file - Editor Board tab: "Font…" button opens font dialog (rfd file picker, tile dims, tilesheet preview) - Glyph picker: tile grid from active font replaces hardcoded ASCII char grid - map_file: TileIndex untagged enum accepts char or integer in palette entries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+77
-28
@@ -3,36 +3,55 @@ use serde::Deserialize;
|
||||
|
||||
/// The visual representation of a single board cell.
|
||||
///
|
||||
/// `Glyph` holds everything needed to draw one cell on screen: which character
|
||||
/// to display and what colors to use. It is stored per-cell (not per archetype),
|
||||
/// so individual cells can vary their appearance independently — for example,
|
||||
/// a field of "fire" tiles where each flame has a slightly different color
|
||||
/// while all sharing the same [`Archetype`] behavior.
|
||||
/// `Glyph` holds everything needed to draw one cell on screen: which tile
|
||||
/// index to display and what colors to use. It is stored per-cell (not per
|
||||
/// archetype), so individual cells can vary their appearance independently.
|
||||
///
|
||||
/// `tile` is a left-to-right, top-to-bottom index into the board's bitmap
|
||||
/// font. For the default CP437 font this matches the ASCII/CP437 code point.
|
||||
///
|
||||
/// `Glyph` values come from the map file palette and are set at load time.
|
||||
/// The player is the only entity whose glyph is hardcoded at runtime
|
||||
/// (see [`Glyph::player`]).
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Glyph {
|
||||
/// The character to display in this cell.
|
||||
pub ch: char,
|
||||
/// Foreground (text) color.
|
||||
/// Tile index into the board's bitmap font (left-to-right, top-to-bottom).
|
||||
pub tile: u32,
|
||||
/// Foreground color, applied to non-background pixels of the tile.
|
||||
pub fg: Color32,
|
||||
/// Background color, drawn as a filled rectangle behind the character.
|
||||
/// Background color, drawn as a filled rectangle behind the tile.
|
||||
pub bg: Color32,
|
||||
}
|
||||
|
||||
impl Glyph {
|
||||
/// Returns the glyph used to render the player: `@` in light blue on black.
|
||||
/// Returns the glyph used to render the player: tile 64 (`@`) in light blue on black.
|
||||
///
|
||||
/// This is the only hardcoded glyph; all other glyphs come from the map
|
||||
/// file palette. It will be removed once the player becomes a scripted
|
||||
/// object with its own palette entry.
|
||||
pub fn player() -> Self {
|
||||
Self { ch: '@', fg: Color32::LIGHT_BLUE, bg: Color32::BLACK }
|
||||
pub const fn player() -> Self {
|
||||
Self {
|
||||
tile: 64,
|
||||
fg: Color32::LIGHT_BLUE,
|
||||
bg: Color32::BLACK,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies a bitmap font for a board.
|
||||
///
|
||||
/// Each board can optionally specify a font image and tile dimensions.
|
||||
/// When absent, the app's default embedded CP437 font is used.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct FontSpec {
|
||||
/// Path to the PNG font image, relative to the working directory.
|
||||
pub path: String,
|
||||
/// Width of each tile in the font image, in pixels.
|
||||
pub tile_w: u32,
|
||||
/// Height of each tile in the font image, in pixels.
|
||||
pub tile_h: u32,
|
||||
}
|
||||
|
||||
/// The behavioral properties of a board cell at runtime.
|
||||
///
|
||||
/// `Behavior` is a plain data struct returned by [`Archetype::behavior`]. It
|
||||
@@ -87,19 +106,31 @@ impl Archetype {
|
||||
/// For `Object`, this is a placeholder until Rhai scripts drive the behavior.
|
||||
pub fn behavior(&self) -> Behavior {
|
||||
match self {
|
||||
Archetype::Empty => Behavior { passable: true, opaque: false },
|
||||
Archetype::Wall => Behavior { passable: false, opaque: true },
|
||||
Archetype::Object => Behavior { passable: false, opaque: false },
|
||||
Archetype::ErrorBlock => Behavior { passable: false, opaque: true },
|
||||
Archetype::Empty => Behavior {
|
||||
passable: true,
|
||||
opaque: false,
|
||||
},
|
||||
Archetype::Wall => Behavior {
|
||||
passable: false,
|
||||
opaque: true,
|
||||
},
|
||||
Archetype::Object => Behavior {
|
||||
passable: false,
|
||||
opaque: false,
|
||||
},
|
||||
Archetype::ErrorBlock => Behavior {
|
||||
passable: false,
|
||||
opaque: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the canonical name used to reference this archetype in map files.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Archetype::Empty => "empty",
|
||||
Archetype::Wall => "wall",
|
||||
Archetype::Object => "object",
|
||||
Archetype::Empty => "empty",
|
||||
Archetype::Wall => "wall",
|
||||
Archetype::Object => "object",
|
||||
Archetype::ErrorBlock => "error_block",
|
||||
}
|
||||
}
|
||||
@@ -110,11 +141,27 @@ impl Archetype {
|
||||
/// cells retain their own per-cell glyph.
|
||||
pub fn default_glyph(&self) -> Glyph {
|
||||
match self {
|
||||
Archetype::Empty => Glyph { ch: ' ', fg: Color32::BLACK, bg: Color32::BLACK },
|
||||
Archetype::Wall => Glyph { ch: '#', fg: Color32::from_rgb(0x80, 0x80, 0x80), bg: Color32::from_rgb(0x60, 0x60, 0x60) },
|
||||
Archetype::Object => Glyph { ch: '?', fg: Color32::YELLOW, bg: Color32::BLACK },
|
||||
Archetype::Empty => Glyph {
|
||||
tile: 32,
|
||||
fg: Color32::BLACK,
|
||||
bg: Color32::BLACK,
|
||||
},
|
||||
Archetype::Wall => Glyph {
|
||||
tile: 35,
|
||||
fg: Color32::from_rgb(0x80, 0x80, 0x80),
|
||||
bg: Color32::from_rgb(0x60, 0x60, 0x60),
|
||||
},
|
||||
Archetype::Object => Glyph {
|
||||
tile: 63,
|
||||
fg: Color32::YELLOW,
|
||||
bg: Color32::BLACK,
|
||||
},
|
||||
// Visually distinct so malformed map files are immediately obvious.
|
||||
Archetype::ErrorBlock => Glyph { ch: '?', fg: Color32::YELLOW, bg: Color32::RED },
|
||||
Archetype::ErrorBlock => Glyph {
|
||||
tile: 63,
|
||||
fg: Color32::YELLOW,
|
||||
bg: Color32::RED,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,10 +175,10 @@ impl TryFrom<&str> for Archetype {
|
||||
/// [`Archetype::ErrorBlock`] and log the error so the problem is visible.
|
||||
fn try_from(name: &str) -> Result<Self, Self::Error> {
|
||||
match name {
|
||||
"empty" => Ok(Archetype::Empty),
|
||||
"wall" => Ok(Archetype::Wall),
|
||||
"empty" => Ok(Archetype::Empty),
|
||||
"wall" => Ok(Archetype::Wall),
|
||||
"object" => Ok(Archetype::Object),
|
||||
_ => Err(format!("unknown archetype: {name}")),
|
||||
_ => Err(format!("unknown archetype: {name}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,6 +277,8 @@ pub struct Board {
|
||||
pub objects: Vec<ObjectDef>,
|
||||
/// Portals on this board. Parsed from the map file; not yet active.
|
||||
pub portals: Vec<PortalDef>,
|
||||
/// Optional font override for this board. When `None`, the app default is used.
|
||||
pub font: Option<FontSpec>,
|
||||
}
|
||||
|
||||
impl Board {
|
||||
@@ -295,4 +344,4 @@ impl GameState {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user