Setting up the project

Build a Flappy Bird Clone in Rust and Bevy 0.14Part 1 - Setting up the project

Game
Bevy 0.14
Flappy Bird
Last update:

Welcome to this tutorial on building a Flappy Bird clone using Rust and Bevy!

In this tutorial we'll create an engaging and fun project. We'll walk through each step, ensuring you understand the concepts and enjoy the process.

By the end, you'll have a fully functional Flappy Bird clone and a solid grasp of Rust and Bevy's capabilities. Let's get started on this exciting project!

This is the first part of the tutorial series, in this part, we'll focus on setting up the project, installing the necessary dependencies, downloading the assets, creating constant variables for the window size, and some other configurations.

These configurations are important to make sure we get a fast compile time while also having optimized code that runs fast and efficiently.

About Bevy

Bevy is a cutting-edge game engine built in Rust, designed to be simple, fast, and highly extensible. It leverages Rust's performance and safety features to provide an efficient and safe development experience.

Bevy is modular, meaning you can pick and choose the components you need, making it highly customizable to fit your project requirements.

It supports modern game development features such as real-time rendering, a powerful entity-component system (ECS), and a robust plugin architecture.

Bevy is still in the early stages of development, at the time of writing this tutorial, we're using Bevy 0.14, so make sure you use the same version to avoid any compatibility issues.

Learn Rust by Practice

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

Entity-Component System (ECS)

At the core of Bevy lies its powerful Entity-Component System (ECS), a design pattern that helps manage the complexity of game development by decoupling data and behavior.

In an ECS, entities are the objects in your game world, which are composed of various components holding data.

Systems are the logic that operates on these components. This separation allows for highly modular and reusable code, making it easier to manage large and complex games.

By using ECS, you can efficiently update and render thousands of entities, ensuring smooth and scalable game performance. Bevy's ECS is designed to be intuitive and efficient, helping you focus on building your game without getting bogged down by boilerplate code.

Setting up the project

Before we start writing game code, let's first start by setting up the project.

Rust is known for it's slow compilation times, and that's something we want to avoid when it comes to game development, we want our changes to be reflected quickly, therefore there are some configuration changes we need to make to speed up the compilation and optimize the development workflow.

You can either follow the official Bevy guide to set up your project in this way based on your operating system: Bevy - Setting up your project.

Alternatively you can use a starter template that we've created to help you get started quickly.

Using the starter template

Run the following command to clone the starter template:

git clone git@github.com:dcodesdev/bevy-starter.git flappy-rust

This will clone the starter template into a directory named flappy-rust or any name of your choice.

Update the project name in the Cargo.toml file to flappy-rust:

[package]
name = "flappy-rust"

Your Cargo.toml file should look like this:

[package]
name = "flappy-rust"
version = "0.1.0"
edition = "2021"
 
[dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] }
 
# Enable a small amount of optimization in debug mode
[profile.dev]
opt-level = 1
 
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
[profile.dev.package."*"]
opt-level = 3

This makes sure the output binary is optimized while having a faster compilation time.

Adding the assets

Next, let's add the assets required for the game. You can download the assets from the link below:

This includes the textures for the bird, background, and pipes. Also it includes the sound effects that we need for the game. Place these assets in a directory named assets at the root of your project.

Your project directory should look like this:

flappy-rust
├── .cargo
   └── config.toml
├── .gitignore
├── rust-toolchain.toml
├── src
   └── main.rs
├── assets
   ├── audio
   ├── wing.ogg
   ├── point.ogg
   └── hit.ogg
   └── texture
       ├── numbers.png
       ├── game-over.png
       ├── space.png
       ├── bird.png
       ├── background.png
       ├── base.png
       └── pipe.png
├── Cargo.lock
├── Cargo.toml
└── README.md

Setting up Bevy

Now that we have the project structure and assets in place, let's set up Bevy in our project.

Bevy is already added to our Cargo.toml file with the dynamic_linking feature enabled. This will help speed up the compilation time.

[dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] }

Creating a Bevy App

A Bevy App is the entry point to a Bevy game. It manages the game loop, event handling, and resource management.

The template we used already created an App with the default Bevy plugins.

use bevy::prelude::*;
 
fn main() {
    App::new().add_plugins(DefaultPlugins).run();
}

Customizing window size

If you have a look at the assets we downloaded, you'll see that the background.png texture has a height of 512 pixels. We want to set the window size to match the background texture size otherwise the texture will be stretched.

In order to prevent that from happening we need to set a fixed size for the window. We can do that by customizing the DefaultPlugins and setting the window size.

fn main() {
    App::new()
        .add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                title: "Flappy Rust".to_string(),
                resolution: (800., 512.).into(),
                resizable: false,
                ..default()
            }),
            ..default()
        }))
        .run();
}

We have set the window title to Flappy Rust and the resolution to 800x512 pixels. We also disabled window resizing, so the window size remains fixed.

We are also using the ..default() syntax to set the default values for the window that are not explicitly set. The Default trait is available in the standard library and is implemented for the Window which fills the other fields with default values.

Let's run the code and see what we have so far:

cargo run

The first time you run the code, it will take some time to compile. Subsequent runs will be faster due to the optimizations we made.

Flappy Rust Window Flappy Rust Window

This works well, but the code is a bit cluttered and we want our main.rs file to be very minimalistic because we're going to add a lot of components in the future. We can make this customization a separate module and add it as a plugin to the App.

Creating a Bevy Plugin

A Bevy Plugin is a collection of systems, components, and resources that extend Bevy's functionality.

In order to create our own Plugin in Bevy, we need to define a struct and implement the Plugin trait for it.

Let's create a new file named plugin.rs in the src directory and define our plugin in.

We also need to add this line to our main.rs file

use bevy::prelude::*;
 
mod plugin;

Let's define the plugin:

use bevy::prelude::*;
 
pub struct MyPlugin;
 
impl Plugin for MyPlugin {
    fn build(&self, app: &mut App) {
        app.add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                title: "Flappy Rust".to_string(),
                resolution: (800., 512.).into(),
                resizable: false,
                ..default()
            }),
            ..default()
        }));
    }
}

The Plugin trait let's us define a build(&self, app: &mut App) method, the build method triggers when Bevy App is first created at the startup of your game.

The method takes a mutable reference to the App struct, which allows us to add plugins to the App.

Adding constants

It's common to re-use some values in different parts of the game, for example, the window size. We can also make the WINDOW_WIDTH and WINDOW_HEIGHT values constant variables that we can later re-use in the other parts of the game, so let's create a new file called constants.rs in the src directory and define the constants in it.

pub const WINDOW_WIDTH: f32 = 800.0;
pub const WINDOW_HEIGHT: f32 = 512.0;

We can then import the constants in the plugin.rs file and use them in the resolution:

use bevy::prelude::*;
 
use crate::constants::{WINDOW_HEIGHT, WINDOW_WIDTH};
 
pub struct MyPlugin;
 
impl Plugin for MyPlugin {
    fn build(&self, app: &mut App) {
        app.add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                title: "Flappy Rust".to_string(),
                resolution: (WINDOW_WIDTH, WINDOW_HEIGHT).into(),
                resizable: false,
                ..default()
            }),
            ..default()
        }));
    }
}

We can then go back to our main.rs file and add our plugin to the App:

use bevy::prelude::*;
use plugin::MyPlugin;
 
mod constants;
mod plugin;
 
fn main() {
    App::new().add_plugins(MyPlugin).run();
}

As you can see, we've achieved the same result but in a more organized and modular way.

Our src directory structure now looks like this:

src
├── main.rs
├── plugin.rs
└── constants.rs

Now that we've set up the project, we're ready to start building our Flappy Bird clone. In the next part of the tutorial, we'll write our setup function which runs at startup to spawn all of the game entities we're gonna need.