|
1 | | -use bevy::{ecs::spawn::SpawnIter, prelude::*}; |
| 1 | +use bevy::{ |
| 2 | + ecs::{spawn::SpawnIter, system::SystemParam}, |
| 3 | + input::keyboard::KeyboardInput, |
| 4 | + prelude::*, |
| 5 | +}; |
2 | 6 | use bevy_aseprite_ultra::prelude::*; |
3 | 7 |
|
4 | 8 | use crate::{ |
5 | | - gameplay::{FactorySystems, structure::build::QueueStructureSpawn}, |
| 9 | + gameplay::{ |
| 10 | + FactorySystems, structure::assets::StructureDef, world::construction::StructureConstructed, |
| 11 | + }, |
6 | 12 | screens::Screen, |
7 | 13 | }; |
8 | 14 |
|
9 | | -pub fn plugin(app: &mut App) { |
10 | | - app.register_type::<HotbarItemSelected>(); |
11 | | - app.register_type::<HotbarItemDeselected>(); |
| 15 | +const DIGIT_KEYS: [KeyCode; 9] = [ |
| 16 | + KeyCode::Digit1, |
| 17 | + KeyCode::Digit2, |
| 18 | + KeyCode::Digit3, |
| 19 | + KeyCode::Digit4, |
| 20 | + KeyCode::Digit5, |
| 21 | + KeyCode::Digit6, |
| 22 | + KeyCode::Digit7, |
| 23 | + KeyCode::Digit8, |
| 24 | + KeyCode::Digit9, |
| 25 | +]; |
12 | 26 |
|
13 | | - app.register_type::<HotbarSelection>(); |
| 27 | +pub fn plugin(app: &mut App) { |
| 28 | + app.register_type::<HotbarSlot>(); |
14 | 29 | app.register_type::<HotbarAction>(); |
| 30 | + app.register_type::<HotbarActionOf>(); |
| 31 | + app.register_type::<HotbarActionKind>(); |
15 | 32 | app.register_type::<HotbarShortcut>(); |
16 | 33 |
|
17 | | - app.init_resource::<HotbarSelection>(); |
| 34 | + app.register_type::<HotbarSelectedEntity>(); |
| 35 | + app.init_resource::<HotbarSelectedEntity>(); |
18 | 36 |
|
19 | | - app.add_systems(OnEnter(Screen::Gameplay), spawn_hotbar); |
| 37 | + app.add_event::<HotbarSelectionChanged>(); |
| 38 | + |
| 39 | + app.add_systems( |
| 40 | + OnEnter(Screen::Gameplay), |
| 41 | + (spawn_hotbar, assign_hotbar_items).chain(), |
| 42 | + ); |
20 | 43 |
|
21 | | - app.add_observer(on_hotbar_slot_click); |
22 | | - app.add_observer(on_slot_selected); |
| 44 | + app.add_observer(select_on_click); |
23 | 45 |
|
24 | 46 | app.add_systems( |
25 | 47 | Update, |
26 | 48 | ( |
27 | | - check_for_hotbar_shortcuts.in_set(FactorySystems::Input), |
| 49 | + select_on_keyboard_shortcuts |
| 50 | + .in_set(FactorySystems::Input) |
| 51 | + .run_if(on_event::<KeyboardInput>), |
28 | 52 | highlight_selected_slot.in_set(FactorySystems::UI), |
29 | | - update_icon.in_set(FactorySystems::UI), |
30 | | - deselect_slot.run_if(on_event::<QueueStructureSpawn>), |
| 53 | + deselect_slot.run_if(on_event::<StructureConstructed>), |
31 | 54 | ), |
32 | 55 | ); |
33 | 56 | } |
34 | 57 |
|
35 | | -#[derive(Event, Reflect)] |
36 | | -pub struct SelectHotbarSlot(String); |
| 58 | +#[derive(SystemParam)] |
| 59 | +pub struct HotbarSelection<'w, 's> { |
| 60 | + hotbar_selected_entity: Res<'w, HotbarSelectedEntity>, |
| 61 | + hotbar_actions: Query<'w, 's, &'static HotbarAction>, |
| 62 | + hotbar_action_kind: Query<'w, 's, &'static HotbarActionKind>, |
| 63 | +} |
| 64 | + |
| 65 | +impl HotbarSelection<'_, '_> { |
| 66 | + pub fn action(&self) -> Option<&HotbarActionKind> { |
| 67 | + self.hotbar_selected_entity |
| 68 | + .and_then(|selection| self.hotbar_actions.get(selection).ok()) |
| 69 | + .and_then(|action| self.hotbar_action_kind.get(action.0).ok()) |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +#[derive(SystemParam)] |
| 74 | +struct HotbarSelector<'w> { |
| 75 | + selection: ResMut<'w, HotbarSelectedEntity>, |
| 76 | + events: EventWriter<'w, HotbarSelectionChanged>, |
| 77 | +} |
37 | 78 |
|
38 | | -#[derive(Event, Reflect)] |
39 | | -pub struct HotbarItemSelected(pub String); |
| 79 | +impl HotbarSelector<'_> { |
| 80 | + fn select(&mut self, entity: Option<Entity>) { |
| 81 | + if self.selection.0 == entity { |
| 82 | + if self.selection.is_some() { |
| 83 | + self.events.write(HotbarSelectionChanged { |
| 84 | + previous: self.selection.0, |
| 85 | + current: None, |
| 86 | + }); |
| 87 | + self.selection.0 = None; |
| 88 | + } |
| 89 | + } else { |
| 90 | + self.events.write(HotbarSelectionChanged { |
| 91 | + previous: self.selection.0, |
| 92 | + current: entity, |
| 93 | + }); |
| 94 | + self.selection.0 = entity; |
| 95 | + } |
| 96 | + } |
| 97 | +} |
40 | 98 |
|
41 | | -#[derive(Event, Reflect)] |
42 | | -pub struct HotbarItemDeselected; |
| 99 | +#[derive(Event, Reflect, Debug)] |
| 100 | +pub struct HotbarSelectionChanged { |
| 101 | + pub previous: Option<Entity>, |
| 102 | + pub current: Option<Entity>, |
| 103 | +} |
43 | 104 |
|
44 | | -#[derive(Resource, Default, Reflect)] |
| 105 | +#[derive(Resource, Reflect, Debug, Default, Deref, DerefMut)] |
45 | 106 | #[reflect(Resource)] |
46 | | -pub struct HotbarSelection(pub Option<String>); |
| 107 | +struct HotbarSelectedEntity(Option<Entity>); |
47 | 108 |
|
48 | | -#[derive(Component, Reflect)] |
| 109 | +#[derive(Component, Reflect, Debug, Default)] |
49 | 110 | #[reflect(Component)] |
50 | | -struct HotbarAction(String); |
| 111 | +struct HotbarSlot; |
51 | 112 |
|
52 | | -#[derive(Component, Reflect)] |
| 113 | +#[derive(Component, Reflect, Debug)] |
53 | 114 | #[reflect(Component)] |
54 | | -struct HotbarShortcut(KeyCode); |
| 115 | +#[relationship_target(relationship = HotbarActionOf, linked_spawn)] |
| 116 | +struct HotbarAction(Entity); |
55 | 117 |
|
56 | | -#[derive(Component, Reflect)] |
| 118 | +#[derive(Component, Reflect, Debug)] |
57 | 119 | #[reflect(Component)] |
58 | | -#[require(AseAnimation)] |
59 | | -struct HotbarIcon(String); |
| 120 | +#[relationship(relationship_target = HotbarAction)] |
| 121 | +struct HotbarActionOf(pub Entity); |
| 122 | + |
| 123 | +#[derive(Component, Reflect, Debug)] |
| 124 | +#[reflect(Component)] |
| 125 | +pub enum HotbarActionKind { |
| 126 | + PlaceStructure(Handle<StructureDef>), |
| 127 | +} |
| 128 | + |
| 129 | +#[derive(Component, Reflect, Debug)] |
| 130 | +#[reflect(Component)] |
| 131 | +struct HotbarShortcut(KeyCode); |
60 | 132 |
|
61 | 133 | fn spawn_hotbar(mut commands: Commands) { |
62 | 134 | commands.spawn(( |
63 | | - Name::new("Build Hotbar"), |
| 135 | + Name::new("Hotbar"), |
| 136 | + StateScoped(Screen::Gameplay), |
64 | 137 | Node { |
65 | 138 | position_type: PositionType::Absolute, |
66 | 139 | bottom: Val::Px(8.0), |
67 | | - width: Val::Percent(100.0), |
| 140 | + width: Val::Auto, |
68 | 141 | height: Val::Auto, |
| 142 | + margin: UiRect::axes(Val::Auto, Val::ZERO), |
69 | 143 | justify_content: JustifyContent::Center, |
70 | 144 | align_items: AlignItems::Center, |
71 | 145 | column_gap: Val::Px(8.0), |
72 | 146 | row_gap: Val::Px(8.0), |
73 | 147 | ..default() |
74 | 148 | }, |
75 | 149 | Pickable::IGNORE, |
76 | | - Children::spawn(SpawnIter( |
77 | | - [ |
78 | | - (KeyCode::Digit1, "miner"), |
79 | | - (KeyCode::Digit2, "smelter"), |
80 | | - (KeyCode::Digit3, "constructor"), |
81 | | - ] |
82 | | - .iter() |
83 | | - .map(move |(shortcut, structure_id)| { |
84 | | - ( |
85 | | - Name::new(format!("Hotbar Slot {structure_id:?}")), |
86 | | - Node { |
87 | | - width: Val::Px(64.0), |
88 | | - height: Val::Px(64.0), |
89 | | - border: UiRect::all(Val::Px(4.0)), |
90 | | - ..default() |
91 | | - }, |
92 | | - Pickable::default(), |
93 | | - BorderColor(Color::WHITE), |
94 | | - HotbarShortcut(*shortcut), |
95 | | - HotbarAction(structure_id.to_string()), |
96 | | - children![( |
97 | | - Name::new("Icon"), |
98 | | - ImageNode::default(), |
99 | | - Pickable::IGNORE, |
100 | | - HotbarIcon(structure_id.to_string()), |
101 | | - )], |
102 | | - ) |
103 | | - }), |
104 | | - )), |
| 150 | + Children::spawn(SpawnIter((0..DIGIT_KEYS.len()).map(|i| { |
| 151 | + ( |
| 152 | + Name::new(format!("Hotbar Slot {}", i + 1)), |
| 153 | + Node { |
| 154 | + width: Val::Px(64.0), |
| 155 | + height: Val::Px(64.0), |
| 156 | + border: UiRect::all(Val::Px(4.0)), |
| 157 | + ..default() |
| 158 | + }, |
| 159 | + Pickable::default(), |
| 160 | + BorderColor(Color::WHITE), |
| 161 | + HotbarSlot, |
| 162 | + HotbarShortcut(DIGIT_KEYS[i]), |
| 163 | + ) |
| 164 | + }))), |
105 | 165 | )); |
106 | 166 | } |
107 | 167 |
|
108 | | -fn on_hotbar_slot_click( |
109 | | - trigger: Trigger<Pointer<Click>>, |
110 | | - hotbar_actions: Query<&HotbarAction>, |
| 168 | +fn assign_hotbar_items( |
111 | 169 | mut commands: Commands, |
| 170 | + asset_server: Res<AssetServer>, |
| 171 | + structure_defs: Res<Assets<StructureDef>>, |
| 172 | + query: Query<Entity, With<HotbarShortcut>>, |
112 | 173 | ) { |
113 | | - let Ok(action) = hotbar_actions.get(trigger.target) else { |
114 | | - return; |
115 | | - }; |
116 | | - |
117 | | - commands.trigger(SelectHotbarSlot(action.0.to_owned())); |
| 174 | + for (hotbar_slot, (asset_id, structure_def)) in query.iter().zip(structure_defs.iter()) { |
| 175 | + commands.spawn(( |
| 176 | + Name::new("Hotbar Action"), |
| 177 | + ChildOf(hotbar_slot), |
| 178 | + HotbarActionOf(hotbar_slot), |
| 179 | + HotbarActionKind::PlaceStructure(Handle::Weak(asset_id)), |
| 180 | + ImageNode::default(), |
| 181 | + AseAnimation { |
| 182 | + aseprite: asset_server |
| 183 | + .load(format!("sprites/structures/{}.aseprite", structure_def.id)), |
| 184 | + animation: Animation::tag("work").with_speed(0.0), |
| 185 | + }, |
| 186 | + Pickable::IGNORE, |
| 187 | + )); |
| 188 | + } |
118 | 189 | } |
119 | 190 |
|
120 | | -fn check_for_hotbar_shortcuts( |
121 | | - keys: Res<ButtonInput<KeyCode>>, |
122 | | - hotbar_slots: Query<(&HotbarAction, &HotbarShortcut)>, |
| 191 | +fn highlight_selected_slot( |
123 | 192 | mut commands: Commands, |
| 193 | + current_selection: Res<HotbarSelectedEntity>, |
| 194 | + mut highlighted_selection: Local<Option<Entity>>, |
124 | 195 | ) { |
125 | | - for (action, shortcut) in hotbar_slots { |
126 | | - if !keys.just_pressed(shortcut.0) { |
127 | | - continue; |
128 | | - } |
| 196 | + if let Some(highlighted) = *highlighted_selection { |
| 197 | + commands.entity(highlighted).remove::<BackgroundColor>(); |
| 198 | + } |
129 | 199 |
|
130 | | - commands.trigger(SelectHotbarSlot(action.0.to_owned())); |
| 200 | + if let Some(selection) = current_selection.0 { |
| 201 | + commands |
| 202 | + .entity(selection) |
| 203 | + .insert(BackgroundColor(Color::WHITE)); |
| 204 | + |
| 205 | + *highlighted_selection = Some(selection); |
131 | 206 | } |
132 | 207 | } |
133 | 208 |
|
134 | | -fn on_slot_selected( |
135 | | - trigger: Trigger<SelectHotbarSlot>, |
136 | | - mut hotbar_selection: ResMut<HotbarSelection>, |
137 | | - mut commands: Commands, |
| 209 | +fn select_on_click( |
| 210 | + trigger: Trigger<Pointer<Click>>, |
| 211 | + hotbar_slots: Query<Entity, With<HotbarSlot>>, |
| 212 | + mut hotbar: HotbarSelector, |
138 | 213 | ) { |
139 | | - let event = trigger.event(); |
140 | | - |
141 | | - if hotbar_selection.0 == Some(event.0.to_owned()) { |
142 | | - hotbar_selection.0 = None; |
143 | | - commands.trigger(HotbarItemDeselected); |
144 | | - } else { |
145 | | - hotbar_selection.0 = Some(event.0.to_owned()); |
146 | | - commands.trigger(HotbarItemSelected(event.0.to_owned())); |
147 | | - } |
| 214 | + if hotbar_slots.contains(trigger.target) { |
| 215 | + hotbar.select(Some(trigger.target)); |
| 216 | + }; |
148 | 217 | } |
149 | 218 |
|
150 | | -fn highlight_selected_slot( |
151 | | - mut commands: Commands, |
152 | | - hotbar_slots: Query<(Entity, &HotbarAction)>, |
153 | | - selection: Res<HotbarSelection>, |
| 219 | +fn select_on_keyboard_shortcuts( |
| 220 | + keys: Res<ButtonInput<KeyCode>>, |
| 221 | + hotbar_slots: Query<(Entity, &HotbarShortcut), With<HotbarSlot>>, |
| 222 | + mut hotbar: HotbarSelector, |
154 | 223 | ) { |
155 | | - for (entity, slot) in hotbar_slots { |
156 | | - if selection.0.as_ref().is_some_and(|b| *b == slot.0) { |
157 | | - commands |
158 | | - .entity(entity) |
159 | | - .insert(BackgroundColor(Color::WHITE)); |
160 | | - } else { |
161 | | - commands.entity(entity).remove::<BackgroundColor>(); |
| 224 | + for (entity, shortcut) in hotbar_slots { |
| 225 | + if keys.just_pressed(shortcut.0) { |
| 226 | + hotbar.select(Some(entity)); |
162 | 227 | } |
163 | 228 | } |
164 | 229 | } |
165 | 230 |
|
166 | | -fn deselect_slot(mut hotbar_selection: ResMut<HotbarSelection>, mut commands: Commands) { |
167 | | - hotbar_selection.0 = None; |
168 | | - commands.trigger(HotbarItemDeselected); |
169 | | -} |
170 | | - |
171 | | -fn update_icon( |
172 | | - query: Query<(&mut AseAnimation, &HotbarIcon), Changed<HotbarIcon>>, |
173 | | - asset_server: Res<AssetServer>, |
174 | | -) { |
175 | | - for (mut animation, icon) in query { |
176 | | - animation.aseprite = asset_server.load(format!("sprites/structures/{}.aseprite", icon.0)); |
177 | | - animation.animation = Animation::tag("work").with_speed(0.0); |
178 | | - } |
| 231 | +fn deselect_slot(mut hotbar: HotbarSelector) { |
| 232 | + hotbar.select(None); |
179 | 233 | } |
0 commit comments