diff --git a/assets/sprites/interactive/container.png b/assets/sprites/interactive/container.png new file mode 100644 index 0000000..651e42c Binary files /dev/null and b/assets/sprites/interactive/container.png differ diff --git a/assets/sprites/interactive/crate_closed.png b/assets/sprites/interactive/crate_closed.png deleted file mode 100644 index ca3e7d0..0000000 Binary files a/assets/sprites/interactive/crate_closed.png and /dev/null differ diff --git a/assets/sprites/interactive/crate_opened.png b/assets/sprites/interactive/crate_opened.png deleted file mode 100644 index 50d72f1..0000000 Binary files a/assets/sprites/interactive/crate_opened.png and /dev/null differ diff --git a/assets/sprites/interactive/door_closed.png b/assets/sprites/interactive/door_closed.png index 40c3693..49c336b 100644 Binary files a/assets/sprites/interactive/door_closed.png and b/assets/sprites/interactive/door_closed.png differ diff --git a/assets/sprites/interactive/door_opened.png b/assets/sprites/interactive/door_opened.png index 694e25e..35a6888 100644 Binary files a/assets/sprites/interactive/door_opened.png and b/assets/sprites/interactive/door_opened.png differ diff --git a/assets/sprites/interactive/padlock.png b/assets/sprites/interactive/padlock.png index 3f5d639..b1d92f0 100644 Binary files a/assets/sprites/interactive/padlock.png and b/assets/sprites/interactive/padlock.png differ diff --git a/assets/sprites/interactive/stairs.png b/assets/sprites/interactive/stairs.png index 9c8e23a..3915c93 100644 Binary files a/assets/sprites/interactive/stairs.png and b/assets/sprites/interactive/stairs.png differ diff --git a/assets/sprites/interactive/wardrobe.png b/assets/sprites/interactive/wardrobe.png deleted file mode 100644 index 570f147..0000000 Binary files a/assets/sprites/interactive/wardrobe.png and /dev/null differ diff --git a/assets/sprites/level/ceiling.png b/assets/sprites/level/ceiling.png deleted file mode 100644 index bdc1d97..0000000 Binary files a/assets/sprites/level/ceiling.png and /dev/null differ diff --git a/assets/sprites/level/floor.png b/assets/sprites/level/floor.png deleted file mode 100644 index c1e25b6..0000000 Binary files a/assets/sprites/level/floor.png and /dev/null differ diff --git a/assets/sprites/interactive/lamp.png b/assets/sprites/level/lamp.png similarity index 100% rename from assets/sprites/interactive/lamp.png rename to assets/sprites/level/lamp.png diff --git a/assets/sprites/level/wall.png b/assets/sprites/level/wall.png deleted file mode 100644 index a103fb5..0000000 Binary files a/assets/sprites/level/wall.png and /dev/null differ diff --git a/assets/sprites/level/wall_connector.png b/assets/sprites/level/wall_connector.png deleted file mode 100644 index a9be497..0000000 Binary files a/assets/sprites/level/wall_connector.png and /dev/null differ diff --git a/assets/sprites/level/window.png b/assets/sprites/level/window.png deleted file mode 100644 index 4e81bd1..0000000 Binary files a/assets/sprites/level/window.png and /dev/null differ diff --git a/src/layout/asset/mod.rs b/src/layout/asset/mod.rs index 4a355a2..09fb80a 100644 --- a/src/layout/asset/mod.rs +++ b/src/layout/asset/mod.rs @@ -24,6 +24,7 @@ pub fn load_level ( level_handle: Res, mut commands: Commands, asset_server: Res, + textures: Res, level_assets: Res>, ) { let Some(level) = level_assets.get(&level_handle.0) else { @@ -61,7 +62,7 @@ pub fn load_level ( for DoorData { pos, facing_left, lock } in level.interactive.doors.iter() { let door_pos = vec2(meters(pos.x + 0.5), meters(pos.y)); - let mut door = parent.spawn(door_bundle(&asset_server, door_pos, *facing_left)); + let mut door = parent.spawn(door_bundle(&textures, door_pos, *facing_left)); if let Some(lock_facing_left) = lock { door.with_child(padlock_bundle(&asset_server, *lock_facing_left)); } @@ -87,7 +88,7 @@ pub fn load_level ( for ContainerData { pos, size, items } in level.interactive.containers.iter() { let pos = vec2(meters(pos.x), meters(pos.y)); - let mut container = parent.spawn(container_bundle(&asset_server, pos, *size)); + let mut container = parent.spawn(container_bundle(&textures, pos, *size)); for item in items { // TODO: replace with proper item-by-id system diff --git a/src/layout/container.rs b/src/layout/container.rs index 62f5f0c..e334a02 100644 --- a/src/layout/container.rs +++ b/src/layout/container.rs @@ -12,8 +12,6 @@ use crate::{ use super::*; -const CRATE_CLOSED_ASSET: &'static str = "sprites/interactive/crate_closed.png"; - #[derive(Component, Clone, Copy, Default, Reflect, Debug, PartialEq, Eq)] #[reflect(Component, Clone, Default, Debug, PartialEq)] #[require(Sprite, InteractiveObject, Inventory)] @@ -38,15 +36,26 @@ pub fn on_container_interact( } pub fn container_bundle( - asset_server: &Res, + textures: &Res, position: Vec2, inventory_size: UVec2, ) -> impl Bundle { - let image = asset_server.load(CRATE_CLOSED_ASSET); + let texture_atlas = TextureAtlas { + layout: textures.container.atlas.clone(), + index: textures.container.indices["main"], + }; + let hl_texture_atlas = texture_atlas.clone().with_index(textures.container.indices["highlighted"]); + let sprite = Sprite { + image: textures.container.image.clone(), + texture_atlas: Some(texture_atlas), + ..default() + }; + let mut highlight_sprite = sprite.clone(); + highlight_sprite.texture_atlas = Some(hl_texture_atlas); ( Container, Transform::from_xyz(position.x, position.y - meters(0.5), 0.), - Sprite::from_image(image), + sprite, Inventory::new(inventory_size), Children::spawn(( Spawn(( @@ -54,6 +63,11 @@ pub fn container_bundle( Sensor, Transform::from_xyz(0., meters(0.5), 0.), )), + Spawn(( + highlight_sprite, + Transform::from_xyz(0., 0., 1.), + Visibility::Hidden, + )), )), Name::new(format!("Container {}x{}", inventory_size.x, inventory_size.y)), ) diff --git a/src/layout/door.rs b/src/layout/door.rs index 376969a..ba10e0e 100644 --- a/src/layout/door.rs +++ b/src/layout/door.rs @@ -6,12 +6,9 @@ use crate::meters; use super::*; -const DOOR_OPENED_ASSET: &'static str = "sprites/interactive/door_opened.png"; -const DOOR_CLOSED_ASSET: &'static str = "sprites/interactive/door_closed.png"; - #[derive(Component, Clone, Copy, Reflect, PartialEq, Eq, Debug)] #[reflect(Component, Clone, Default, PartialEq, Debug)] -#[require(Sprite, InteractiveObject)] +#[require(InteractiveObject)] pub struct Door(pub i8); #[derive(Component, Clone, Copy, Reflect, Default, PartialEq, Eq, Debug)] @@ -19,20 +16,33 @@ pub struct Door(pub i8); #[require(Collider, LightOccluder2d)] pub struct DoorCollider; +#[derive(Component, Clone, Copy, Reflect, Default, PartialEq, Eq, Debug)] +#[reflect(Component, Clone, Default, PartialEq, Debug)] +#[require(Sprite)] +pub struct DoorHighlight; + impl Default for Door { fn default() -> Self { Self(1) } } +impl Door { + #[inline(always)] + pub fn is_facing_left(&self) -> bool { + self.0 < 0 + } +} + pub fn on_door_interact( event: On, mut commands: Commands, locked_query: Query<(), With>, door_query: Query<(&Door, &Children)>, door_collider_query: Query>, - mut sprite_query: Query<(&mut Sprite, &mut Transform)>, - asset_server: Res, + mut sprite_query: Query<&mut Transform, With>, + highlight_query: Query<(), With>, + textures: Res, ) { if locked_query.get(event.entity).is_ok() { return; @@ -43,13 +53,18 @@ pub fn on_door_interact( let maybe_door_collider = children.iter() .find_map(|id| if let Ok(id) = door_collider_query.get(id) { Some(id) } else { None } ); for child in children { - if let Ok((mut sprite, mut transform)) = sprite_query.get_mut(*child) { - let (image, translation) = if maybe_door_collider.is_none() { (DOOR_CLOSED_ASSET, 0.) } - else { (DOOR_OPENED_ASSET, door.0 as f32 * meters(0.5)) }; - sprite.image = asset_server.load(image); - transform.translation.x = translation; - break; + match sprite_query.get_mut(*child) { + Ok(mut transform) => { + let translation = if maybe_door_collider.is_none() { 0. } + else { door.0 as f32 * meters(0.5) }; + transform.translation.x = translation; + }, + Err(_) => continue, } + let texture = if maybe_door_collider.is_none() { &textures.door_closed } else { &textures.door_opened }; + let (sprite, highlight_sprite) = door_sprites(texture, door.is_facing_left()); + let needed_sprite = if highlight_query.get(*child).is_err() { sprite } else { highlight_sprite }; + commands.entity(*child).insert(needed_sprite); } if let Some(id) = maybe_door_collider { @@ -68,9 +83,26 @@ pub fn door_collider_bundle() -> impl Bundle { ) } -pub fn door_bundle(asset_server: &Res, position: Vec2, facing_left: bool) -> impl Bundle { +fn door_sprites(texture: &AtlasLayoutTexture, facing_left: bool) -> (Sprite, Sprite) { + let texture_atlas = TextureAtlas { + layout: texture.atlas.clone(), + index: texture.indices["main"], + }; + let hl_texture_atlas = texture_atlas.clone().with_index(texture.indices["highlighted"]); + let sprite = Sprite { + image: texture.image.clone(), + flip_x: facing_left, + texture_atlas: Some(texture_atlas), + ..default() + }; + let mut highlight_sprite = sprite.clone(); + highlight_sprite.texture_atlas = Some(hl_texture_atlas); + (sprite, highlight_sprite) +} + +pub fn door_bundle(textures: &Res, position: Vec2, facing_left: bool) -> impl Bundle { let direction = if facing_left { -1 } else { 1 }; - let image = asset_server.load(DOOR_CLOSED_ASSET); + let (sprite, highlight_sprite) = door_sprites(&textures.door_closed, facing_left); ( Door(direction), Transform::from_xyz(position.x, position.y, 0.), @@ -79,17 +111,18 @@ pub fn door_bundle(asset_server: &Res, position: Vec2, facing_left: door_collider_bundle(), ( Collider::cuboid(meters(0.5), meters(1.)), + Transform::default(), Sensor, - Transform::from_xyz(0., 0., 0.), ), ( - Sprite { - image, - flip_x: facing_left, - ..default() - }, - Transform::from_xyz(0., 0., 0.), + sprite, ), + ( + highlight_sprite, + Transform::from_xyz(0., 0., 1.), + DoorHighlight, + Visibility::Hidden, + ) ], ) } diff --git a/src/layout/light.rs b/src/layout/light.rs index 9516dbd..a2fa077 100644 --- a/src/layout/light.rs +++ b/src/layout/light.rs @@ -3,7 +3,7 @@ use bevy_light_2d::prelude::*; use crate::meters; -const LAMP_IMAGE_PATH: &'static str = "sprites/interactive/lamp.png"; +const LAMP_IMAGE_PATH: &'static str = "sprites/level/lamp.png"; #[derive(Component, Clone, Copy, Default, Reflect, Debug, PartialEq, Eq)] #[reflect(Component, Clone, Default, Debug, PartialEq)] diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 645e79b..f2cd0a5 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use bevy::prelude::*; pub mod asset; @@ -35,3 +37,38 @@ pub struct LevelAssetHandle(Handle); pub struct InteractionEvent { pub entity: Entity, } + +#[derive(Reflect, Clone, PartialEq, Eq, Debug, Deref, DerefMut, Default)] +#[reflect(Clone, PartialEq, Debug, Default)] +pub struct SingleLayoutTexture(Handle); + +#[derive(Reflect, Clone, PartialEq, Eq, Debug, Default)] +#[reflect(Clone, PartialEq, Debug, Default)] +pub struct AtlasLayoutTexture { + image: Handle, + atlas: Handle, + indices: HashMap, +} + +impl AtlasLayoutTexture { + #[inline(always)] + pub fn new( + image: Handle, + atlas: Handle, + indices: HashMap, + ) -> Self { + Self { image, atlas, indices } + } +} + +#[derive(Resource, Reflect, Clone, PartialEq, Eq, Debug, Default)] +#[reflect(Resource, Clone, PartialEq, Debug, Default)] +pub struct LayoutTextures { + pub container: AtlasLayoutTexture, + pub door_closed: AtlasLayoutTexture, + pub door_opened: AtlasLayoutTexture, + pub light: SingleLayoutTexture, + pub lock: AtlasLayoutTexture, + pub stairs: AtlasLayoutTexture, + pub tilemap: SingleLayoutTexture, +} diff --git a/src/layout/systems.rs b/src/layout/systems.rs index ae27b14..f61b6e8 100644 --- a/src/layout/systems.rs +++ b/src/layout/systems.rs @@ -91,3 +91,50 @@ pub fn setup_world( let level_handle = asset_server.load(startup_args.level.clone()); commands.insert_resource(LevelAssetHandle(level_handle)); } + +fn double_atlas( + atlases: &mut ResMut>, + indices: &mut HashMap, + single_size: UVec2 +) -> Handle { + let mut atlas = TextureAtlasLayout::new_empty(uvec2(single_size.x * 2, single_size.y)); + indices.insert( + "main".to_owned(), + atlas.add_texture(URect::from_corners( + uvec2(0, 0), + uvec2(single_size.x, single_size.y) + )), + ); + indices.insert( + "highlighted".to_owned(), + atlas.add_texture(URect::from_corners( + uvec2(single_size.x, 0), + uvec2(single_size.x * 2, single_size.y) + )), + ); + atlases.add(atlas) +} + +pub fn load_layout_textures( + asset_server: Res, + mut atlases: ResMut>, + mut textures: ResMut, +) { + // Container + let image = asset_server.load("sprites/interactive/container.png"); + let mut indices = HashMap::new(); + let atlas = double_atlas(&mut atlases, &mut indices, uvec2(32, 16)); + textures.container = AtlasLayoutTexture::new(image, atlas, indices); + + // Closed door + let image = asset_server.load("sprites/interactive/door_closed.png"); + let mut indices = HashMap::new(); + let atlas = double_atlas(&mut atlases, &mut indices, uvec2(4, 32)); + textures.door_closed = AtlasLayoutTexture::new(image, atlas, indices); + + // Opened door + let image = asset_server.load("sprites/interactive/door_opened.png"); + let mut indices = HashMap::new(); + let atlas = double_atlas(&mut atlases, &mut indices, uvec2(16, 32)); + textures.door_opened = AtlasLayoutTexture::new(image, atlas, indices); +} diff --git a/src/lib.rs b/src/lib.rs index b4be247..6a008ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,9 +110,11 @@ impl Plugin for ExpeditionPlugin { .init_state::() .init_state::() .insert_resource(ui::WindowSize::default()) + .insert_resource(layout::LayoutTextures::default()) .add_systems(Startup, ( setup_global, layout::systems::setup_world, + layout::systems::load_layout_textures, )) .add_systems(Update, ( insert_entity_name, diff --git a/src/tests/level.rs b/src/tests/level.rs index f01b118..001678d 100644 --- a/src/tests/level.rs +++ b/src/tests/level.rs @@ -7,8 +7,8 @@ use crate::layout::asset::structs::LevelAsset; fn deserialize_from_str() { let level_str = include_str!("../../assets/levels/level.toml"); let level_alt_str = include_str!("../../assets/levels/level_alt.toml"); - let level = toml::de::from_str::(level_str).unwrap(); - let level_alt = toml::de::from_str::(level_alt_str).unwrap(); + let _ = toml::de::from_str::(level_str).unwrap(); + let _ = toml::de::from_str::(level_alt_str).unwrap(); } #[test]