leafwing_input_manager/
plugin.rs

1//! Contains the main plugin exported by this crate.
2
3use core::hash::Hash;
4use core::marker::PhantomData;
5use std::fmt::Debug;
6
7use bevy::app::{App, FixedPostUpdate, Plugin, RunFixedMainLoop};
8use bevy::input::InputSystems;
9#[cfg(feature = "picking")]
10use bevy::picking::PickingSystems;
11use bevy::prelude::*;
12use bevy::reflect::TypePath;
13#[cfg(feature = "ui")]
14use bevy::ui::UiSystems;
15use updating::CentralInputStore;
16
17use crate::action_state::{ActionState, ButtonData};
18use crate::clashing_inputs::ClashStrategy;
19use crate::input_map::InputMap;
20use crate::input_processing::*;
21use crate::prelude::updating::register_standard_input_kinds;
22#[cfg(feature = "timing")]
23use crate::timing::Timing;
24use crate::user_input::*;
25use crate::Actionlike;
26
27/// A [`Plugin`] that collects [`ButtonInput`] from disparate sources,
28/// producing an [`ActionState`] that can be conveniently checked
29///
30/// This plugin needs to be passed in an [`Actionlike`] enum type that you've created for your game.
31/// Each variant represents a "virtual button" whose state is stored in an [`ActionState`] struct.
32///
33/// In order to control entities via actions using this crate, you need:
34///  - an [`InputMap`] component, which stores an entity-specific mapping between the assorted input streams and an internal representation of "actions"
35///  - an [`ActionState`] component, which stores the current input state for that entity in a source-agnostic fashion
36///
37/// If you have more than one distinct type of action (e.g., menu actions, camera actions, and player actions),
38/// consider creating multiple `Actionlike` enums
39/// and adding a copy of this plugin for each `Actionlike` type.
40///
41/// All actions can be dynamically enabled or disabled by calling the relevant methods on
42/// `ActionState<A>`. This can be useful when working with states to pause the game, navigate
43/// menus, and so on.
44///
45/// ## Systems
46///
47/// **WARNING:** These systems run during [`PreUpdate`].
48/// If you have systems that care about inputs and actions that also run during this stage,
49/// you must define an ordering between your systems or behavior will be very erratic.
50/// The stable system sets for these systems are available under [`InputManagerSystem`] enum.
51///
52/// Complete list:
53///
54/// - [`tick_action_state`](crate::systems::tick_action_state), which resets the `pressed` and `just_pressed` fields of the [`ActionState`] each frame
55/// - [`update_action_state`](crate::systems::update_action_state), which collects [`ButtonInput`] resources to update the [`ActionState`]
56pub struct InputManagerPlugin<A: Actionlike> {
57    _phantom: PhantomData<A>,
58    machine: Machine,
59}
60
61// Deriving default induces an undesired bound on the generic
62impl<A: Actionlike> Default for InputManagerPlugin<A> {
63    fn default() -> Self {
64        Self {
65            _phantom: PhantomData,
66            machine: Machine::Client,
67        }
68    }
69}
70
71impl<A: Actionlike> InputManagerPlugin<A> {
72    /// Creates a version of the plugin intended to run on the server
73    ///
74    /// Inputs will not be processed; instead, [`ActionState`]
75    /// should be copied directly from the state provided by the client,
76    /// or constructed from [`ActionDiff`](crate::action_diff::ActionDiff) message streams.
77    #[must_use]
78    pub fn server() -> Self {
79        Self {
80            _phantom: PhantomData,
81            machine: Machine::Server,
82        }
83    }
84}
85
86/// Which machine is this plugin running on?
87enum Machine {
88    Server,
89    Client,
90}
91
92impl<A: Actionlike + TypePath + bevy::reflect::GetTypeRegistration> Plugin
93    for InputManagerPlugin<A>
94{
95    fn build(&self, app: &mut App) {
96        use crate::systems::*;
97
98        match self.machine {
99            Machine::Client => {
100                if !app.is_plugin_added::<CentralInputStorePlugin>() {
101                    app.add_plugins(CentralInputStorePlugin);
102                }
103
104                // Main schedule
105                app.add_systems(
106                    PreUpdate,
107                    (
108                        tick_action_state::<A>.in_set(TickActionStateSystem::<A>::new()),
109                        clear_central_input_store,
110                    )
111                        .in_set(InputManagerSystem::Tick)
112                        .before(InputManagerSystem::Update),
113                )
114                .add_systems(PostUpdate, release_on_input_map_removed::<A>);
115
116                app.add_systems(
117                    PreUpdate,
118                    update_action_state::<A>.in_set(InputManagerSystem::Update),
119                );
120
121                app.configure_sets(
122                    PreUpdate,
123                    InputManagerSystem::ManualControl.after(InputManagerSystem::Update),
124                );
125
126                app.configure_sets(
127                    PreUpdate,
128                    InputManagerSystem::Unify
129                        .after(InputManagerSystem::Filter)
130                        .after(InputSystems),
131                );
132
133                app.configure_sets(
134                    PreUpdate,
135                    InputManagerSystem::Update
136                        .after(InputSystems)
137                        .after(InputManagerSystem::Unify),
138                );
139
140                #[cfg(feature = "ui")]
141                app.add_systems(
142                    PreUpdate,
143                    filter_captured_input
144                        .before(update_action_state::<A>)
145                        .in_set(InputManagerSystem::Filter),
146                );
147
148                #[cfg(feature = "ui")]
149                app.configure_sets(
150                    PreUpdate,
151                    InputManagerSystem::Filter.after(UiSystems::Focus),
152                );
153
154                #[cfg(feature = "ui")]
155                app.configure_sets(
156                    PreUpdate,
157                    InputManagerSystem::ManualControl
158                        .after(InputManagerSystem::Tick)
159                        // Must run after the system is updated from inputs, or it will be forcibly released due to the inputs
160                        // not being pressed
161                        .after(InputManagerSystem::Update)
162                        .after(UiSystems::Focus)
163                        .after(InputSystems),
164                );
165
166                #[cfg(feature = "picking")]
167                app.configure_sets(
168                    PreUpdate,
169                    InputManagerSystem::Update.before(PickingSystems::Hover),
170                );
171
172                // FixedMain schedule
173                app.add_systems(
174                    RunFixedMainLoop,
175                    (
176                        swap_to_fixed_update::<A>,
177                        // we want to update the ActionState only once, even if the FixedMain schedule runs multiple times
178                        update_action_state::<A>,
179                    )
180                        .chain()
181                        .in_set(RunFixedMainLoopSystems::BeforeFixedMainLoop),
182                );
183
184                app.add_systems(FixedPostUpdate, release_on_input_map_removed::<A>);
185                app.add_systems(
186                    FixedPostUpdate,
187                    tick_action_state::<A>
188                        .in_set(TickActionStateSystem::<A>::new())
189                        .in_set(InputManagerSystem::Tick)
190                        .before(InputManagerSystem::Update),
191                );
192                app.add_systems(
193                    RunFixedMainLoop,
194                    swap_to_update::<A>.in_set(RunFixedMainLoopSystems::AfterFixedMainLoop),
195                );
196            }
197            Machine::Server => {
198                app.add_systems(
199                    PreUpdate,
200                    tick_action_state::<A>
201                        .in_set(TickActionStateSystem::<A>::new())
202                        .in_set(InputManagerSystem::Tick),
203                );
204            }
205        };
206
207        #[cfg(feature = "mouse")]
208        app.register_buttonlike_input::<MouseButton>()
209            .register_buttonlike_input::<MouseMoveDirection>()
210            .register_buttonlike_input::<MouseButton>()
211            .register_axislike_input::<MouseMoveAxis>()
212            .register_dual_axislike_input::<MouseMove>()
213            .register_buttonlike_input::<MouseScrollDirection>()
214            .register_axislike_input::<MouseScrollAxis>()
215            .register_dual_axislike_input::<MouseScroll>();
216
217        #[cfg(feature = "keyboard")]
218        app.register_buttonlike_input::<KeyCode>()
219            .register_buttonlike_input::<ModifierKey>();
220
221        #[cfg(feature = "gamepad")]
222        app.register_buttonlike_input::<GamepadControlDirection>()
223            .register_axislike_input::<GamepadControlAxis>()
224            .register_dual_axislike_input::<GamepadStick>()
225            .register_buttonlike_input::<GamepadButton>();
226
227        // Virtual Axes
228        app.register_axislike_input::<VirtualAxis>()
229            .register_dual_axislike_input::<VirtualDPad>()
230            .register_triple_axislike_input::<VirtualDPad3D>();
231
232        // Chords
233        app.register_buttonlike_input::<ButtonlikeChord>()
234            .register_axislike_input::<AxislikeChord>()
235            .register_dual_axislike_input::<DualAxislikeChord>()
236            .register_triple_axislike_input::<TripleAxislikeChord>();
237
238        // General-purpose reflection
239        app.register_type::<ActionState<A>>()
240            .register_type::<InputMap<A>>()
241            .register_type::<ButtonData>()
242            .register_type::<ActionState<A>>()
243            .register_type::<CentralInputStore>();
244
245        // Processors
246        app.register_type::<AxisProcessor>()
247            .register_type::<AxisBounds>()
248            .register_type::<AxisExclusion>()
249            .register_type::<AxisDeadZone>()
250            .register_type::<DualAxisProcessor>()
251            .register_type::<DualAxisInverted>()
252            .register_type::<DualAxisSensitivity>()
253            .register_type::<DualAxisBounds>()
254            .register_type::<DualAxisExclusion>()
255            .register_type::<DualAxisDeadZone>()
256            .register_type::<CircleBounds>()
257            .register_type::<CircleExclusion>()
258            .register_type::<CircleDeadZone>();
259
260        // Resources
261        app.init_resource::<ClashStrategy>();
262
263        #[cfg(feature = "timing")]
264        app.register_type::<Timing>();
265    }
266}
267
268/// [`SystemSet`]s for the [`crate::systems`] used by this crate
269///
270/// `Reset` must occur before `Update`
271#[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)]
272pub enum InputManagerSystem {
273    /// Advances action timers.
274    ///
275    /// Cleans up the state of the input manager, clearing `just_pressed` and `just_released`
276    Tick,
277    /// Accumulates various input message streams into a total delta for the frame.
278    Accumulate,
279    /// Filters out inputs that are captured by UI elements.
280    Filter,
281    /// Gathers all of the input data into the [`CentralInputStore`] resource.
282    Unify,
283    /// Collects input data to update the [`ActionState`].
284    ///
285    /// See [`UpdateableUserInput`](crate::user_input::updating) for more information.
286    Update,
287    /// Manually control the [`ActionState`]
288    ///
289    /// Must run after [`InputManagerSystem::Update`] or the action state will be overridden
290    ManualControl,
291}
292
293#[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)]
294/// [`SystemSet`] for the [`tick_action_state`](crate::systems::tick_action_state) system, is a child set of [`InputManagerSystem::Tick`].
295pub struct TickActionStateSystem<A: Actionlike> {
296    phantom_data: PhantomData<A>,
297}
298
299impl<A: Actionlike> TickActionStateSystem<A> {
300    /// Creates a [`TickActionStateSystem`] set instance.
301    pub fn new() -> Self {
302        Self {
303            phantom_data: PhantomData,
304        }
305    }
306}
307
308impl<A: Actionlike> Default for TickActionStateSystem<A> {
309    fn default() -> Self {
310        Self::new()
311    }
312}
313
314/// A plugin that keeps track of all inputs in a central store.
315///
316/// This plugin is added by default by [`InputManagerPlugin`],
317/// and will register all of the standard [`UserInput`]s.
318///
319/// To add more inputs, call [`crate::user_input::updating::InputRegistration::register_input_kind`] via [`App`] during [`App`] setup.
320pub struct CentralInputStorePlugin;
321
322impl Plugin for CentralInputStorePlugin {
323    fn build(&self, app: &mut App) {
324        app.insert_resource(CentralInputStore::default());
325
326        register_standard_input_kinds(app);
327
328        app.configure_sets(PreUpdate, InputManagerSystem::Unify.after(InputSystems));
329    }
330}