generated from 2ndbeam/bevy-template
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:
parent
ab993be476
commit
c46fa75e54
3 changed files with 114 additions and 15 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue