Skip to content

Commit 9968f15

Browse files
authored
refactor: rework construction and hotbar to isolate logic (#36)
1 parent 7836640 commit 9968f15

File tree

15 files changed

+425
-302
lines changed

15 files changed

+425
-302
lines changed

assets/sprites/structures/merger.aseprite

Lines changed: 0 additions & 3 deletions
This file was deleted.

assets/sprites/structures/power_pole.aseprite

Lines changed: 0 additions & 3 deletions
This file was deleted.

assets/sprites/structures/windmill.aseprite

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/gameplay/hud/hotbar.rs

Lines changed: 166 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,179 +1,233 @@
1-
use bevy::{ecs::spawn::SpawnIter, prelude::*};
1+
use bevy::{
2+
ecs::{spawn::SpawnIter, system::SystemParam},
3+
input::keyboard::KeyboardInput,
4+
prelude::*,
5+
};
26
use bevy_aseprite_ultra::prelude::*;
37

48
use crate::{
5-
gameplay::{FactorySystems, structure::build::QueueStructureSpawn},
9+
gameplay::{
10+
FactorySystems, structure::assets::StructureDef, world::construction::StructureConstructed,
11+
},
612
screens::Screen,
713
};
814

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+
];
1226

13-
app.register_type::<HotbarSelection>();
27+
pub fn plugin(app: &mut App) {
28+
app.register_type::<HotbarSlot>();
1429
app.register_type::<HotbarAction>();
30+
app.register_type::<HotbarActionOf>();
31+
app.register_type::<HotbarActionKind>();
1532
app.register_type::<HotbarShortcut>();
1633

17-
app.init_resource::<HotbarSelection>();
34+
app.register_type::<HotbarSelectedEntity>();
35+
app.init_resource::<HotbarSelectedEntity>();
1836

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+
);
2043

21-
app.add_observer(on_hotbar_slot_click);
22-
app.add_observer(on_slot_selected);
44+
app.add_observer(select_on_click);
2345

2446
app.add_systems(
2547
Update,
2648
(
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>),
2852
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>),
3154
),
3255
);
3356
}
3457

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+
}
3778

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+
}
4098

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+
}
43104

44-
#[derive(Resource, Default, Reflect)]
105+
#[derive(Resource, Reflect, Debug, Default, Deref, DerefMut)]
45106
#[reflect(Resource)]
46-
pub struct HotbarSelection(pub Option<String>);
107+
struct HotbarSelectedEntity(Option<Entity>);
47108

48-
#[derive(Component, Reflect)]
109+
#[derive(Component, Reflect, Debug, Default)]
49110
#[reflect(Component)]
50-
struct HotbarAction(String);
111+
struct HotbarSlot;
51112

52-
#[derive(Component, Reflect)]
113+
#[derive(Component, Reflect, Debug)]
53114
#[reflect(Component)]
54-
struct HotbarShortcut(KeyCode);
115+
#[relationship_target(relationship = HotbarActionOf, linked_spawn)]
116+
struct HotbarAction(Entity);
55117

56-
#[derive(Component, Reflect)]
118+
#[derive(Component, Reflect, Debug)]
57119
#[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);
60132

61133
fn spawn_hotbar(mut commands: Commands) {
62134
commands.spawn((
63-
Name::new("Build Hotbar"),
135+
Name::new("Hotbar"),
136+
StateScoped(Screen::Gameplay),
64137
Node {
65138
position_type: PositionType::Absolute,
66139
bottom: Val::Px(8.0),
67-
width: Val::Percent(100.0),
140+
width: Val::Auto,
68141
height: Val::Auto,
142+
margin: UiRect::axes(Val::Auto, Val::ZERO),
69143
justify_content: JustifyContent::Center,
70144
align_items: AlignItems::Center,
71145
column_gap: Val::Px(8.0),
72146
row_gap: Val::Px(8.0),
73147
..default()
74148
},
75149
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+
}))),
105165
));
106166
}
107167

108-
fn on_hotbar_slot_click(
109-
trigger: Trigger<Pointer<Click>>,
110-
hotbar_actions: Query<&HotbarAction>,
168+
fn assign_hotbar_items(
111169
mut commands: Commands,
170+
asset_server: Res<AssetServer>,
171+
structure_defs: Res<Assets<StructureDef>>,
172+
query: Query<Entity, With<HotbarShortcut>>,
112173
) {
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+
}
118189
}
119190

120-
fn check_for_hotbar_shortcuts(
121-
keys: Res<ButtonInput<KeyCode>>,
122-
hotbar_slots: Query<(&HotbarAction, &HotbarShortcut)>,
191+
fn highlight_selected_slot(
123192
mut commands: Commands,
193+
current_selection: Res<HotbarSelectedEntity>,
194+
mut highlighted_selection: Local<Option<Entity>>,
124195
) {
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+
}
129199

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);
131206
}
132207
}
133208

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,
138213
) {
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+
};
148217
}
149218

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,
154223
) {
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));
162227
}
163228
}
164229
}
165230

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);
179233
}

src/gameplay/logistics/path.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ pub(super) fn plugin(app: &mut App) {
2424
Update,
2525
(
2626
build_paths
27-
.in_set(FactorySystems::Build)
27+
.in_set(FactorySystems::Construction)
2828
.run_if(on_event::<Pointer<DragDrop>>),
2929
spawn_intersection
30-
.in_set(FactorySystems::Build)
30+
.in_set(FactorySystems::Construction)
3131
.run_if(on_event::<Pointer<Click>>),
3232
),
3333
);

src/gameplay/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub fn plugin(app: &mut App) {
1515
Update,
1616
(
1717
FactorySystems::Input,
18-
FactorySystems::Build,
18+
FactorySystems::Construction,
1919
FactorySystems::Power,
2020
FactorySystems::Logistics,
2121
FactorySystems::Work,
@@ -40,7 +40,7 @@ pub fn plugin(app: &mut App) {
4040
#[derive(SystemSet, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
4141
pub enum FactorySystems {
4242
Input,
43-
Build,
43+
Construction,
4444
Power,
4545
Logistics,
4646
Work,

0 commit comments

Comments
 (0)