Use a tileset
The three windowed backends (software, gl, wgpu) can composite PNG sprite sheets over or
instead of the bitmap font, via each crate’s tilesets feature. crossterm and terminal-wasm
have no pixels to sprite, so the same draw call that places a sprite on a windowed backend falls
back to plain text glyphs there automatically, with no capability check or cfg on your part.
Loading a sheet
A tileset is a sprite sheet PNG sliced into equally sized tiles, each mapped to a glyph via a
Codepage.
Build one with
TilesetOptions::builder
and register it on the backend’s builder before opening the window:
#[cfg(any(feature = "software", feature = "gl", feature = "wgpu"))]
fn tilesets() -> [retroglyph_window::tileset::TilesetOptions; 2] {
use retroglyph_window::tileset::{Codepage, TilesetOptions};
let room = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/tileset.png")).to_vec();
let chest = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/chest.png")).to_vec();
[
TilesetOptions::builder(room)
.tile_size(8, 16)
.columns(2)
.codepage(Codepage::Custom(vec!['#', '.', '@', '$']))
.build()
.expect("room asset is a valid 16x32 PNG, evenly divisible into 8x16 tiles"),
TilesetOptions::builder(chest)
.tile_size(32, 32)
.columns(1)
.codepage(Codepage::Custom(vec!['[']))
.build()
.expect("chest asset is a valid 32x32 PNG holding one tile"),
]
}
Codepage::Custom maps tiles to specific characters, in sheet order (here, #/./@/$ for a
four-tile sheet). Codepage::Cp437 (the default) and Codepage::Unicode { start } are the other
two options, for sheets authored against one of those existing layouts instead.
Every graphical backend takes a TilesetOptions the same way, via
PresenterBuilder::tileset
(or, when writing code generic over the three backends, retroglyph_window::PresenterBuilder’s
configure-style hook, as examples/examples/07_sprites_tileset.rs’s own configure function does
to register the same tilesets across all three builder types).
Drawing a sprite
Draw a single-cell sprite with the glyph it’s mapped to, exactly like drawing any other cell:
surface.put((x, y), '#', Style::default()) picks up the wall sprite from the sheet above instead
of the font glyph, on every backend that has one.
A sprite larger than one cell (a chest, a portrait, a boss) uses
Surface::put_span
instead: it declares a footprint of (width, height) cells, anchored at one glyph, with the rest of
the footprint carrying the sprite’s text fallback (what a cell backend actually prints, one
character per covered cell). One call, no capability check: a pixel backend blits one sprite across
the whole footprint, a cell backend prints the fallback glyphs. Grid::span_owner resolves any cell
inside a multi-cell sprite back to its anchor in O(1), which is what you want for hit-testing (e.g.
“did the player step on any part of the chest”) instead of hand-rolled rectangle math.
Recoloring a sprite per cell
Surface::with_tint
recolors a sprite for one draw call, without touching the underlying tileset. Use
Tint::Multiply
to darken toward black (a torchlight falloff around the player, for example) or
Tint::Mix
to blend toward another color (highlighting an interactable object). Tints only affect sprites; a
cell backend, with no sprite pixels to recolor, renders unaffected.
examples/examples/07_sprites_tileset.rs is a complete, runnable reference for all of the above,
including multi-cell spans and tinting:
cargo run --example 07_sprites_tileset --features software
cargo run --example 07_sprites_tileset --features gl
cargo run --example 07_sprites_tileset --features wgpu
See also
- Choose a backend for which crate to add
tilesetsto.