Adding Resources

Build a Flappy Bird Clone in Rust and Bevy 0.14Part 4 - Adding Resources

Game
Bevy 0.14
Flappy Bird
Last update:

In the previous part of the tutorial we added the systems to create some animations in the game. In this part we will add some resources to the game. Resources are data that is shared between systems. In this case we will add a resource to store the current state of the game.

Resources in Bevy

Resources are global data that can be accessed by the systems. The difference between resources and components is that resources allow you to store a single global instance of some type of data. This data can be accessed by any system in the game. Components, on the other hand, are attached to entities, they are not a single instance of data, instead they can be attached to multiple entities.

Learn Rust by Practice

Master Rust through hands-on coding exercises and real-world examples.

Adding Game State Resource

In our case, we need to create a resource to store some information about the game state. We need to store a few things:

  • Game State (active, inactive, game over)
  • The score of the player

A Resource in bevy is a struct or enum that implements the trait Resource.

Let's create new file resources.rs in the src directory:

src
├── components.rs
├── resources.rs
├── main.rs
├── setup.rs
├── systems.rs
├── plugin.rs
├── constants.rs
└── utils.rs

Update main.rs to include the new file:

use bevy::prelude::*;
use plugin::MyPlugin;
use resources::*;
use setup::setup;
use systems::*;
 
mod components;
mod constants;
mod plugin;
mod resources;
mod setup;
mod systems;
mod utils;

We can create a struct to store the game state and the score.

// resources.rs
 
#[derive(Resource)]
pub struct Game {
    pub score: u32,
    pub state: GameState,
}
 
pub enum GameState {
    Active,
    Inactive,
    GameOver,
}

We can now add this resource to the game. We can do this by adding the resource to the App in the main.rs file, using the init_resource method which takes in one generic which is the struct that implements the Resource trait.

fn main() {
    App::new()
        .init_resource::<Game>()
        .add_systems(Startup, setup)
        .add_systems(Update, blink_space_bar_text)
        .add_systems(Update, move_background)
        .add_systems(Update, move_ground)
        .add_systems(Update, animate_bird)
        .add_plugins(MyPlugin)
        .run();
}

However, this gets us a compile error, because bevy expects the Resource to also implement the Default trait. To do that, we can use the derive macro and implement the Default trait for the Game struct, and implement Default for the GameState manually to set the default state of the game to Inactive.

#[derive(Resource, Default)]
pub struct Game {
    pub score: u32,
    pub state: GameState,
}
 
pub enum GameState {
    Active,
    Inactive,
    GameOver,
}
 
impl Default for GameState {
    fn default() -> Self {
        GameState::Inactive
    }
}

Conditional Systems

In the previous part of the tutorial, we added systems to create animations in the game, but they were always running, even when the game wasn't started. We can add a helper function to check the game state and only run the system if the game is in a specific state.

fn is_game_active(game: Res<Game>) -> bool {
    game.state == GameState::Active
}
 
fn is_game_not_active(game: Res<Game>) -> bool {
    game.state != GameState::Active
}

To use the == operator, we need to implement the PartialEq trait for the GameState enum.

#[derive(PartialEq)]
pub enum GameState {
    Active,
    Inactive,
    GameOver,
}

Now we can use these functions to run the systems only when the game is active.

fn main() {
    App::new()
        .init_resource::<Game>()
        .add_systems(Startup, setup)
        .add_systems(Update, blink_space_bar_text.run_if(is_game_not_active))
        .add_systems(Update, move_background.run_if(is_game_active))
        .add_systems(Update, move_ground.run_if(is_game_active))
        .add_systems(Update, animate_bird.run_if(is_game_active))
        .add_plugins(MyPlugin)
        .run();
}

The run_if method takes a function that returns a boolean, if the function returns true, the system will run, otherwise it will be skipped.

If you run the game, you'll see the animations are stopped, and the game is not running.

Now, we need a way to detect the user input when they press the space bar to start the game.

Detecting user input

To detect the user input, we can create another system that listens for the space bar key press. We can use the ButtonInput resource to listen for the key press event.

// systems.rs
 
pub fn start_game(
    mut game: ResMut<Game>,
    mut space_query: Query<(&mut PressSpaceBarText, &mut Visibility)>,
    mut game_over_query: Query<&mut Visibility, (With<GameOverText>, Without<PressSpaceBarText>)>,
    keyboard_input: Res<ButtonInput<KeyCode>>,
) {
    if !keyboard_input.just_pressed(KeyCode::Space) {
        return;
    }
 
    game.state = GameState::Active;
 
    // Hiding the PressSpaceBarText
    let (mut space, mut visibility) = space_query.single_mut();
    space.0.reset();
    *visibility = Visibility::Hidden;
 
    // Hiding the GameOverText
    let mut game_over_visibility = game_over_query.single_mut();
    *game_over_visibility = Visibility::Hidden;
}

Adding it to the Bevy App

// main.rs
 
fn main() {
    App::new()
        .init_resource::<Game>()
        .add_systems(Startup, setup)
        .add_systems(Update, blink_space_bar_text.run_if(is_game_not_active))
        .add_systems(Update, move_background.run_if(is_game_active))
        .add_systems(Update, move_ground.run_if(is_game_active))
        .add_systems(Update, animate_bird.run_if(is_game_active))
        .add_systems(Update, start_game.run_if(is_game_not_active))
        .add_plugins(MyPlugin)
        .run();
}

We only want to start the game if the user pressed Space and the game is not already active. We can use the just_pressed method on the ButtonInput resource to check if the key was pressed. We also set the GameOverText and PressSpaceBarText Visibility to Hidden when the game starts.

Using the Without<PressSpaceBarText> here is critical, because in the previous query we have queried for PressSpaceBarText and Visibility, so we need to exclude the PressSpaceBarText from this query. If we don't use the Without<PrestSpaceBarText> to exclude the PressSpaceBarText from this query, Bevy will panic, if you want to learn more about this error, read more about it here

That's it for this part, we added a resource to store the game state and the score, and we added systems to start the game when the user presses the space bar and to show the GameOverText when the game is over.

In the next part of the tutorial, we're going to implement the physics of the game. We will add gravity to the bird and make it fall down when the game is active, we'll create the jump mechanic for the bird, and we'll add collision detection to detect when the bird hits the ground.