From 9ec7e869d6e0034b7ce74158fb202dbb843a4692 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Fri, 24 Feb 2023 13:15:38 +0100 Subject: [PATCH] add common run conditions to bevy_input --- crates/bevy_input/Cargo.toml | 3 + crates/bevy_input/src/common_conditions.rs | 97 ++++++++++++++++++++ crates/bevy_input/src/lib.rs | 2 + examples/ecs/run_conditions.rs | 102 ++++----------------- 4 files changed, 119 insertions(+), 85 deletions(-) create mode 100644 crates/bevy_input/src/common_conditions.rs diff --git a/crates/bevy_input/Cargo.toml b/crates/bevy_input/Cargo.toml index 4dcb1ea340e72..57d52d135ce28 100644 --- a/crates/bevy_input/Cargo.toml +++ b/crates/bevy_input/Cargo.toml @@ -23,3 +23,6 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.9.0", features = ["glam" # other serde = { version = "1", features = ["derive"], optional = true } thiserror = "1.0" + +[dev-dependencies] +bevy = { path = "../../", version = "0.9.0" } \ No newline at end of file diff --git a/crates/bevy_input/src/common_conditions.rs b/crates/bevy_input/src/common_conditions.rs new file mode 100644 index 0000000000000..efae7a5837636 --- /dev/null +++ b/crates/bevy_input/src/common_conditions.rs @@ -0,0 +1,97 @@ +use crate::Input; +use bevy_ecs::system::Res; +use std::hash::Hash; + +/// Stateful run condition that can be toggled via a input press using [`Input::just_pressed`]. +/// +/// ```rust,no_run +/// use bevy::prelude::*; +/// use bevy::input::common_conditions::input_toggle_active; +/// +/// fn main() { +/// App::new() +/// .add_plugins(DefaultPlugins) +/// .add_system(pause_menu.run_if(input_toggle_active(false, KeyCode::Escape))) +/// .run(); +/// } +/// +/// fn pause_menu() { +/// println!("in pause menu"); +/// } +/// ``` +/// +/// If you want other system to be able to access whether the toggled state is active, +/// you should use a custom resource or a state for that: +/// ```rust,no_run +/// use bevy::prelude::*; +/// use bevy::input::common_conditions::input_toggle_active; +/// +/// #[derive(Resource, Default)] +/// struct Paused(bool); +/// +/// fn main() { +/// App::new() +/// .add_plugins(DefaultPlugins) +/// .init_resource::() +/// .add_system(pause_menu.run_if(|paused: Res| paused.0)) +/// .run(); +/// } +/// +/// fn update_pause_state(mut paused: ResMut, input: Input) { +/// if input.just_pressed(KeyCode::Escape) { +/// paused.0 = !paused.0; +/// } +/// } +/// +/// fn pause_menu() { +/// println!("in pause menu"); +/// } +/// +/// ``` +pub fn input_toggle_active(default: bool, input: T) -> impl FnMut(Res>) -> bool +where + T: Copy + Eq + Hash + Send + Sync + 'static, +{ + let mut active = default; + move |inputs: Res>| { + active ^= inputs.just_pressed(input); + active + } +} + +/// Run condition that is active if [`Input::pressed`] is true for the given input. +pub fn input_pressed(input: T) -> impl FnMut(Res>) -> bool +where + T: Copy + Eq + Hash + Send + Sync + 'static, +{ + move |inputs: Res>| inputs.pressed(input) +} + +/// Run condition that is active if [`Input::just_pressed`] is true for the given input. +/// +/// ```rust,no_run +/// use bevy::prelude::*; +/// use bevy::input::common_conditions::input_just_pressed; +/// fn main() { +/// App::new() +/// .add_plugins(DefaultPlugins) +/// .add_system(jump.run_if(input_just_pressed(KeyCode::Space))) +/// .run(); +/// } +/// +/// # fn jump() {} +/// ``` +pub fn input_just_pressed(input: T) -> impl FnMut(Res>) -> bool +where + T: Copy + Eq + Hash + Send + Sync + 'static, +{ + move |inputs: Res>| inputs.just_pressed(input) +} + +/// Run condition that is active if [`Input::just_released`] is true for the given input. +pub fn input_just_released(input: T) -> impl FnMut(Res>) -> bool +where + T: Copy + Eq + Hash + Send + Sync + 'static, +{ + move |inputs: Res>| inputs.just_released(input) +} diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index cbf4bf0e7c660..7fc988171cf56 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -1,4 +1,6 @@ mod axis; +/// Common run conditions +pub mod common_conditions; pub mod gamepad; mod input; pub mod keyboard; diff --git a/examples/ecs/run_conditions.rs b/examples/ecs/run_conditions.rs index 76a13319b8881..ef8aca5496f76 100644 --- a/examples/ecs/run_conditions.rs +++ b/examples/ecs/run_conditions.rs @@ -1,99 +1,31 @@ //! This example demonstrates how to use run conditions to control when systems run. +use std::hash::Hash; + use bevy::prelude::*; fn main() { - println!(); - println!("For the first 2 seconds you will not be able to increment the counter"); - println!("Once that time has passed you can press space, enter, left mouse, right mouse or touch the screen to increment the counter"); - println!(); - App::new() .add_plugins(DefaultPlugins) - .init_resource::() - .add_system( - increment_input_counter - // The common_conditions module has a few useful run conditions - // for checking resources and states. These are included in the prelude. - .run_if(resource_exists::()) - // This is a custom run condition, defined using a system that returns - // a `bool` and which has read-only `SystemParam`s. - // Both run conditions must return `true` in order for the system to run. - // Note that this second run condition will be evaluated even if the first returns `false`. - .run_if(has_user_input), - ) - .add_system( - print_input_counter - // `.and_then()` is a run condition combinator that only evaluates the second condition - // if the first condition returns `true`. This behavior is known as "short-circuiting", - // and is how the `&&` operator works in Rust (as well as most C-family languages). - // In this case, the short-circuiting behavior prevents the second run condition from - // panicking if the `InputCounter` resource has not been initialized. - .run_if(resource_exists::().and_then( - // This is a custom run condition in the form of a closure. - // This is useful for small, simple run conditions you don't need to reuse. - // All the normal rules still apply: all parameters must be read only except for local parameters. - |counter: Res| counter.is_changed() && !counter.is_added(), - )), - ) - .add_system( - print_time_message - // This function returns a custom run condition, much like the common conditions module. - // It will only return true once 2 seconds have passed. - .run_if(time_passed(2.0)) - // You can use the `not` condition from the common_conditions module - // to inverse a run condition. In this case it will return true if - // less than 2.5 seconds have elapsed since the app started. - .run_if(not(time_passed(2.5))), - ) + .add_system(print.run_if(input_toggle_active(true, KeyCode::Escape))) + .add_system(print_2.after(print)) .run(); } - -#[derive(Resource, Default)] -struct InputCounter(usize); - -/// Return true if any of the defined inputs were just pressed. -/// This is a custom run condition, it can take any normal system parameters as long as -/// they are read only (except for local parameters which can be mutable). -/// It returns a bool which determines if the system should run. -fn has_user_input( - keyboard_input: Res>, - mouse_button_input: Res>, - touch_input: Res, -) -> bool { - keyboard_input.just_pressed(KeyCode::Space) - || keyboard_input.just_pressed(KeyCode::Return) - || mouse_button_input.just_pressed(MouseButton::Left) - || mouse_button_input.just_pressed(MouseButton::Right) - || touch_input.any_just_pressed() -} - -/// This is a function that returns a closure which can be used as a run condition. -/// This is useful because you can reuse the same run condition but with different variables. -/// This is how the common conditions module works. -fn time_passed(t: f32) -> impl FnMut(Local, Res