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::InputSystem;
9#[cfg(feature = "picking")]
10use bevy::picking::PickSet;
11use bevy::prelude::*;
12use bevy::reflect::TypePath;
13#[cfg(feature = "ui")]
14use bevy::ui::UiSystem;
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/// Each [`InputManagerBundle`](crate::InputManagerBundle) contains:
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) event 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(InputSystem),
131                );
132
133                app.configure_sets(
134                    PreUpdate,
135                    InputManagerSystem::Update
136                        .after(InputSystem)
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(PreUpdate, InputManagerSystem::Filter.after(UiSystem::Focus));
150
151                #[cfg(feature = "ui")]
152                app.configure_sets(
153                    PreUpdate,
154                    InputManagerSystem::ManualControl
155                        .after(InputManagerSystem::Tick)
156                        // Must run after the system is updated from inputs, or it will be forcibly released due to the inputs
157                        // not being pressed
158                        .after(InputManagerSystem::Update)
159                        .after(UiSystem::Focus)
160                        .after(InputSystem),
161                );
162
163                #[cfg(feature = "picking")]
164                app.configure_sets(PreUpdate, InputManagerSystem::Update.before(PickSet::Hover));
165
166                // FixedMain schedule
167                app.add_systems(
168                    RunFixedMainLoop,
169                    (
170                        swap_to_fixed_update::<A>,
171                        // we want to update the ActionState only once, even if the FixedMain schedule runs multiple times
172                        update_action_state::<A>,
173                    )
174                        .chain()
175                        .in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop),
176                );
177
178                app.add_systems(FixedPostUpdate, release_on_input_map_removed::<A>);
179                app.add_systems(
180                    FixedPostUpdate,
181                    tick_action_state::<A>
182                        .in_set(TickActionStateSystem::<A>::new())
183                        .in_set(InputManagerSystem::Tick)
184                        .before(InputManagerSystem::Update),
185                );
186                app.add_systems(
187                    RunFixedMainLoop,
188                    swap_to_update::<A>.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop),
189                );
190            }
191            Machine::Server => {
192                app.add_systems(
193                    PreUpdate,
194                    tick_action_state::<A>
195                        .in_set(TickActionStateSystem::<A>::new())
196                        .in_set(InputManagerSystem::Tick),
197                );
198            }
199        };
200
201        #[cfg(feature = "mouse")]
202        app.register_buttonlike_input::<MouseButton>()
203            .register_buttonlike_input::<MouseMoveDirection>()
204            .register_buttonlike_input::<MouseButton>()
205            .register_axislike_input::<MouseMoveAxis>()
206            .register_dual_axislike_input::<MouseMove>()
207            .register_buttonlike_input::<MouseScrollDirection>()
208            .register_axislike_input::<MouseScrollAxis>()
209            .register_dual_axislike_input::<MouseScroll>();
210
211        #[cfg(feature = "keyboard")]
212        app.register_buttonlike_input::<KeyCode>()
213            .register_buttonlike_input::<ModifierKey>();
214
215        #[cfg(feature = "gamepad")]
216        app.register_buttonlike_input::<GamepadControlDirection>()
217            .register_axislike_input::<GamepadControlAxis>()
218            .register_dual_axislike_input::<GamepadStick>()
219            .register_buttonlike_input::<GamepadButton>();
220
221        // Virtual Axes
222        app.register_axislike_input::<VirtualAxis>()
223            .register_dual_axislike_input::<VirtualDPad>()
224            .register_triple_axislike_input::<VirtualDPad3D>();
225
226        // Chords
227        app.register_buttonlike_input::<ButtonlikeChord>()
228            .register_axislike_input::<AxislikeChord>()
229            .register_dual_axislike_input::<DualAxislikeChord>()
230            .register_triple_axislike_input::<TripleAxislikeChord>();
231
232        // General-purpose reflection
233        app.register_type::<ActionState<A>>()
234            .register_type::<InputMap<A>>()
235            .register_type::<ButtonData>()
236            .register_type::<ActionState<A>>()
237            .register_type::<CentralInputStore>();
238
239        // Processors
240        app.register_type::<AxisProcessor>()
241            .register_type::<AxisBounds>()
242            .register_type::<AxisExclusion>()
243            .register_type::<AxisDeadZone>()
244            .register_type::<DualAxisProcessor>()
245            .register_type::<DualAxisInverted>()
246            .register_type::<DualAxisSensitivity>()
247            .register_type::<DualAxisBounds>()
248            .register_type::<DualAxisExclusion>()
249            .register_type::<DualAxisDeadZone>()
250            .register_type::<CircleBounds>()
251            .register_type::<CircleExclusion>()
252            .register_type::<CircleDeadZone>();
253
254        // Resources
255        app.init_resource::<ClashStrategy>();
256
257        #[cfg(feature = "timing")]
258        app.register_type::<Timing>();
259    }
260}
261
262/// [`SystemSet`]s for the [`crate::systems`] used by this crate
263///
264/// `Reset` must occur before `Update`
265#[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)]
266pub enum InputManagerSystem {
267    /// Advances action timers.
268    ///
269    /// Cleans up the state of the input manager, clearing `just_pressed` and `just_released`
270    Tick,
271    /// Accumulates various input event streams into a total delta for the frame.
272    Accumulate,
273    /// Filters out inputs that are captured by UI elements.
274    Filter,
275    /// Gathers all of the input data into the [`CentralInputStore`] resource.
276    Unify,
277    /// Collects input data to update the [`ActionState`].
278    ///
279    /// See [`UpdateableUserInput`](crate::user_input::updating) for more information.
280    Update,
281    /// Manually control the [`ActionState`]
282    ///
283    /// Must run after [`InputManagerSystem::Update`] or the action state will be overridden
284    ManualControl,
285}
286
287#[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)]
288/// [`SystemSet`] for the [`tick_action_state`](crate::systems::tick_action_state) system, is a child set of [`InputManagerSystem::Tick`].
289pub struct TickActionStateSystem<A: Actionlike> {
290    phantom_data: PhantomData<A>,
291}
292
293impl<A: Actionlike> TickActionStateSystem<A> {
294    /// Creates a [`TickActionStateSystem`] set instance.
295    pub fn new() -> Self {
296        Self {
297            phantom_data: PhantomData,
298        }
299    }
300}
301
302impl<A: Actionlike> Default for TickActionStateSystem<A> {
303    fn default() -> Self {
304        Self::new()
305    }
306}
307
308/// A plugin that keeps track of all inputs in a central store.
309///
310/// This plugin is added by default by [`InputManagerPlugin`],
311/// and will register all of the standard [`UserInput`]s.
312///
313/// To add more inputs, call [`crate::user_input::updating::InputRegistration::register_input_kind`] via [`App`] during [`App`] setup.
314pub struct CentralInputStorePlugin;
315
316impl Plugin for CentralInputStorePlugin {
317    fn build(&self, app: &mut App) {
318        app.insert_resource(CentralInputStore::default());
319
320        register_standard_input_kinds(app);
321
322        app.configure_sets(PreUpdate, InputManagerSystem::Unify.after(InputSystem));
323    }
324}