generated from 2ndbeam/bevy-template
ui: Beginning of UI-related stuff
- UiRoot component - WindowSize resource and update_window_size system - UiInventory and UiInventorySlot components - UiInventory now shows player inventory slots
This commit is contained in:
parent
5f59e02788
commit
ab993be476
6 changed files with 183 additions and 10 deletions
|
|
@ -6,7 +6,7 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.18.0" }
|
||||
bevy = { version = "0.18.0", features = ["debug"] }
|
||||
bevy_common_assets = { version = "0.15.0", features = ["toml"] }
|
||||
bevy_input = { version = "0.18.0", features = ["serde", "serialize"] }
|
||||
leafwing-input-manager = "0.20.0"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
pub mod item;
|
||||
pub mod ui;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Inventory {
|
||||
pub size: UVec2,
|
||||
}
|
||||
|
||||
/// Marker that this inventory will show up when UI is built
|
||||
#[derive(Component)]
|
||||
pub struct ActiveInventory;
|
||||
|
||||
impl Inventory {
|
||||
pub fn new(size: UVec2) -> Self {
|
||||
Self { size }
|
||||
|
|
|
|||
93
src/inventory/ui.rs
Normal file
93
src/inventory/ui.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use crate::{inventory::{ActiveInventory, Inventory}, ui::{UiRoot, WindowSize}};
|
||||
|
||||
const UI_SLOT_ASSET_PATH: &'static str = "sprites/ui/inventory_slot.png";
|
||||
|
||||
#[derive(Component)]
|
||||
#[require(Node)]
|
||||
pub struct UiInventory;
|
||||
|
||||
#[derive(Component)]
|
||||
#[require(Node, ImageNode)]
|
||||
pub struct UiInventorySlot(UVec2);
|
||||
|
||||
fn ui_inventory_bundle(inventory: &Inventory, window_size: &Res<WindowSize>) -> impl Bundle {
|
||||
let window_ratio = window_size.aspect_ratio();
|
||||
let (width, height) = {
|
||||
if window_ratio >= 1. {
|
||||
(auto(), percent(100))
|
||||
} else {
|
||||
(percent(100), auto())
|
||||
}
|
||||
};
|
||||
(
|
||||
UiInventory,
|
||||
Node {
|
||||
align_self: AlignSelf::Center,
|
||||
align_content: AlignContent::Center,
|
||||
display: Display::Grid,
|
||||
width,
|
||||
height,
|
||||
aspect_ratio: Some(inventory.size.x as f32 / inventory.size.y as f32),
|
||||
grid_auto_columns: vec![GridTrack::percent(100. / inventory.size.x as f32)],
|
||||
grid_auto_rows: vec![GridTrack::percent(100. / inventory.size.y as f32)],
|
||||
..default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn inventory_slot_bundle(x: u32, y: u32, width: u32, height: u32, image: Handle<Image>) -> impl Bundle {
|
||||
(
|
||||
UiInventorySlot(UVec2::new(x, y)),
|
||||
ImageNode {
|
||||
image,
|
||||
image_mode: NodeImageMode::Stretch,
|
||||
..default()
|
||||
},
|
||||
Node {
|
||||
width: percent(100.),
|
||||
height: percent(100.),
|
||||
grid_column: GridPlacement::start(x as i16 + 1),
|
||||
grid_row: GridPlacement::start(y as i16 + 1),
|
||||
..default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn setup_ui_inventory(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
inventory_query: Query<(&Inventory, Option<&Children>), With<ActiveInventory>>,
|
||||
root_query: Query<Entity, With<UiRoot>>,
|
||||
window_size: Res<WindowSize>,
|
||||
) {
|
||||
let Ok(root) = root_query.single() else {
|
||||
error!("Query contains more than one UiRoot");
|
||||
return;
|
||||
};
|
||||
let ui_slot_image: Handle<Image> = asset_server.load(UI_SLOT_ASSET_PATH);
|
||||
for (inventory, _children) in inventory_query {
|
||||
let inventory_entity = commands.spawn(ui_inventory_bundle(inventory, &window_size))
|
||||
.with_children(|commands| {
|
||||
for x in 0..inventory.size.x {
|
||||
for y in 0..inventory.size.y {
|
||||
commands.spawn(inventory_slot_bundle(x, y, inventory.size.x, inventory.size.y, ui_slot_image.clone()));
|
||||
}
|
||||
}
|
||||
}).id();
|
||||
commands.entity(root).add_child(inventory_entity);
|
||||
|
||||
// for simplicity we'll show only first inventory
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_ui_inventory(
|
||||
mut commands: Commands,
|
||||
inventory_query: Query<Entity, With<UiInventory>>,
|
||||
) {
|
||||
for entity in inventory_query {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
18
src/lib.rs
18
src/lib.rs
|
|
@ -2,6 +2,7 @@ pub mod player;
|
|||
pub mod layout;
|
||||
pub mod input;
|
||||
pub mod inventory;
|
||||
pub mod ui;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
|
|
@ -19,6 +20,13 @@ pub enum InputAction {
|
|||
Interact,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
|
||||
pub enum GameState {
|
||||
#[default]
|
||||
Running,
|
||||
Inventory,
|
||||
}
|
||||
|
||||
impl InputAction {
|
||||
pub fn default_input_map() -> InputMap<Self> {
|
||||
let input_map = InputMap::default()
|
||||
|
|
@ -51,12 +59,20 @@ fn camera_bundle() -> impl Bundle {
|
|||
|
||||
fn setup_global(mut commands: Commands) {
|
||||
commands.spawn(camera_bundle());
|
||||
commands.spawn(ui::UiRoot::new());
|
||||
}
|
||||
|
||||
impl Plugin for ExpeditionPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(input::InputAssetPlugin::<InputAction>::default())
|
||||
.init_state::<GameState>()
|
||||
.insert_resource(ui::WindowSize::default())
|
||||
.add_systems(Startup, (player::setup_player, setup_global))
|
||||
.add_systems(Update, player::handle_input);
|
||||
.add_systems(Update, (
|
||||
player::handle_input,
|
||||
ui::update_window_size,
|
||||
))
|
||||
.add_systems(OnEnter(GameState::Inventory), inventory::ui::setup_ui_inventory)
|
||||
.add_systems(OnExit(GameState::Inventory), inventory::ui::clear_ui_inventory);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use bevy::prelude::*;
|
||||
use leafwing_input_manager::prelude::*;
|
||||
|
||||
use crate::InputAction as Action;
|
||||
use crate::{GameState, InputAction as Action, inventory::{ActiveInventory, Inventory}};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Player {
|
||||
|
|
@ -18,6 +18,8 @@ fn player_bundle(asset_server: &Res<AssetServer>) -> impl Bundle {
|
|||
Sprite::from_image(image),
|
||||
Transform::from_xyz(0f32, 0f32, 1f32),
|
||||
Action::default_input_map(),
|
||||
Inventory::new(UVec2::new(12, 8)),
|
||||
ActiveInventory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -27,15 +29,33 @@ pub fn setup_player(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
|
||||
pub fn handle_input(
|
||||
time: Res<Time>,
|
||||
mut player: Query<(&Player, &ActionState<Action>, &mut Transform, &mut Sprite)>,
|
||||
state: Res<State<GameState>>,
|
||||
mut next_state: ResMut<NextState<GameState>>,
|
||||
mut player: Query<(&Player, &mut ActionState<Action>, &mut Transform, &mut Sprite)>,
|
||||
) {
|
||||
let player = player.single_mut().expect("Player should be single");
|
||||
let (Player {speed}, action_state, mut transform, mut sprite) = player;
|
||||
match state.get() {
|
||||
GameState::Running => {
|
||||
let (Player {speed}, mut action_state, mut transform, mut sprite) = player;
|
||||
|
||||
let direction = action_state.clamped_value(&Action::Move);
|
||||
if action_state.just_released(&Action::ToggleInventory) {
|
||||
next_state.set(GameState::Inventory);
|
||||
action_state.reset(&Action::ToggleInventory);
|
||||
}
|
||||
|
||||
transform.translation.x += direction * speed * time.delta_secs();
|
||||
if direction != 0f32 {
|
||||
sprite.flip_x = direction < 0f32;
|
||||
let direction = action_state.clamped_value(&Action::Move);
|
||||
|
||||
transform.translation.x += direction * speed * time.delta_secs();
|
||||
if direction != 0f32 {
|
||||
sprite.flip_x = direction < 0f32;
|
||||
}
|
||||
},
|
||||
GameState::Inventory => {
|
||||
let (_, mut action_state, _, _) = player;
|
||||
if action_state.just_released(&Action::ToggleInventory) {
|
||||
next_state.set(GameState::Running);
|
||||
action_state.reset(&Action::ToggleInventory);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
39
src/ui.rs
Normal file
39
src/ui.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use bevy::{prelude::*, window::WindowResized};
|
||||
|
||||
#[derive(Component)]
|
||||
#[require(Node)]
|
||||
pub struct UiRoot;
|
||||
|
||||
#[derive(Resource, Deref, DerefMut, Default)]
|
||||
pub struct WindowSize(Vec2);
|
||||
|
||||
impl WindowSize {
|
||||
pub fn aspect_ratio(&self) -> f32 {
|
||||
self.x as f32 / self.y as f32
|
||||
}
|
||||
}
|
||||
|
||||
impl UiRoot {
|
||||
pub fn new() -> impl Bundle {
|
||||
(
|
||||
UiRoot,
|
||||
Node {
|
||||
width: percent(100.),
|
||||
height: percent(100.),
|
||||
align_self: AlignSelf::Center,
|
||||
justify_self: JustifySelf::Center,
|
||||
..default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_window_size(
|
||||
mut resize_reader: MessageReader<WindowResized>,
|
||||
mut window_size: ResMut<WindowSize>,
|
||||
) {
|
||||
for event in resize_reader.read() {
|
||||
window_size.x = event.width;
|
||||
window_size.y = event.height;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue