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"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = { version = "0.18.0" }
|
bevy = { version = "0.18.0", features = ["debug"] }
|
||||||
bevy_common_assets = { version = "0.15.0", features = ["toml"] }
|
bevy_common_assets = { version = "0.15.0", features = ["toml"] }
|
||||||
bevy_input = { version = "0.18.0", features = ["serde", "serialize"] }
|
bevy_input = { version = "0.18.0", features = ["serde", "serialize"] }
|
||||||
leafwing-input-manager = "0.20.0"
|
leafwing-input-manager = "0.20.0"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
pub mod item;
|
pub mod item;
|
||||||
|
pub mod ui;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Inventory {
|
pub struct Inventory {
|
||||||
pub size: UVec2,
|
pub size: UVec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Marker that this inventory will show up when UI is built
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct ActiveInventory;
|
||||||
|
|
||||||
impl Inventory {
|
impl Inventory {
|
||||||
pub fn new(size: UVec2) -> Self {
|
pub fn new(size: UVec2) -> Self {
|
||||||
Self { size }
|
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 layout;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod inventory;
|
pub mod inventory;
|
||||||
|
pub mod ui;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
|
@ -19,6 +20,13 @@ pub enum InputAction {
|
||||||
Interact,
|
Interact,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
|
||||||
|
pub enum GameState {
|
||||||
|
#[default]
|
||||||
|
Running,
|
||||||
|
Inventory,
|
||||||
|
}
|
||||||
|
|
||||||
impl InputAction {
|
impl InputAction {
|
||||||
pub fn default_input_map() -> InputMap<Self> {
|
pub fn default_input_map() -> InputMap<Self> {
|
||||||
let input_map = InputMap::default()
|
let input_map = InputMap::default()
|
||||||
|
|
@ -51,12 +59,20 @@ fn camera_bundle() -> impl Bundle {
|
||||||
|
|
||||||
fn setup_global(mut commands: Commands) {
|
fn setup_global(mut commands: Commands) {
|
||||||
commands.spawn(camera_bundle());
|
commands.spawn(camera_bundle());
|
||||||
|
commands.spawn(ui::UiRoot::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin for ExpeditionPlugin {
|
impl Plugin for ExpeditionPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_plugins(input::InputAssetPlugin::<InputAction>::default())
|
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(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 bevy::prelude::*;
|
||||||
use leafwing_input_manager::prelude::*;
|
use leafwing_input_manager::prelude::*;
|
||||||
|
|
||||||
use crate::InputAction as Action;
|
use crate::{GameState, InputAction as Action, inventory::{ActiveInventory, Inventory}};
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
|
|
@ -18,6 +18,8 @@ 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)),
|
||||||
|
ActiveInventory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,15 +29,33 @@ pub fn setup_player(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
|
||||||
pub fn handle_input(
|
pub fn handle_input(
|
||||||
time: Res<Time>,
|
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 = 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();
|
let direction = action_state.clamped_value(&Action::Move);
|
||||||
if direction != 0f32 {
|
|
||||||
sprite.flip_x = direction < 0f32;
|
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