diff --git a/Cargo.toml b/Cargo.toml index b570af77bb33b..771dd72970cfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -938,6 +938,16 @@ description = "Query for entities that had a specific component removed earlier category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "run_conditions" +path = "examples/ecs/run_conditions.rs" + +[package.metadata.example.run_conditions] +name = "Run Conditions" +description = "Run systems only when one or multiple conditions are met" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "startup_system" path = "examples/ecs/startup_system.rs" diff --git a/examples/README.md b/examples/README.md index cfa05e8b44756..f335db7f9a37d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -205,6 +205,7 @@ Example | Description [Nondeterministic System Order](../examples/ecs/nondeterministic_system_order.rs) | Systems run in paralell, but their order isn't always deteriministic. Here's how to detect and fix this. [Parallel Query](../examples/ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator` [Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed earlier in the current frame +[Run Conditions](../examples/ecs/run_conditions.rs) | Run systems only when one or multiple conditions are met [Startup System](../examples/ecs/startup_system.rs) | Demonstrates a startup system (one that runs once when the app starts up) [State](../examples/ecs/state.rs) | Illustrates how to use States to control transitioning from a Menu state to an InGame state [System Closure](../examples/ecs/system_closure.rs) | Show how to use closures as systems, and how to configure `Local` variables by capturing external state diff --git a/examples/ecs/run_conditions.rs b/examples/ecs/run_conditions.rs new file mode 100644 index 0000000000000..a0147d93ae1e9 --- /dev/null +++ b/examples/ecs/run_conditions.rs @@ -0,0 +1,97 @@ +//! This example demonstrates how to use run conditions to control when systems run. + +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 our custom run condition. Both this and the + // above condition must be true for the system to run. + .run_if(has_user_input), + ) + .add_system( + print_input_counter + // This is also a custom run condition but this time 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. + // In this case we will only run if the input counter resource exists and has changed but not just been added. + .run_if(|res: Option>| { + if let Some(counter) = res { + counter.is_changed() && !counter.is_added() + } else { + false + } + }), + ) + .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))), + ) + .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