diff options
| author | Kai Stevenson <kai@kaistevenson.com> | 2026-08-26 23:54:09 -0700 |
|---|---|---|
| committer | Kai Stevenson <kai@kaistevenson.com> | 2026-08-26 23:54:09 -0700 |
| commit | a272aa9d9b2c642871abe2dc3379e6ffcce8bf56 (patch) | |
| tree | 0d9bc97a916e7df97281cc4d81cc0c292540517c | |
| parent | 893502300c0206cf629ff4e9ed5b9327743ebd30 (diff) | |
sprite loader
| -rw-r--r-- | Cargo.lock | 33 | ||||
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | assets/sprites/tnt/img.png | bin | 0 -> 299 bytes | |||
| -rw-r--r-- | assets/sprites/tnt/manifest.toml | 3 | ||||
| -rw-r--r-- | src/content/entities/entity_tnt.rs | 44 | ||||
| -rw-r--r-- | src/content/entities/mod.rs | 1 | ||||
| -rw-r--r-- | src/content/materials/mod.rs | 21 | ||||
| -rw-r--r-- | src/input.rs | 6 | ||||
| -rw-r--r-- | src/main.rs | 7 | ||||
| -rw-r--r-- | src/sprite_loader.rs | 79 |
10 files changed, 189 insertions, 8 deletions
@@ -2448,11 +2448,14 @@ dependencies = [ "futures", "fxhash", "glam 0.33.3", + "png", "puffin", "puffin_http", "rand", "rapier2d", "rayon", + "serde", + "toml", "wgpu", "wgpu-types", "winit", @@ -2800,6 +2803,15 @@ dependencies = [ ] [[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3113,6 +3125,21 @@ dependencies = [ ] [[package]] +name = "toml" +version = "1.1.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3143,6 +3170,12 @@ dependencies = [ ] [[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] name = "tracing" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -33,6 +33,9 @@ bytemuck = "1.25.2" fxhash = "0.2.1" rapier2d = { version = "0.35.2", features = ["debug-render"] } glam = "0.33.3" +png = "0.18.1" +toml = "1.1.4" +serde = "1.0.229" [features] profiler = [] diff --git a/assets/sprites/tnt/img.png b/assets/sprites/tnt/img.png Binary files differnew file mode 100644 index 0000000..b5fafac --- /dev/null +++ b/assets/sprites/tnt/img.png diff --git a/assets/sprites/tnt/manifest.toml b/assets/sprites/tnt/manifest.toml new file mode 100644 index 0000000..242756c --- /dev/null +++ b/assets/sprites/tnt/manifest.toml @@ -0,0 +1,3 @@ +[palette] +3 = { material = "Wood" } +27 = { material = "Tnt" }
\ No newline at end of file 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<fn(ctx: &mut UpdateCtx) -> 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<u8>, u32, u32), Box<dyn Error>> { + 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<u8, PaletteEntry>, +} + +fn load_manifest(path: &Path) -> Result<SpriteManifest, Box<dyn Error>> { + Ok(toml::from_str(&std::fs::read_to_string(path)?)?) +} + +pub struct SpriteCells { + pub cells: Vec<Cell>, + 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<Cell> = 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) + } + } +} |
