Switch to 16-bit truecolor framebuffer and PNG assets
pbio moves to git.bnd.wtf/irrlicht/pbio.git branch truecolor16: the framebuffer is now RGB565 u16, no palette. Assets are loaded from PNG via the png crate and quantized to RGB565 on load; transparency comes from the alpha channel as a per-tile opacity mask, so any color (black included) can be opaque. The TGA loader and the palette-order fragility are gone. overworld.png is the unmodified Mini-Medieval Overworld sheet (same 24-column layout as the old TGA, so map tile ids are unchanged); entities.png holds the player sprite extracted from the old entities.tga. Adds an ignored snapshot test that renders the first frame headlessly to a PNG for eyeballing the renderer without a window. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
64a6e1d42d
commit
5a055c048d
+40
-11
@@ -3,7 +3,7 @@ mod pixelhelper;
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use crate::assets::Image;
|
||||
use crate::assets::{Image, Tile};
|
||||
use crate::input::{GameAction, InputState};
|
||||
use sim::entity::Entity;
|
||||
use sim::map::TileMap;
|
||||
@@ -25,8 +25,11 @@ const MOVE_INTERVAL_MS: usize = (TICK_MS * TICKS_PER_MOVE as f32) as usize;
|
||||
/// drag) is dropped instead of spiralling into a burst of ticks.
|
||||
const MAX_FRAME_MS: f32 = 250.0;
|
||||
|
||||
/// Palette index for the planned route. Dim blue in RGB332 (r=0, g=0, b=2).
|
||||
const ROUTE_COLOR: u8 = 0b000_000_10;
|
||||
/// Color of the planned route: dim blue.
|
||||
const ROUTE_COLOR: u16 = pbio::rgb565(0, 0, 170);
|
||||
|
||||
/// Fill for tiles outside the tileset / world.
|
||||
const VOID_COLOR: u16 = pbio::rgb565(0, 0, 0);
|
||||
|
||||
/// Viewport geometry: 30×30 tiles of 8 px, top-left of the framebuffer.
|
||||
const VIEW_TILES: i32 = 30;
|
||||
@@ -50,8 +53,8 @@ struct EntityLerp {
|
||||
}
|
||||
|
||||
pub struct Game {
|
||||
tileset: Vec<[u8; 64]>,
|
||||
entity_tileset: Vec<[u8; 64]>,
|
||||
tileset: Vec<Tile>,
|
||||
entity_tileset: Vec<Tile>,
|
||||
sim: Sim,
|
||||
player_id: u32,
|
||||
/// Frame time not yet consumed by whole ticks.
|
||||
@@ -74,8 +77,8 @@ pub struct Game {
|
||||
|
||||
impl Game {
|
||||
pub fn start() -> Self {
|
||||
let tileset = Image::from_tga("assets/tilesets/overworld.tga").to_tileset();
|
||||
let entity_tileset = Image::from_tga("assets/tilesets/entities.tga").to_tileset();
|
||||
let tileset = Image::from_png("assets/tilesets/overworld.png").to_tileset();
|
||||
let entity_tileset = Image::from_png("assets/tilesets/entities.png").to_tileset();
|
||||
|
||||
let map = TileMap::from_csv("assets/map_test");
|
||||
println!("loaded map {}×{} ({} tiles)", map.width, map.height, map.tiles.len());
|
||||
@@ -109,7 +112,7 @@ impl Game {
|
||||
self.sim.world.tile_flags(wx as i16, wy as i16).map_or(true, |f| f.collidable())
|
||||
}
|
||||
|
||||
pub fn update(&mut self, render_frame: &mut [u8], dt: usize, input: &InputState)
|
||||
pub fn update(&mut self, render_frame: &mut [u16], dt: usize, input: &InputState)
|
||||
-> Option<GameSignal>
|
||||
{
|
||||
if input.mouse_clicked() || input.mouse_held() {
|
||||
@@ -315,8 +318,7 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_viewport(&self, frame: &mut [u8]) {
|
||||
const WALL: u8 = 0x00;
|
||||
fn render_viewport(&self, frame: &mut [u16]) {
|
||||
const W: usize = 320;
|
||||
|
||||
frame.fill(0);
|
||||
@@ -345,7 +347,7 @@ impl Game {
|
||||
} else {
|
||||
for dy in 0..8 {
|
||||
for dx in 0..8 {
|
||||
pixelhelper::set_pixel(frame, W, px + dx, py + dy, WALL);
|
||||
pixelhelper::set_pixel(frame, W, px + dx, py + dy, VOID_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,3 +385,30 @@ impl Game {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod snapshot {
|
||||
/// Renders the first frame headlessly to the PNG named by `$SNAPSHOT` — a way to
|
||||
/// eyeball the renderer without a window:
|
||||
/// `SNAPSHOT=/tmp/frame.png cargo test -p game snapshot -- --ignored`
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn dump_frame() {
|
||||
std::env::set_current_dir("..").unwrap();
|
||||
let mut g = super::Game::start();
|
||||
let input = crate::input::InputState::new();
|
||||
let mut frame = vec![0u16; 320 * 240];
|
||||
g.update(&mut frame, 0, &input);
|
||||
let out = std::env::var("SNAPSHOT").unwrap();
|
||||
let mut rgb = Vec::with_capacity(320 * 240 * 3);
|
||||
for p in &frame {
|
||||
rgb.push(((p >> 11) as u8) << 3);
|
||||
rgb.push((((p >> 5) & 63) as u8) << 2);
|
||||
rgb.push(((p & 31) as u8) << 3);
|
||||
}
|
||||
let f = std::fs::File::create(out).unwrap();
|
||||
let mut enc = png::Encoder::new(std::io::BufWriter::new(f), 320, 240);
|
||||
enc.set_color(png::ColorType::Rgb);
|
||||
enc.write_header().unwrap().write_image_data(&rgb).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user