From d65ca6fe97c05cddc514d8ece7459d3b0b5ede0d Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Thu, 19 Mar 2026 11:24:10 +0300 Subject: [PATCH] feat: Implemented crates functionality --- src/inventory/mod.rs | 2 +- src/inventory/ui.rs | 52 ++++++++++++++++++++++++++++---------- src/layout.rs | 60 ++++++++++++++++++++++++++++++++++++++++---- src/player.rs | 17 +++++++------ 4 files changed, 105 insertions(+), 26 deletions(-) diff --git a/src/inventory/mod.rs b/src/inventory/mod.rs index 20878e0..91fd924 100644 --- a/src/inventory/mod.rs +++ b/src/inventory/mod.rs @@ -3,7 +3,7 @@ use bevy::prelude::*; pub mod item; pub mod ui; -#[derive(Component, Reflect)] +#[derive(Component, Reflect, Default)] pub struct Inventory { pub size: UVec2, } diff --git a/src/inventory/ui.rs b/src/inventory/ui.rs index 013e910..bab72ca 100644 --- a/src/inventory/ui.rs +++ b/src/inventory/ui.rs @@ -2,7 +2,7 @@ use std::f32::consts::FRAC_PI_2; use bevy::{ecs::relationship::RelatedSpawner, prelude::*, ui_widgets::{ControlOrientation, CoreScrollbarThumb, Scrollbar}}; -use crate::{inventory::{ActiveInventory, Inventory, item::Item}, ui::{UiRoot, UiRotateEvent}}; +use crate::{inventory::{ActiveInventory, Inventory, item::Item}, player::Player, ui::{UiRoot, UiRotateEvent}}; const UI_SLOT_ASSET_PATH: &'static str = "sprites/ui/inventory_slot.png"; const TEMP_ITEM_PATH: &'static str = "sprites/items/choco_bar.png"; @@ -254,11 +254,13 @@ pub fn on_ui_rotate( } } -fn ui_manager_bundle(children: Vec) -> impl Bundle { +fn ui_manager_bundle(children: Vec, aligned_left: bool) -> impl Bundle { + let left = if aligned_left { Val::ZERO } else { percent(50.) }; ( UiInventoryManager, Node { position_type: PositionType::Absolute, + left, width: percent(50.), height: percent(100.), scrollbar_width: 8., @@ -353,6 +355,7 @@ fn inventory_slot_bundle(x: u32, y: u32, image: Handle) -> impl Bundle { should_block_lower: true, is_hoverable: true, }, + GlobalZIndex(1), BackgroundColor::DEFAULT, Name::new(format!("UiInventorySlot({x},{y})")), ) @@ -376,7 +379,7 @@ fn ui_item_bundle(item: &Item, item_entity: Entity, image: Handle) -> imp }, BackgroundColor(Color::hsla(0., 0., 0., 0.5)), ui_transform, - GlobalZIndex(1), + GlobalZIndex(2), Pickable { should_block_lower: false, is_hoverable: true, @@ -391,7 +394,9 @@ fn ui_item_bundle(item: &Item, item_entity: Entity, image: Handle) -> imp pub fn setup_ui_inventory( mut commands: Commands, asset_server: Res, - inventory_query: Query<(Entity, &Inventory, Option<&Children>), With>, + inventory_query: Query<(Option<&ChildOf>, Entity, &Inventory, Option<&Children>)>, + player_query: Query<(), With>, + active_inventory_query: Query>, item_query: Query<&Item>, root_query: Query>, ) { @@ -401,17 +406,30 @@ pub fn setup_ui_inventory( }; let ui_slot_image: Handle = asset_server.load(UI_SLOT_ASSET_PATH); let temp_item_image: Handle = asset_server.load(TEMP_ITEM_PATH); - let mut inventory_ids = Vec::new(); - for (inventory_entity, inventory, children) in inventory_query.iter().sort::() { + let (mut player_inventory_ids, mut active_inventory_ids) = (Vec::new(), Vec::new()); + for (inventory_parent, inventory_entity, inventory, children) in inventory_query.iter().sort::() { + let is_player = match inventory_parent { + Some(parent) => { + if player_query.get(parent.0) + .is_ok() { true } + else { + if active_inventory_query.get(inventory_entity) + .is_ok() { false } + else { continue; } + } + }, + None => { + if active_inventory_query.get(inventory_entity) + .is_ok() { false } + else { continue; } + }, + }; let items = match children { Some(children) => { children.iter().filter_map(|item_entity| { match item_query.get(item_entity) { Ok(item) => Some((item, item_entity)), - Err(err) => { - warn!("Error querying item {item_entity}: {err}"); - None - }, + Err(_) => None, } }).collect::>() } @@ -437,10 +455,18 @@ pub fn setup_ui_inventory( } } } }).id(); - inventory_ids.push(inventory_id); + if is_player { + player_inventory_ids.push(inventory_id); + } else { + active_inventory_ids.push(inventory_id); + } } - let inventory_manager = commands.spawn(ui_manager_bundle(inventory_ids)).id(); - commands.entity(root).add_child(inventory_manager); + let player_inventory_manager = commands.spawn(ui_manager_bundle(player_inventory_ids, true)).id(); + let active_inventory_manager = commands.spawn(ui_manager_bundle(active_inventory_ids, false)).id(); + for entity in active_inventory_query { + commands.entity(entity).remove::(); + } + commands.entity(root).add_children(&[player_inventory_manager, active_inventory_manager]); } pub fn clear_ui_inventory( diff --git a/src/layout.rs b/src/layout.rs index 447bf76..9f021f9 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,10 +1,11 @@ use bevy::{ecs::query::QueryFilter, prelude::*}; use bevy_rapier2d::prelude::*; -use crate::{PIXELS_PER_METER, player::Player}; +use crate::{GameState, PIXELS_PER_METER, inventory::{ActiveInventory, Inventory, item::Item}, player::Player}; const DOOR_OPENED_ASSET: &'static str = "sprites/interactive/door_opened.png"; const DOOR_CLOSED_ASSET: &'static str = "sprites/interactive/door_closed.png"; +const CRATE_CLOSED_ASSET: &'static str = "sprites/interactive/crate_closed.png"; #[derive(Component)] pub struct MayInteract; @@ -19,6 +20,10 @@ pub struct Wall; #[require(Sprite, InteractiveObject)] pub struct Door(pub i8); +#[derive(Component)] +#[require(Sprite, InteractiveObject, Inventory)] +pub struct Crate; + #[derive(Component)] pub struct Locked; @@ -76,6 +81,42 @@ pub fn detect_interact_collisions( } } +fn on_crate_interact( + event: On, + mut commands: Commands, + locked_query: Query<(), With>, + crate_query: Query<(), With>, + mut next_state: ResMut>, +) { + if locked_query.get(event.entity).is_ok() { + return; + } + if crate_query.get(event.entity).is_err() { + return; + } + + commands.entity(event.entity).insert(ActiveInventory); + next_state.set(GameState::Inventory); +} + +fn crate_bundle(image: Handle, position: Vec2, inventory_size: UVec2, items: Vec) -> impl Bundle { + ( + Crate, + Transform::from_xyz(position.x, position.y - PIXELS_PER_METER * 0.5, 0.), + Sprite::from_image(image), + Inventory::new(inventory_size), + Observer::new(on_crate_interact), + Children::spawn(( + SpawnIter(items.into_iter()), + Spawn(( + Collider::cuboid(PIXELS_PER_METER, PIXELS_PER_METER), + Sensor, + Transform::from_xyz(0., PIXELS_PER_METER * 0.5, 0.), + )), + )), + ) +} + fn on_door_interact( event: On, mut commands: Commands, @@ -92,17 +133,15 @@ fn on_door_interact( let was_opened = if collider_query.get(event.entity).is_ok() { false } else if no_collider_query.get(event.entity).is_ok() { true } else { - error!("on_door_interact fired but entity {} isn't door", event.entity); return; }; let Ok((door, children)) = door_query.get(event.entity) else { - error!("on_door_interact fired but entity {} has no children", event.entity); return; }; for child in children { if let Ok((mut sprite, mut transform)) = sprite_query.get_mut(*child) { let (image, translation) = if was_opened { (DOOR_CLOSED_ASSET, 0.) } - else { (DOOR_OPENED_ASSET, door.0 as f32 * 8.) }; + else { (DOOR_OPENED_ASSET, door.0 as f32 * PIXELS_PER_METER * 0.5) }; sprite.image = asset_server.load(image); transform.translation.x = translation; break; @@ -159,9 +198,20 @@ pub fn setup_world( mut commands: Commands, asset_server: Res, ) { + let items = vec![ + Item::new_positioned(uvec2(1, 1), uvec2(0, 0)), + Item::new_positioned(uvec2(2, 1), uvec2(1, 0)), + Item::new_positioned(uvec2(3, 1), uvec2(3, 0)), + Item::new_positioned(uvec2(2, 2), uvec2(6, 0)), + Item::new_positioned(uvec2(6, 2), uvec2(0, 1)), + Item::new_positioned(uvec2(2, 3), uvec2(6, 2)), + Item::new_positioned(uvec2(4, 4), uvec2(0, 4)), + ]; let door_image = asset_server.load(DOOR_CLOSED_ASSET); + let crate_image = asset_server.load(CRATE_CLOSED_ASSET); commands.spawn(door_bundle(door_image.clone(), vec2(16., 0.), true)); commands.spawn(door_bundle(door_image.clone(), vec2(48., 0.), false)); commands.spawn(door_bundle(door_image.clone(), vec2(80., 0.), false)).insert(Locked); - commands.spawn(wall_bundle(vec2(-16., 0.))); + commands.spawn(wall_bundle(vec2(-48., 0.))); + commands.spawn(crate_bundle(crate_image.clone(), vec2(-32., 0.), uvec2(8, 8), items)); } diff --git a/src/player.rs b/src/player.rs index a10cdd0..0bd3f7c 100644 --- a/src/player.rs +++ b/src/player.rs @@ -2,7 +2,7 @@ use bevy::prelude::*; use bevy_rapier2d::prelude::*; use leafwing_input_manager::prelude::*; -use crate::{GameState, InputAction as Action, PIXELS_PER_METER, inventory::{ActiveInventory, Inventory, item::Item}, layout::{InteractionEvent, MayInteract}}; +use crate::{GameState, InputAction as Action, PIXELS_PER_METER, inventory::{Inventory, item::Item}, layout::{InteractionEvent, MayInteract}}; #[derive(Component, Reflect)] pub struct Player { @@ -14,7 +14,7 @@ fn player_bundle(asset_server: &Res) -> impl Bundle { let image = asset_server.load("sprites/player/player.png"); ( Player { - speed: 51.2, + speed: PIXELS_PER_METER * 0.8, }, Sprite::from_image(image), Transform::from_xyz(0f32, 0f32, 1f32), @@ -27,9 +27,9 @@ fn player_bundle(asset_server: &Res) -> impl Bundle { ActiveEvents::COLLISION_EVENTS, Sleeping::disabled(), children![ - (Inventory::new(UVec2::new(6, 2)), ActiveInventory), - (Inventory::new(UVec2::new(5, 3)), ActiveInventory), - (Inventory::new(UVec2::new(4, 4)), ActiveInventory), + Inventory::new(UVec2::new(6, 2)), + Inventory::new(UVec2::new(5, 3)), + Inventory::new(UVec2::new(4, 4)), ], ) } @@ -90,7 +90,7 @@ pub fn handle_input( sprite.flip_x = direction < 0f32; } - if action_state.just_pressed(&Action::Interact) { + if action_state.just_released(&Action::Interact) { let mut action_happened = false; for interactable_id in interactables { commands.trigger(InteractionEvent { entity: interactable_id }); @@ -99,13 +99,16 @@ pub fn handle_input( if !action_happened { commands.run_system_cached(try_insert_item); } + action_state.reset(&Action::Interact); } }, GameState::Inventory => { let (_, mut action_state, _, _) = player; - if action_state.just_released(&Action::ToggleInventory) { + if action_state.just_released(&Action::ToggleInventory) + || action_state.just_released(&Action::Interact) { next_state.set(GameState::Running); action_state.reset(&Action::ToggleInventory); + action_state.reset(&Action::Interact); } }, }