Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add example for run conditions #7652

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,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"
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
97 changes: 97 additions & 0 deletions examples/ecs/run_conditions.rs
Original file line number Diff line number Diff line change
@@ -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::<InputCounter>()
.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::<InputCounter>())
LiamGallagher737 marked this conversation as resolved.
Show resolved Hide resolved
// 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<Res<InputCounter>>| {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I correct in thinking that once #7605 in merged we could do something like this?

.run_if(resource_exists::<InputCounter>())
.and_then(|counter: Res<InputCounter>| counter.is_changed() && !counter.is_added())

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, precisely :) Interestingly, the short circuiting nature would make sure that the second run condition doesn't panic. That's nice.

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<Input<KeyCode>>,
mouse_button_input: Res<Input<MouseButton>>,
touch_input: Res<Touches>,
) -> 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<f32>, Res<Time>) -> bool {
move |mut timer: Local<f32>, time: Res<Time>| {
// Tick the timer
*timer += time.delta_seconds();
// Return true if the timer has passed the time
*timer >= t
}
}

/// SYSTEM: Increment the input counter
/// Notice how we can take just the `ResMut` and not have to wrap
/// it in an option incase it hasen't been initialized, this is becuase
/// it has a run codition that checks if the `InputCounter` resource exsists
fn increment_input_counter(mut counter: ResMut<InputCounter>) {
LiamGallagher737 marked this conversation as resolved.
Show resolved Hide resolved
counter.0 += 1;
}

/// SYSTEM: Print the input counter
fn print_input_counter(counter: Res<InputCounter>) {
println!("Input counter: {}", counter.0);
}

/// SYSTEM: Adds the input counter resource
fn print_time_message() {
println!("It has been more than 2 seconds since the program started and less than 2.5 seconds");
}