feat!: Item processing

- Added try_insert_item system
- Added rotated field on Item
- Implemented UI item drawing with scale and rotation

BREAKING CHANGE: Item.swap_size renamed to Item.rotate
    Item.clone_swapped renamed to Item.clone_rotated
This commit is contained in:
Alexey 2026-03-11 14:55:27 +03:00
commit c46fa75e54
3 changed files with 114 additions and 15 deletions

View file

@ -2,19 +2,20 @@ use std::mem::swap;
use bevy::prelude::*; use bevy::prelude::*;
#[derive(Component, Clone)] #[derive(Component, Clone, Debug)]
pub struct Item { pub struct Item {
pub size: UVec2, pub size: UVec2,
pub position: Option<UVec2>, pub position: Option<UVec2>,
pub rotated: bool,
} }
impl Item { impl Item {
pub fn new(size: UVec2) -> Self { pub fn new(size: UVec2) -> Self {
Self { size, position: None } Self { size, position: None, rotated: false }
} }
pub fn new_positioned(size: UVec2, position: UVec2) -> Self { pub fn new_positioned(size: UVec2, position: UVec2) -> Self {
Self { size, position: Some(position) } Self { size, position: Some(position), rotated: false }
} }
pub fn rect(&self) -> Option<URect> { pub fn rect(&self) -> Option<URect> {
@ -36,14 +37,15 @@ impl Item {
} }
/// Swap size.x with size.y /// Swap size.x with size.y
pub fn swap_size(&mut self) { pub fn rotate(&mut self) {
swap(&mut self.size.x, &mut self.size.y); swap(&mut self.size.x, &mut self.size.y);
self.rotated = !self.rotated;
} }
/// Get clone of item with swapped size /// Get clone of item with swapped size
pub fn clone_swapped(&self) -> Self { pub fn clone_rotated(&self) -> Self {
let mut new = self.clone(); let mut new = self.clone();
new.swap_size(); new.rotate();
new new
} }
} }

View file

@ -1,8 +1,11 @@
use std::f32::consts::FRAC_PI_2;
use bevy::prelude::*; use bevy::prelude::*;
use crate::{inventory::{ActiveInventory, Inventory}, ui::{UiRoot, WindowSize}}; use crate::{inventory::{ActiveInventory, Inventory, item::Item}, ui::{UiRoot, WindowSize}};
const UI_SLOT_ASSET_PATH: &'static str = "sprites/ui/inventory_slot.png"; const UI_SLOT_ASSET_PATH: &'static str = "sprites/ui/inventory_slot.png";
const TEMP_ITEM_PATH: &'static str = "sprites/items/choco_bar.png";
#[derive(Component)] #[derive(Component)]
#[require(Node)] #[require(Node)]
@ -12,6 +15,10 @@ pub struct UiInventory;
#[require(Node, ImageNode)] #[require(Node, ImageNode)]
pub struct UiInventorySlot(UVec2); pub struct UiInventorySlot(UVec2);
#[derive(Component)]
#[require(Node, ImageNode)]
pub struct UiItem(Entity);
fn ui_inventory_bundle(inventory: &Inventory, window_size: &Res<WindowSize>) -> impl Bundle { fn ui_inventory_bundle(inventory: &Inventory, window_size: &Res<WindowSize>) -> impl Bundle {
let window_ratio = window_size.aspect_ratio(); let window_ratio = window_size.aspect_ratio();
let (width, height) = { let (width, height) = {
@ -37,7 +44,7 @@ fn ui_inventory_bundle(inventory: &Inventory, window_size: &Res<WindowSize>) ->
) )
} }
fn inventory_slot_bundle(x: u32, y: u32, width: u32, height: u32, image: Handle<Image>) -> impl Bundle { fn inventory_slot_bundle(x: u32, y: u32, image: Handle<Image>) -> impl Bundle {
( (
UiInventorySlot(UVec2::new(x, y)), UiInventorySlot(UVec2::new(x, y)),
ImageNode { ImageNode {
@ -50,15 +57,54 @@ fn inventory_slot_bundle(x: u32, y: u32, width: u32, height: u32, image: Handle<
height: percent(100.), height: percent(100.),
grid_column: GridPlacement::start(x as i16 + 1), grid_column: GridPlacement::start(x as i16 + 1),
grid_row: GridPlacement::start(y as i16 + 1), grid_row: GridPlacement::start(y as i16 + 1),
align_items: AlignItems::Start,
..default() ..default()
}, },
) )
} }
fn ui_item_bundle(item: &Item, item_entity: Entity, image: Handle<Image>) -> impl Bundle {
let (left, top, min_width, min_height, ui_transform) = match item.rotated {
true => (
percent(100.),
percent(-100.),
percent(100. * item.size.y as f32),
percent(100. * item.size.x as f32),
UiTransform::from_rotation(Rot2::radians(-FRAC_PI_2)),
),
false => (
auto(),
auto(),
percent(100. * item.size.x as f32),
percent(100. * item.size.y as f32),
UiTransform::default(),
),
};
(
UiItem(item_entity),
ImageNode {
image,
image_mode: NodeImageMode::Stretch,
..default()
},
Node {
left,
top,
min_width,
min_height,
..default()
},
BackgroundColor(Color::hsla(0., 0., 0., 0.5)),
ui_transform,
GlobalZIndex(1),
)
}
pub fn setup_ui_inventory( pub fn setup_ui_inventory(
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
inventory_query: Query<(&Inventory, Option<&Children>), With<ActiveInventory>>, inventory_query: Query<(&Inventory, Option<&Children>), With<ActiveInventory>>,
item_query: Query<&Item>,
root_query: Query<Entity, With<UiRoot>>, root_query: Query<Entity, With<UiRoot>>,
window_size: Res<WindowSize>, window_size: Res<WindowSize>,
) { ) {
@ -67,14 +113,31 @@ pub fn setup_ui_inventory(
return; return;
}; };
let ui_slot_image: Handle<Image> = asset_server.load(UI_SLOT_ASSET_PATH); let ui_slot_image: Handle<Image> = asset_server.load(UI_SLOT_ASSET_PATH);
for (inventory, _children) in inventory_query { let temp_item_image: Handle<Image> = asset_server.load(TEMP_ITEM_PATH);
for (inventory, children) in inventory_query {
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
},
}
}).collect::<Vec<(&Item, Entity)>>()
}
None => Vec::new(),
};
let inventory_entity = commands.spawn(ui_inventory_bundle(inventory, &window_size)) let inventory_entity = commands.spawn(ui_inventory_bundle(inventory, &window_size))
.with_children(|commands| { .with_children(|commands| {
for x in 0..inventory.size.x { for x in 0..inventory.size.x { for y in 0..inventory.size.y {
for y in 0..inventory.size.y { let mut slot_commands = commands.spawn(inventory_slot_bundle(x, y, ui_slot_image.clone()));
commands.spawn(inventory_slot_bundle(x, y, inventory.size.x, inventory.size.y, ui_slot_image.clone())); if let Some((item, entity)) = items.iter()
.find(|(i, _)| i.position.unwrap_or_default() == UVec2::new(x, y)) {
slot_commands.with_child(ui_item_bundle(item, *entity, temp_item_image.clone()));
} }
} } }
}).id(); }).id();
commands.entity(root).add_child(inventory_entity); commands.entity(root).add_child(inventory_entity);

View file

@ -1,7 +1,7 @@
use bevy::prelude::*; use bevy::prelude::*;
use leafwing_input_manager::prelude::*; use leafwing_input_manager::prelude::*;
use crate::{GameState, InputAction as Action, inventory::{ActiveInventory, Inventory}}; use crate::{GameState, InputAction as Action, inventory::{ActiveInventory, Inventory, item::Item}};
#[derive(Component)] #[derive(Component)]
pub struct Player { pub struct Player {
@ -18,7 +18,7 @@ fn player_bundle(asset_server: &Res<AssetServer>) -> impl Bundle {
Sprite::from_image(image), Sprite::from_image(image),
Transform::from_xyz(0f32, 0f32, 1f32), Transform::from_xyz(0f32, 0f32, 1f32),
Action::default_input_map(), Action::default_input_map(),
Inventory::new(UVec2::new(12, 8)), Inventory::new(UVec2::new(4, 4)),
ActiveInventory, ActiveInventory,
) )
} }
@ -27,7 +27,37 @@ pub fn setup_player(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(player_bundle(&asset_server)); commands.spawn(player_bundle(&asset_server));
} }
pub fn try_insert_item(
mut commands: Commands,
item_query: Query<&Item>,
inventory_query: Query<(Entity, &Inventory, Option<&Children>)>,
) {
let mut item = Item::new(UVec2::new(1, 1));
for (entity, inventory, children) in inventory_query {
let children = match children {
Some(children) => &children[..],
None => &[],
};
match inventory.find_free_space(item_query, children, item.size) {
Some((position, should_rotate)) => {
if should_rotate {
item.rotate();
}
item.position = Some(position);
info!("Spawning item {item:?}");
commands.entity(entity).with_child(item);
},
None => {
warn!("Inventory does not have space for {}", item.size);
},
}
// only first inventory
break;
}
}
pub fn handle_input( pub fn handle_input(
mut commands: Commands,
time: Res<Time>, time: Res<Time>,
state: Res<State<GameState>>, state: Res<State<GameState>>,
mut next_state: ResMut<NextState<GameState>>, mut next_state: ResMut<NextState<GameState>>,
@ -49,6 +79,10 @@ pub fn handle_input(
if direction != 0f32 { if direction != 0f32 {
sprite.flip_x = direction < 0f32; sprite.flip_x = direction < 0f32;
} }
if action_state.just_pressed(&Action::Interact) {
commands.run_system_cached(try_insert_item);
}
}, },
GameState::Inventory => { GameState::Inventory => {
let (_, mut action_state, _, _) = player; let (_, mut action_state, _, _) = player;