From a272aa9d9b2c642871abe2dc3379e6ffcce8bf56 Mon Sep 17 00:00:00 2001 From: Kai Stevenson Date: Wed, 26 Aug 2026 23:54:09 -0700 Subject: sprite loader --- src/content/entities/entity_tnt.rs | 44 +++++++++++++++++++++ src/content/entities/mod.rs | 1 + src/content/materials/mod.rs | 21 +++++++--- src/input.rs | 6 ++- src/main.rs | 7 +++- src/sprite_loader.rs | 79 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 src/content/entities/entity_tnt.rs create mode 100644 src/sprite_loader.rs (limited to 'src') diff --git a/src/content/entities/entity_tnt.rs b/src/content/entities/entity_tnt.rs new file mode 100644 index 0000000..2e4cef5 --- /dev/null +++ b/src/content/entities/entity_tnt.rs @@ -0,0 +1,44 @@ +use glam::{IVec2, Vec2}; + +use crate::{ + sim::{ + entity::{EntityBehaviour, EntityCells, EntityDef, EntityUpdateCtx}, + lib::force::apply_explosion, + sim_manager::SimCtx, + }, + sprite_loader::load_sprite_to_cells, +}; + +struct TntEntityBehaviour { + pub fuse: f32, +} + +impl EntityBehaviour for TntEntityBehaviour { + fn update(&mut self, update_ctx: &mut EntityUpdateCtx, ctx: &mut SimCtx, delta_time: f32) { + self.fuse -= delta_time; + if self.fuse <= 0.0 { + apply_explosion( + ctx, + update_ctx.entity_data.transform(ctx).unwrap().0, + 35, + Vec2::new(0.0, -0.6), + 400.0, + ); + update_ctx.deferred_destroy(update_ctx.entity_data.id); + } + } +} + +pub fn entity_tnt_def(position: Vec2) -> EntityDef { + let sprite_cells = load_sprite_to_cells("assets/sprites/tnt"); + let entity_cells = EntityCells { + cells: sprite_cells.cells, + size: IVec2::new(sprite_cells.width as i32, sprite_cells.height as i32), + }; + + EntityDef::from_cells( + position, + entity_cells, + Some(Box::new(TntEntityBehaviour { fuse: 3.0 })), + ) +} diff --git a/src/content/entities/mod.rs b/src/content/entities/mod.rs index 652ee6d..0ec45ce 100644 --- a/src/content/entities/mod.rs +++ b/src/content/entities/mod.rs @@ -1,3 +1,4 @@ pub mod entity_bullet_emitter; pub mod entity_cube; pub mod entity_grenade; +pub mod entity_tnt; diff --git a/src/content/materials/mod.rs b/src/content/materials/mod.rs index 8116565..819ec2a 100644 --- a/src/content/materials/mod.rs +++ b/src/content/materials/mod.rs @@ -1,3 +1,5 @@ +use serde::Deserialize; + use crate::sim::cell_manager::sim::{PostUpdateAction, UpdateCtx}; pub mod fire; @@ -6,7 +8,7 @@ pub mod liquid; pub mod powder; #[repr(u8)] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize)] pub enum MaterialId { Void = 0, Sand, @@ -15,6 +17,7 @@ pub enum MaterialId { Fire, Smoke, Steel, + Tnt, } #[repr(u8)] @@ -35,7 +38,7 @@ pub struct MaterialDef { pub sim_update: Option PostUpdateAction>, } -static MATERIALS: [MaterialDef; 7] = [ +static MATERIALS: [MaterialDef; 8] = [ MaterialDef { name: "Void", color: (0x00, 0x00, 0x00, 0x00), @@ -53,8 +56,8 @@ static MATERIALS: [MaterialDef; 7] = [ }, MaterialDef { name: "Wood", - color: (0x85, 0x56, 0x1D, 0xFF), - density: 50, + color: (0x66, 0x39, 0x31, 0xFF), + density: 60, form: MaterialForm::Solid, sim_update: None, }, @@ -88,10 +91,17 @@ static MATERIALS: [MaterialDef; 7] = [ form: MaterialForm::Solid, sim_update: None, }, + MaterialDef { + name: "TNT", + color: (0xA6, 0x34, 0x23, 0xFF), + density: 55, + form: MaterialForm::Solid, + sim_update: None, + }, ]; impl MaterialId { - pub const ALL: [MaterialId; 7] = [ + pub const ALL: [MaterialId; 8] = [ MaterialId::Void, MaterialId::Sand, MaterialId::Wood, @@ -99,6 +109,7 @@ impl MaterialId { MaterialId::Fire, MaterialId::Smoke, MaterialId::Steel, + MaterialId::Tnt, ]; #[inline] pub fn def(self) -> &'static MaterialDef { diff --git a/src/input.rs b/src/input.rs index 09a0436..30f3834 100644 --- a/src/input.rs +++ b/src/input.rs @@ -23,6 +23,7 @@ pub enum Input { // game controls? Grenade, + Tnt, // placeholder actions Action1, @@ -33,9 +34,9 @@ pub enum Input { Action6, } -const INPUT_VAR_LEN: usize = 16; +const INPUT_VAR_LEN: usize = 17; -const KEYMAP: [(KeyCode, Input); 16] = [ +const KEYMAP: [(KeyCode, Input); INPUT_VAR_LEN] = [ (KeyCode::KeyW, Input::Up), (KeyCode::KeyA, Input::Left), (KeyCode::KeyS, Input::Down), @@ -46,6 +47,7 @@ const KEYMAP: [(KeyCode, Input); 16] = [ (KeyCode::KeyV, Input::ClearEntities), (KeyCode::KeyP, Input::ClearParticles), (KeyCode::KeyG, Input::Grenade), + (KeyCode::KeyT, Input::Tnt), (KeyCode::Digit1, Input::Action1), (KeyCode::Digit2, Input::Action2), (KeyCode::Digit3, Input::Action3), diff --git a/src/main.rs b/src/main.rs index 6e6c9d9..00f2cbc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod content; mod input; mod renderer; mod sim; +mod sprite_loader; mod vfx; use futures::executor; @@ -21,7 +22,7 @@ use crate::{ content::{ entities::{ entity_bullet_emitter::entity_bullet_emitter_def, entity_cube::entity_cube_def, - entity_grenade::entity_grenade_def, + entity_grenade::entity_grenade_def, entity_tnt::entity_tnt_def, }, materials::MaterialId, }, @@ -95,6 +96,10 @@ impl App { sim.create_entity(entity_grenade_def(self.input_manager.world_mouse_pos, 3.0)); } + if self.input_manager.pressed(Input::Tnt) { + sim.create_entity(entity_tnt_def(self.input_manager.world_mouse_pos)); + } + if self.input_manager.pressed(Input::Action3) { sim.create_entity(entity_bullet_emitter_def( self.input_manager.world_mouse_pos, diff --git a/src/sprite_loader.rs b/src/sprite_loader.rs new file mode 100644 index 0000000..eccf066 --- /dev/null +++ b/src/sprite_loader.rs @@ -0,0 +1,79 @@ +use serde::Deserialize; +use std::{collections::HashMap, error::Error, fs::File, io::BufReader, path::Path}; + +use crate::{content::materials::MaterialId, sim::cell::Cell}; + +fn load_img(path: &Path) -> Result<(Vec, u32, u32), Box> { + let mut decoder = png::Decoder::new(BufReader::new(File::open(path)?)); + decoder.set_transformations(png::Transformations::IDENTITY); + + let mut reader = decoder.read_info()?; + // only None if it's too big for memory + let mut buf = vec![0; reader.output_buffer_size().unwrap()]; + let info = reader.next_frame(&mut buf)?; + + if info.color_type != png::ColorType::Indexed { + return Err(format!("{:#?}: not indexed", path.to_str()).into()); + } + if info.bit_depth != png::BitDepth::Eight { + return Err(format!( + "{:#?}: expected 8-bit, got {:?}", + path.to_str(), + info.bit_depth + ) + .into()); + } + + buf.truncate(info.buffer_size()); + Ok((buf, info.width, info.height)) +} + +#[derive(Deserialize)] +struct PaletteEntry { + material: MaterialId, +} + +#[derive(Deserialize)] +struct SpriteManifest { + palette: HashMap, +} + +fn load_manifest(path: &Path) -> Result> { + Ok(toml::from_str(&std::fs::read_to_string(path)?)?) +} + +pub struct SpriteCells { + pub cells: Vec, + pub width: u32, + pub height: u32, +} + +pub fn load_sprite_to_cells(path: &str) -> SpriteCells { + let img_result = load_img(&Path::join(Path::new(path), Path::new("img.png"))); + match img_result { + Ok((indices, width, height)) => { + let manifest_result = + load_manifest(&Path::join(Path::new(path), Path::new("manifest.toml"))); + match manifest_result { + Ok(SpriteManifest { palette }) => { + let cells: Vec = indices + .iter() + .map(|i| Cell::from_material(palette.get(i).unwrap().material)) + .collect(); + + SpriteCells { + cells, + width, + height, + } + } + Err(e) => { + panic!("Couldn't load sprite: {}", e) + } + } + } + Err(e) => { + panic!("Couldn't load sprite: {}", e) + } + } +} -- cgit v1.3.1