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 2 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 @@ -929,6 +929,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
98 changes: 98 additions & 0 deletions examples/ecs/run_conditions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! 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.set(bevy::render::RenderPlugin {
wgpu_settings: bevy::render::settings::WgpuSettings {
backends: Some(bevy::render::settings::Backends::PRIMARY),
..Default::default()
},
}))
.add_system(
increment_input_counter
// The common conditions module has a few useful run conditions
LiamGallagher737 marked this conversation as resolved.
Show resolved Hide resolved
// for checking resources and states, these are included in 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
LiamGallagher737 marked this conversation as resolved.
Show resolved Hide resolved
.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
LiamGallagher737 marked this conversation as resolved.
Show resolved Hide resolved
.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(
add_input_counter
// This is a custom generator function that returns a run
// condition, must like the common conditions module.
// It will only return true once 2 seconds has passed
LiamGallagher737 marked this conversation as resolved.
Show resolved Hide resolved
.run_if(time_passed(2.0)),
)
.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
LiamGallagher737 marked this conversation as resolved.
Show resolved Hide resolved
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 generator fuction that returns a closure which meets all the run condition rules
/// This is useful becuase you can reuse the same run condition but with different variables
/// This is how the common conditions module works
LiamGallagher737 marked this conversation as resolved.
Show resolved Hide resolved
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: Adds the input counter resource
fn add_input_counter(mut commands: Commands) {
LiamGallagher737 marked this conversation as resolved.
Show resolved Hide resolved
commands.init_resource::<InputCounter>();
}

/// 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);
}