Skip to main content

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