-
-
Save ethereumdegen/ea04d0054d28be269734ba05bde2626e to your computer and use it in GitHub Desktop.
Bevy Ui - Inventory Menu
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Renders the currently-grabbed-item icon at the cursor | |
*/ | |
use bevy::prelude::*; | |
use crate::{appstate::AppState, file_system_interaction::texture_atlas::TextureAtlasAssets, items::inventory::InventoryComponent, player_control::player_embodiment::Player}; | |
use super::{components::ui_icon_component::UiIconComponent, inventory_menu::InventoryMenuComponent}; | |
use crate::file_system_interaction::asset_loading::TextureAssets; | |
use crate::ui::components::ui_icon_component::UiIconSource; | |
use bevy::window::PrimaryWindow; | |
pub(crate) fn cursor_grab_icon_plugin(app: &mut App) { | |
app.add_systems( | |
OnEnter(AppState::Playing), | |
spawn_cursor_grab_icon.run_if(not(any_with_component::<CursorGrabIconComponent>)), | |
) | |
//update the contents based on the inventory menu resource !? | |
.add_systems( | |
Update, | |
( | |
update_cursor_grab_icon_visibility_position, | |
update_cursor_grab_icon_data_source, | |
update_cursor_grab_icon_contents | |
).chain().run_if(any_with_component::<CursorGrabIconComponent>), | |
) | |
; | |
} | |
#[derive(Component,Default)] | |
pub struct CursorGrabIconComponent{ | |
pub icon_data_source: Option<CursorGrabIconDataSource> | |
//pub tooltip_data_source: Option<Entity>, //should have a TooltipDataSource. If none, this is not visible | |
//pub render_offset: Vec2, | |
//tooltip_menu_data: Option<ActiveTooltipMenuData> | |
} | |
pub enum CursorGrabIconDataSource{ | |
InventorySlot(usize) | |
} | |
// this should spawn the nodes such as the root node and the atlasimagenode | |
fn spawn_cursor_grab_icon( | |
mut commands: Commands, | |
texture_atlas_assets: Res<TextureAtlasAssets>, | |
images: Res<TextureAssets>, | |
windows: Query<&Window, With<PrimaryWindow>>, | |
) { | |
let Some(cursor_position) = windows.single().cursor_position() else {return}; | |
let dimensions:[u32;2] = [40,40]; | |
let icons_atlas = &texture_atlas_assets.item_icons_atlas.as_ref().unwrap(); | |
let cursor_icon_node = commands | |
.spawn(( | |
// Button, | |
// Interaction::default(), | |
AtlasImageBundle { | |
style: Style { | |
position_type: PositionType::Absolute, | |
left: Val::Px(cursor_position.x ), | |
top: Val::Px(cursor_position.y ), | |
width: Val::Px(dimensions[0] as f32), | |
height: Val::Px(dimensions[1] as f32), | |
..default() | |
}, | |
texture_atlas: TextureAtlas { | |
layout: icons_atlas.layout.clone_weak(), | |
index: 1, | |
}, | |
image: UiImage { | |
texture: icons_atlas.image.clone_weak(), | |
flip_x: false, | |
flip_y: false, | |
}, | |
visibility: Visibility::Inherited, //for now | |
..default() | |
}, | |
)) | |
.insert(( | |
UiIconComponent { icon_source: None }, | |
CursorGrabIconComponent::default(), | |
ZIndex::Global( 110 ) | |
)) | |
.id(); | |
} | |
fn update_cursor_grab_icon_visibility_position( | |
mut cursor_grab_icon_query: Query<(& CursorGrabIconComponent, &mut Visibility, &mut Style )>, | |
windows: Query<&Window, With<PrimaryWindow>>, | |
) { | |
let Some(cursor_position) = windows.single().cursor_position() else {return}; | |
//should always be positioned by the cursor | |
let Some((cursor_grab_icon, mut visibility, mut style )) = cursor_grab_icon_query.get_single_mut().ok() else {return}; | |
match cursor_grab_icon.icon_data_source { | |
Some( _ ) => *visibility = Visibility::Inherited, | |
None => *visibility = Visibility::Hidden, | |
} | |
style.left = Val::Px(cursor_position.x); | |
style.top = Val::Px( cursor_position.y ); | |
} | |
fn update_cursor_grab_icon_data_source( | |
mut cursor_grab_icon_query: Query<&mut CursorGrabIconComponent>, | |
inventory_menu_query: Query<&InventoryMenuComponent> | |
) { | |
let Some(inventory_menu) = inventory_menu_query.get_single().ok() else {return}; | |
let Some(mut cursor_grab_icon) = cursor_grab_icon_query.get_single_mut().ok() else {return}; | |
match inventory_menu.grabbed_inventory_slot { | |
Some(grabbed_slot) => cursor_grab_icon.icon_data_source = Some( CursorGrabIconDataSource::InventorySlot(grabbed_slot) ) , | |
None => cursor_grab_icon.icon_data_source = None | |
}; | |
} | |
// this should update the atlasnode image prop | |
fn update_cursor_grab_icon_contents( | |
mut cursor_grab_icon_query: Query<(&CursorGrabIconComponent, &mut UiIconComponent), Changed<CursorGrabIconComponent>>, | |
player_query: Query<&InventoryComponent, With<Player>>, | |
) { | |
let Some((cursor_grab_icon, mut ui_icon_comp)) = cursor_grab_icon_query.get_single_mut().ok() else {return}; | |
let Some(player_inventory) = player_query.get_single().ok() else {return}; | |
let Some(CursorGrabIconDataSource::InventorySlot(slot_index)) = cursor_grab_icon.icon_data_source else {return}; | |
let Some(item) = player_inventory.items.get(&slot_index) else {return}; | |
// let Some(mut image) = image_query.get_single_mut().ok() else {return}; | |
ui_icon_comp.icon_source = Some(UiIconSource::ItemType(item.item_type_name.clone())); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use bevy::{prelude::*, utils::HashMap}; | |
//could i use enum instead of string maybe ? | |
/* | |
1. Register child ui elements to the root node using the linker map | |
``` | |
let mut elements_map = HashMap::new(); | |
let title = ... TextBundle .. .id(); | |
let choices_array = [ a vec of nodebundle entities] ... [ .. .id() ]; | |
elements_map.insert("choices".into(), LinkedUiElement::Single(title)); | |
elements_map.insert("choices".into(), LinkedUiElement::Array(choices_array)); | |
... | |
commands.entity(root_node) | |
.insert( DialogMenuComponent::default() ) | |
.insert( UiElementLinker { | |
elements_map | |
} ) ; | |
``` | |
2. Then, in your systems, you can find your child elements using the map | |
``` | |
fn update_dialog_menu_contents( | |
menu_query: Query<(Entity,& DialogMenuComponent,&UiElementLinker), Changed<DialogMenuComponent> >, | |
mut text_node_query: Query<&mut Text> | |
){ | |
if let Ok((menu_entity,dialog_menu,element_linker)) = menu_query.get_single(){ | |
let Some(title_element) = element_linker.elements_map.get( "title" ) else { return }; | |
let title_element_node = match title_element { | |
LinkedUiElement::Single(node) => node, | |
_ => return | |
}; | |
let Ok(mut title_node) = text_node_query.get_mut( *title_element_node ) else{ return } ; | |
let current_body = &dialog_menu.body; | |
title_node.sections[0].value = current_body.clone() .unwrap_or(" ? ".into()); | |
... | |
} | |
} | |
``` | |
*/ | |
#[derive(Component,Default)] | |
pub struct UiElementLinker{ | |
pub elements_map: UiElementMap //maps strings to the sub-entity | |
} | |
pub type UiElementMap = HashMap<String, LinkedUiElement> ; | |
#[derive(Clone,Debug)] | |
pub enum LinkedUiElement { | |
Single(Entity), | |
Array(Vec<Entity>) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use bevy::{prelude::*, utils::HashMap}; | |
use bevy_asset_loader::mapped::{AssetFileName}; | |
pub struct TextureAtlasCombined { | |
pub layout: Handle<TextureAtlasLayout>, | |
pub image: Handle<Image> | |
} | |
#[derive(Resource, Default)] | |
pub(crate) struct TextureAtlasAssets { | |
pub(crate) gui_pixel_icons_atlas: Option< TextureAtlasCombined >, | |
pub(crate) ability_icons_atlas: Option< TextureAtlasCombined >, | |
pub(crate) item_icons_atlas: Option< TextureAtlasCombined >, | |
// pub(crate) particles_atlas: Option<Handle<TextureAtlas>>, | |
} | |
pub fn get_index_for_subtexture_by_name( | |
texture_atlas_handle: &Handle<TextureAtlasLayout>, | |
texture_atlases : &Res<Assets<TextureAtlasLayout>>, | |
image_handles_map: &HashMap<AssetFileName,Handle<Image>> , | |
texture_name: &String | |
) -> Option<usize> { | |
if let Some(atlas) = texture_atlases.get(texture_atlas_handle){ | |
if let Some(image_handle) = image_handles_map.get(texture_name.as_str()){ | |
return atlas.get_texture_index(image_handle) | |
} | |
} | |
//self.index_registry.get(texture_name) .copied() //why do we need to do plus 1 ? | |
None | |
} | |
pub fn build_texture_atlas( | |
handles: &HashMap<AssetFileName, Handle<Image>>, | |
max_size: Option<Vec2>, | |
padding: Option<UVec2>, | |
// textures: &mut ResMut<Assets<Image>>, | |
texture_atlases: &mut ResMut<Assets<TextureAtlasLayout>>, | |
images : &mut ResMut<Assets<Image>>, | |
) -> TextureAtlasCombined { | |
let mut texture_atlas_builder = TextureAtlasBuilder::default() | |
.max_size(max_size.unwrap_or( Vec2::new(2048., 2048.) )) | |
.padding(padding.unwrap_or(UVec2::ZERO)); | |
// let mut texture_atlas_index_registry: TextureAtlasIndexRegistry = HashMap::new(); | |
for (icon_name,handle) in handles.iter() { | |
if let Some(texture) = images.get(handle) { | |
texture_atlas_builder.add_texture(Some(handle.clone_weak().into()) , texture ); | |
// texture_atlas_index_registry.insert(icon_name.clone(), index) ; | |
// println!("register atlas image {:?} {:?}", icon_name,index); | |
} else { | |
panic!("Texture handle did not resolve to an `Image` asset: {:?}", icon_name); | |
// continue; | |
} | |
} | |
let (texture_atlas,image) = texture_atlas_builder.finish( ).expect("Failed to build texture atlas."); | |
let texture_atlas_handle = texture_atlases.add(texture_atlas); | |
let image_handle = images.add(image); | |
TextureAtlasCombined { | |
layout: texture_atlas_handle, | |
image: image_handle | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use bevy::prelude::*; | |
use crate::{file_system_interaction::{ability_type::AbilityType, asset_loading::{AbilitySystemTypeAssets, ItemSystemTypeAssets, TextureAssets}, item_type::ItemType, texture_atlas::{get_index_for_subtexture_by_name, TextureAtlasAssets}}, | |
ui::{element_linker::LinkedUiElement, inventory_menu::InventoryUiEquipmentSlot}}; | |
pub(crate) fn ui_icons_plugin(app: &mut App) { | |
app | |
.add_systems(Update, update_icons_from_source.run_if(any_with_component::<UiIconComponent> ) ) | |
; | |
} | |
#[derive(Component,Default)] | |
pub struct UiIconComponent { | |
pub icon_source: Option<UiIconSource> | |
} | |
pub enum UiIconSource { | |
AbilityType(String), | |
ItemType(String) | |
} | |
fn update_icons_from_source( | |
mut image_node_query: Query< (&mut Visibility, &mut TextureAtlas, &UiIconComponent), Changed< UiIconComponent > >, | |
ability_system_type_assets:Res<AbilitySystemTypeAssets> , | |
item_system_type_assets: Res<ItemSystemTypeAssets> , | |
item_types: Res<Assets<ItemType>>, | |
ability_types: Res<Assets<AbilityType>>, | |
texture_atlases: Res<Assets<TextureAtlasLayout>>, | |
images: Res<TextureAssets> | |
){ | |
//render equipped stuff like weapons that are on you !! | |
for ( mut _visibility, mut tex_atlas, ui_icon) in image_node_query.iter_mut() { | |
let Some(icon_source) = &ui_icon.icon_source else{continue}; | |
let icon_name = match &icon_source { | |
UiIconSource::ItemType(item_type_name) => { | |
if let Some(item_type_handle) = item_system_type_assets.item_types.get(item_type_name.as_str()) { | |
if let Some(item_type) = item_types.get(item_type_handle) { | |
if let Some(equipment_item_icon_name) = item_type.equipment_data.as_ref().map(|d| d.icon.clone() ).flatten() { | |
Some(equipment_item_icon_name.clone()) | |
}else { None } | |
}else { None } | |
}else { None } | |
}, | |
UiIconSource::AbilityType(ability_type_name) => { | |
if let Some(ability_type_handle) = ability_system_type_assets.ability_types.get(ability_type_name.as_str()) { | |
if let Some(ability_type) = ability_types.get(ability_type_handle) { | |
if let Some(ability_icon_name) = &ability_type.icon_texture { | |
Some(ability_icon_name .clone()) | |
}else { None } | |
}else { None } | |
}else { None } | |
}, | |
} ; | |
let Some(icon_name) = icon_name else {continue}; | |
let icons_handles_map = match & icon_source { | |
UiIconSource::ItemType( _ ) => Some( &images.item_icons ), | |
UiIconSource::AbilityType( _ ) => Some( &images.ability_icons ), | |
}; | |
let Some(icons_handles_map) = icons_handles_map else {continue}; | |
let atlas_layout = &tex_atlas.layout; | |
// let icons_atlas = &texture_atlas_assets.item_icons_atlas.as_ref().unwrap(); | |
let Some(image_index) = get_index_for_subtexture_by_name( | |
atlas_layout, | |
&texture_atlases, | |
&icons_handles_map, | |
&icon_name | |
) else {continue} ; | |
tex_atlas.index = image_index; | |
} | |
//} | |
} |
Author
ethereumdegen
commented
Apr 29, 2024
the Update system ensures that as the data in my players ItemComponent changes (the items in each slot), the icons will automagically update on this render UI according to the icon defined on the item type struct for the item in that equipment slot.
In this example i equipped a dagger so that icon changed properly. The blank slots show bat wings as a default but ignore that for now .
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment