Tainted\\Coders

Create reusable run conditions

Bevy version: 0.14Last 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,
}

#[derive(Resource)]
struct Score(i32);

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