Skip to content

Instantly share code, notes, and snippets.

@ethereumdegen
Last active April 29, 2024 20:21
Show Gist options
  • Save ethereumdegen/ea04d0054d28be269734ba05bde2626e to your computer and use it in GitHub Desktop.
Save ethereumdegen/ea04d0054d28be269734ba05bde2626e to your computer and use it in GitHub Desktop.
Bevy Ui - Inventory Menu
/*
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()));
}
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>)
}
use crate::{
appstate::AppState,
file_system_interaction::{
asset_loading::{ItemSystemTypeAssets, TextureAssets},
item_type::ItemType,
texture_atlas::{get_index_for_subtexture_by_name, TextureAtlasAssets},
},
items::{equipment::EquipmentSlotType, inventory::{InventoryComponent, InventoryEvent}},
level_instantiation::spawning::objects::item::ItemComponent,
player_control::player_embodiment::Player,
ui::components::ui_icon_component::UiIconSource,
};
use bevy::{prelude::*, utils::HashMap};
use bevy_mod_sysfail::*;
use super::{
element_linker::{LinkedUiElement, UiElementLinker, UiElementMap}, tooltip_menu::TooltipDataSource, ui_menu_state::{UiMenuState, UiMenuType}
};
use super::components::ui_icon_component::UiIconComponent;
/*
TODO
- Put invisible tiles on the 6x10 grid in the bottom, they should all have InventoryUiInventorySlot on them so the DROP grabbed will work
*/
pub(crate) fn inventory_menu_plugin(app: &mut App) {
app.add_systems(
OnEnter(AppState::Playing),
spawn_inventory_menu.run_if(not(any_with_component::<InventoryMenuComponent>)),
)
.add_systems(
Update,
(update_inventory_menu_visibility,
update_inventory_menu_equipment_icons,
handle_equipment_slot_interactions
).chain().run_if(any_with_component::<InventoryMenuComponent>),
)
;
}
#[derive(Component, Default)]
pub struct InventoryMenuComponent {
pub grabbed_inventory_slot: Option<usize> //if Some, an inventory slot is grabbed by the cursor. Should render it on the cursor and should NOT render it in the normal spot (or in the normal spot it will be very transparent)
}
#[derive(Component, Default)]
pub struct InventoryUiBodyPane {
equipment_ui_slots: HashMap<InventoryUiEquipmentSlot, LinkedUiElement>,
}
#[derive(Component, Default)]
pub struct InventoryUiContainerGrid {}
impl InventoryUiContainerGrid {
fn get_grid_tile_size() -> [u32; 2] {
[50, 50]
}
fn get_grid_dimensions() -> [u32; 2] {
[6, 10]
}
fn get_render_offset() -> [u32; 2] {
[14, 547]
}
}
#[derive(Clone, Eq, PartialEq, Debug, Component)]
pub struct InventoryUiInventorySlot(usize) ;
#[derive(Clone, Hash, Eq, PartialEq, Debug, Component)]
pub enum InventoryUiEquipmentSlot {
Head,
ShoulderLeft,
ShoulderRight,
Chest,
Belt,
Legs,
Feet,
HandLeft,
HandRight,
WeaponLeft,
WeaponRight,
}
impl InventoryUiEquipmentSlot {
pub fn get_all_slots() -> Vec<Self> {
vec![
InventoryUiEquipmentSlot::Head,
InventoryUiEquipmentSlot::ShoulderLeft,
InventoryUiEquipmentSlot::ShoulderRight,
InventoryUiEquipmentSlot::Chest,
InventoryUiEquipmentSlot::Belt,
InventoryUiEquipmentSlot::Legs,
InventoryUiEquipmentSlot::Feet,
InventoryUiEquipmentSlot::HandLeft,
InventoryUiEquipmentSlot::HandRight,
InventoryUiEquipmentSlot::WeaponLeft,
InventoryUiEquipmentSlot::WeaponRight,
]
}
pub fn get_equipment_type(&self) -> EquipmentSlotType {
match self {
InventoryUiEquipmentSlot::Head => EquipmentSlotType::Head,
InventoryUiEquipmentSlot::ShoulderLeft => EquipmentSlotType::Shoulders,
InventoryUiEquipmentSlot::ShoulderRight => EquipmentSlotType::Shoulders,
InventoryUiEquipmentSlot::Chest => EquipmentSlotType::Chest,
InventoryUiEquipmentSlot::Belt => EquipmentSlotType::Waist,
InventoryUiEquipmentSlot::Legs => EquipmentSlotType::Legs,
InventoryUiEquipmentSlot::Feet => EquipmentSlotType::Feet,
InventoryUiEquipmentSlot::HandLeft => EquipmentSlotType::Hands,
InventoryUiEquipmentSlot::HandRight => EquipmentSlotType::Hands,
InventoryUiEquipmentSlot::WeaponLeft => EquipmentSlotType::WeaponMain,
InventoryUiEquipmentSlot::WeaponRight => EquipmentSlotType::WeaponMain,
}
}
pub fn get_render_offset(&self) -> [u32; 2] {
match self {
InventoryUiEquipmentSlot::Head => [110, 8],
InventoryUiEquipmentSlot::ShoulderLeft => [182, 75],
InventoryUiEquipmentSlot::ShoulderRight => [40, 75],
InventoryUiEquipmentSlot::Chest => [100, 100],
InventoryUiEquipmentSlot::Belt => [100, 214],
InventoryUiEquipmentSlot::Legs => [110, 250],
InventoryUiEquipmentSlot::Feet => [110, 340],
InventoryUiEquipmentSlot::HandLeft => [10, 175],
InventoryUiEquipmentSlot::HandRight => [205, 175],
InventoryUiEquipmentSlot::WeaponLeft => [10, 313],
InventoryUiEquipmentSlot::WeaponRight => [215, 313],
}
}
pub fn get_render_dimensions(&self) -> [u32; 2] {
match self {
InventoryUiEquipmentSlot::Head => [40, 46],
InventoryUiEquipmentSlot::Chest => [60, 90],
InventoryUiEquipmentSlot::Belt => [60, 16],
InventoryUiEquipmentSlot::Legs => [45, 65],
InventoryUiEquipmentSlot::Feet => [45, 65],
InventoryUiEquipmentSlot::HandLeft => [45, 45],
InventoryUiEquipmentSlot::HandRight => [45, 45],
InventoryUiEquipmentSlot::WeaponLeft => [45, 100],
InventoryUiEquipmentSlot::WeaponRight => [45, 100],
_ => [36, 36],
}
}
//just draw a red / through this icon but... allow interact w it
pub fn get_slot_is_redundant(&self) -> bool {
match self {
InventoryUiEquipmentSlot::ShoulderRight => true,
InventoryUiEquipmentSlot::HandRight => true,
_ => false,
}
}
}
fn update_inventory_menu_visibility(
mut menu_query: Query<(Entity, &mut Visibility), With<InventoryMenuComponent>>,
ui_menu_state: Res<State<UiMenuState>>,
) {
if let Ok((_menu_entity, mut visibility)) = menu_query.get_single_mut() {
let visible = match ui_menu_state.get() {
UiMenuState::MenuOpened(menu_type) => *menu_type == UiMenuType::InventoryMenu,
_ => false,
};
*visibility = match visible {
true => Visibility::Inherited,
false => Visibility::Hidden,
};
}
}
#[sysfail]
fn spawn_inventory_menu(
mut commands: Commands,
texture_atlas_assets: Res<TextureAtlasAssets>,
images: Res<TextureAssets>,
) {
let panel_inventory_handle = images
.ui
.get("PanelInventory.png")
.ok_or("PanelInventory not found")?;
let panel_inventory_body_handle = images
.ui
.get("PanelInventory_Body.png")
.ok_or("PanelInventory_Body not found")?;
let inventory_select_cell_handle = images
.ui
.get("Inventory_SelectCell.png")
.ok_or("Inventory_SelectCell not found")?;
let mut elements_map = HashMap::new();
let root_node = commands
.spawn(NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
position_type: PositionType::Absolute,
top: Val::Px(0.0),
left: Val::Px(0.0),
display: Display::Flex,
flex_direction: FlexDirection::Row,
justify_content: JustifyContent::FlexEnd,
align_items: AlignItems::Center,
//justify_content: JustifyContent::Center,
..default()
},
// visibility: Visibility::Inherited,
..default()
})
.id();
let container_node = commands
.spawn(NodeBundle {
style: Style {
display: Display::Flex,
flex_direction: FlexDirection::Column, // Set direction to column for vertical layout
justify_content: JustifyContent::FlexStart, // Adjust main axis distribution as needed
align_items: AlignItems::FlexStart, // Adjust cross axis alignment as needed (center, flex-end, etc.)
flex_wrap: FlexWrap::Wrap, //use multiple lines
width: Val::Px(527.0),
height: Val::Px(888.0),
// top: Val::Px( 5.0 ),
right: Val::Px(30.0),
..default()
},
// background_color: Color::NONE.into(),
background_color: Color::rgb(0.3, 0.3, 0.3).into(),
..default()
})
// .add_child(title_node)
.id();
let panel_background_node: Entity = commands
.spawn(ImageBundle {
style: Style {
position_type: PositionType::Absolute,
//top: Val::Px(0.0),
//left: Val::Px(0.0),
width: Val::Px(527.),
height: Val::Px(888.),
..default()
},
//texture_atlas: gui_pixel_icons_atlas.clone_weak(),
//texture_atlas_image: UiTextureAtlasImage{ index: heart_icon_index , flip_x: false, flip_y:false },
image: UiImage::new(panel_inventory_handle.clone()),
visibility: Visibility::Inherited,
..default()
})
.id();
commands
.entity(container_node)
.add_child(panel_background_node);
let mut equipment_ui_slots: HashMap<InventoryUiEquipmentSlot, LinkedUiElement> = HashMap::new();
let icons_atlas = &texture_atlas_assets.item_icons_atlas.as_ref().unwrap();
for ui_equipment_slot in InventoryUiEquipmentSlot::get_all_slots() {
let offset = ui_equipment_slot.get_render_offset();
let dimensions = ui_equipment_slot.get_render_dimensions();
let inventory_slot_index = ui_equipment_slot.get_equipment_type().get_inventory_slot_index();
let ui_eq_slot_node = commands
.spawn((
Button,
Interaction::default(),
AtlasImageBundle {
style: Style {
position_type: PositionType::Absolute,
left: Val::Px(offset[0] as f32),
top: Val::Px(offset[1] as f32),
width: Val::Px(dimensions[0] as f32),
height: Val::Px(dimensions[1] as f32),
..default()
},
//texture_atlas: gui_pixel_icons_atlas.clone_weak(),
//texture_atlas_image: UiTextureAtlasImage{ index: heart_icon_index , flip_x: false, flip_y:false },
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 },
ui_equipment_slot.clone(),
InventoryUiInventorySlot( inventory_slot_index ),
TooltipDataSource::InventoryItemSlot( inventory_slot_index ) ,
/* ButtonEventComponent {
events_on_pressed: button_def.button_events,
}*/
))
.id();
equipment_ui_slots.insert(ui_equipment_slot, LinkedUiElement::Single(ui_eq_slot_node));
}
let panel_background_body_node: Entity = commands
.spawn(ImageBundle {
style: Style {
position_type: PositionType::Absolute,
top: Val::Px(80.0),
left: Val::Px(130.0),
width: Val::Px(265.),
height: Val::Px(435.),
..default()
},
//texture_atlas: gui_pixel_icons_atlas.clone_weak(),
//texture_atlas_image: UiTextureAtlasImage{ index: heart_icon_index , flip_x: false, flip_y:false },
image: UiImage::new(panel_inventory_body_handle.clone()),
// visibility: Visibility::Inherited,
..default()
})
.insert(InventoryUiBodyPane {
equipment_ui_slots: equipment_ui_slots.clone(),
})
.id();
for ui_equipment_slot in InventoryUiEquipmentSlot::get_all_slots() {
let Some(linked_elem) = equipment_ui_slots.get(&ui_equipment_slot) else {
continue;
};
let LinkedUiElement::Single(node) = linked_elem else {
continue;
};
commands.entity(panel_background_body_node).add_child(*node);
}
commands
.entity(container_node)
.add_child(panel_background_body_node);
// elements_map.insert("hearts".into(), LinkedUiElement::Array( heart_image_nodes_array ));
commands
.entity(root_node)
.insert(InventoryMenuComponent::default())
.insert(UiElementLinker { elements_map })
.add_child(container_node);
//add invisible buttons to the equipment and item tiles !
// these invis buttons can work just like the buttons in ExitMenu or whatever
}
fn update_inventory_menu_equipment_icons(
player_query: Query<&InventoryComponent, (Changed<InventoryComponent>, With<Player>)>,
//body_pane_query:Query<&InventoryUiBodyPane> ,
mut ui_icon_node_query: Query<(
&mut Visibility,
&mut UiIconComponent,
&InventoryUiEquipmentSlot,
)>,
) {
let Some(item_comp) = player_query.get_single().ok() else {
return;
};
//render equipped stuff like weapons that are on you !!
for (mut visibility, mut icon_comp, equipment_ui_slot) in ui_icon_node_query.iter_mut() {
//hide first as teh default case ! .
*visibility = Visibility::Hidden;
// info!("update_inventory_menu_equipment_icons 1 ");
let Some(item_in_slot) = item_comp.items.get(
&equipment_ui_slot
.get_equipment_type()
.get_inventory_slot_index(),
) else {
continue;
};
let item_type_name = &item_in_slot.item_type_name;
icon_comp.icon_source = Some(UiIconSource::ItemType(item_type_name.clone()));
*visibility = Visibility::Inherited;
}
}
fn handle_equipment_slot_interactions(
mut inventory_menu_query: Query<&mut InventoryMenuComponent>,
interaction_query: Query<
(&Interaction, Option<&InventoryUiEquipmentSlot> , &InventoryUiInventorySlot ),
(Changed<Interaction>, With<Button>),
>,
mut inventory_event_writer: EventWriter<InventoryEvent>
) {
for (interaction, ui_equipment_slot, ui_inventory_slot ) in &interaction_query {
//let mut text = text_query.get_mut(children[0]).unwrap();
match *interaction {
Interaction::Pressed => {
let Some(mut inventory_menu_comp) = inventory_menu_query.get_single_mut().ok() else {continue};
match inventory_menu_comp.grabbed_inventory_slot.clone() {
Some(grabbed_inv_slot_index) => {
info!("should swap inv slots... {:?}", grabbed_inv_slot_index);
//emit an event here !!?
let target_slot_index:usize = ui_inventory_slot.0; //implement me !!!
inventory_event_writer.send(InventoryEvent::SwapItemSlots{
source_slot_index: grabbed_inv_slot_index,
target_slot_index: target_slot_index
}) ;
inventory_menu_comp.grabbed_inventory_slot = None;
},
None =>{
if let Some(ui_equipment_slot) = ui_equipment_slot {
info!("grabbed_inventory_slot {:?}", ui_equipment_slot);
inventory_menu_comp.grabbed_inventory_slot = Some( ui_equipment_slot.get_equipment_type().get_inventory_slot_index() );
}
}
}
}
_ => {}
}
}
}
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
}
}
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;
}
//}
}
@ethereumdegen
Copy link
Author

image

@ethereumdegen
Copy link
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