Tainted \\ Coders

Create reusable run conditions

Last updated:

Create a function that returns an impl Condition<()> type and then use the built-in Bevy run conditions from your function:

#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, States)]
enum AppState {
    #[default]
    MainMenu,
    InGame,
    Paused,
}

fn game_ready() -> impl ReadOnlySystem<In = (), Out = bool> {
    in_state(AppState::MainMenu).and_then(in_state(AppState::InGame))
}

// Condition can be used as a more succinct type
fn score_ready() -> impl Condition<()> {
    resource_exists::<Score>.and_then(game_ready())
}

This lets us use Bevy’s built in run conditions outside of our App definitions.

Bevy’s common run conditions like resource_exists::<T> and in_state are part of the bevy::ecs::schedule::common_conditions.

On the Bevy example on run conditions we can see a somewhat complicated example that chains run conditions together:

#[derive(Resource, Default)]
struct InputCounter(u32);

fn increment_input_counter(mut input_counter: ResMut<InputCounter>) {
    input_counter.0 += 1;
}

fn has_user_input(input_counter: Res<InputCounter>) -> bool {
    input_counter.0 > 0
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_resource::<InputCounter>()
        .add_systems(
            Update,
            increment_input_counter
                .run_if(
                    resource_exists::<InputCounter>
                        .or_else(has_user_input)
                ),
        )
        .run();
}

But extracting these run conditions into a function allows us to write more declarative app definitions that better express the logic of when these systems should run.

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_resource::<Score>()
        .add_systems(
            Update,
            (
                countdown_game_timer.run_if(game_ready),
                update_scoreboard.run_if(score_ready)
            )
        )
        .run();
}

Read more