generated from Leafwing-Studios/template-repo
-
Notifications
You must be signed in to change notification settings - Fork 120
/
arpg_indirection.rs
140 lines (127 loc) · 4.51 KB
/
arpg_indirection.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! In some genres (commonly Diablo-like action RPGs), it is common to have two layers of actions.
//! The first layer corresponds to a "slot",
//! which can then be set to a second-layer "ability" of the player's choice
//!
//! This example demonstrates how to model that pattern by copying [`ActionData`]
//! between two distinct [`ActionState`] components.
use bevy::prelude::*;
use bevy::utils::HashMap;
use leafwing_input_manager::plugin::InputManagerSystem;
use leafwing_input_manager::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// These are the generic "slots" that make up the player's action bar
.add_plugins(InputManagerPlugin::<Slot>::default())
// These are the actual abilities used by our characters
.add_plugins(InputManagerPlugin::<Ability>::default())
.add_systems(Startup, spawn_player)
// This system coordinates the state of our two actions
.add_systems(
PreUpdate,
copy_action_state.after(InputManagerSystem::ManualControl),
)
// Try it out, using QWER / left click / right click!
.add_systems(Update, report_abilities_used)
.run();
}
#[derive(Actionlike, PartialEq, Eq, Clone, Debug, Hash, Copy, Reflect)]
enum Slot {
Primary,
Secondary,
Ability1,
Ability2,
Ability3,
Ability4,
}
impl Slot {
/// You could use the `strum` crate to derive this automatically!
fn variants() -> impl Iterator<Item = Slot> {
use Slot::*;
[Primary, Secondary, Ability1, Ability2, Ability3, Ability4]
.iter()
.copied()
}
}
// The list of possible abilities is typically longer than the list of slots
#[derive(Actionlike, PartialEq, Eq, Hash, Clone, Debug, Copy, Reflect)]
enum Ability {
Slash,
Shoot,
LightningBolt,
Fireball,
Dash,
Heal,
FrozenOrb,
PolymorphSheep,
}
/// This struct stores which ability corresponds to which slot for a particular player
#[derive(Component, Debug, Default, Deref, DerefMut)]
struct AbilitySlotMap {
map: HashMap<Slot, Ability>,
}
#[derive(Component)]
struct Player;
#[derive(Bundle)]
struct PlayerBundle {
player: Player,
slot_input_map: InputMap<Slot>,
slot_action_state: ActionState<Slot>,
// We do not need an InputMap<Ability> component,
// as abilities are never triggered directly from inputs.
ability_action_state: ActionState<Ability>,
ability_slot_map: AbilitySlotMap,
}
fn spawn_player(mut commands: Commands) {
use KeyCode::*;
// We can control which abilities are stored in each mapping
let mut ability_slot_map = AbilitySlotMap::default();
ability_slot_map.insert(Slot::Primary, Ability::Slash);
ability_slot_map.insert(Slot::Secondary, Ability::Shoot);
ability_slot_map.insert(Slot::Ability1, Ability::FrozenOrb);
// Some slots may be empty!
ability_slot_map.insert(Slot::Ability3, Ability::Dash);
ability_slot_map.insert(Slot::Ability4, Ability::PolymorphSheep);
commands.spawn(PlayerBundle {
player: Player,
slot_input_map: InputMap::new([
(Slot::Ability1, KeyQ),
(Slot::Ability2, KeyW),
(Slot::Ability3, KeyE),
(Slot::Ability4, KeyR),
])
.insert(Slot::Primary, MouseButton::Left)
.insert(Slot::Secondary, MouseButton::Right)
.build(),
slot_action_state: ActionState::default(),
ability_action_state: ActionState::default(),
ability_slot_map,
});
}
fn copy_action_state(
mut query: Query<(
&ActionState<Slot>,
&mut ActionState<Ability>,
&AbilitySlotMap,
)>,
) {
for (slot_state, mut ability_state, ability_slot_map) in query.iter_mut() {
for slot in Slot::variants() {
if let Some(matching_ability) = ability_slot_map.get(&slot) {
// This copies the `ActionData` between the ActionStates,
// including information about how long the buttons have been pressed or released
ability_state.set_action_data(
*matching_ability,
slot_state.action_data(&slot).unwrap().clone(),
);
}
}
}
}
fn report_abilities_used(query: Query<&ActionState<Ability>>) {
for ability_state in query.iter() {
for ability in ability_state.get_just_pressed() {
dbg!(ability);
}
}
}