final code

This commit is contained in:
Zhao Yunlong 2025-01-02 20:12:09 +08:00
commit 954e3d3b8b
6 changed files with 4934 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

4725
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

11
Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "flappy_bird"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy = "0.15.0"
rand = "0.8.5"
[profile.dev]
ope-level = 1

BIN
assets/bird.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

BIN
assets/pipe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

197
src/main.rs Normal file
View File

@ -0,0 +1,197 @@
use bevy::{prelude::*, window::{PrimaryMonitor, PrimaryWindow}};
use rand::{rngs::ThreadRng, thread_rng, Rng};
fn main() {
App::new()
.add_plugins(
DefaultPlugins
.set(WindowPlugin{
primary_window: Some(Window{
title: String::from("Flappy Bird"),
position: WindowPosition::Centered(MonitorSelection::Primary),
resolution: Vec2::new(512., 512.).into(),
..Default::default()
}),
..Default::default()
})
.set(ImagePlugin::default_nearest())
)
.add_systems(Startup, setup_level)
.add_systems(Update, (update_bird, update_obstacles))
.run();
}
const PIXEL_RATIO: f32 = 4.0;
const FLAP_FORCE: f32 = 500.;
const GRAVITY: f32 = 2000.;
const VELOCITY_TO_ROTATION_RATIO: f32 = 7.5;
const OBSTACLE_AMOUNT: i32 = 5;
const OBSTACLE_WIDTH: f32 = 32.;
const OBSTACLE_HEIGHT: f32 = 144.;
const OBSTACLE_VERTICLE_OFFSET: f32 = 30.;
const OBSTACLE_GAP_SIZE: f32 = 15.;
const OBSTACLE_SPACING: f32 = 60.;
const OBSTACLE_SCROLL_SPEED: f32 = 150.;
#[derive(Resource)]
pub struct GameManager{
pub pipe_image: Handle<Image>,
pub window_dimensions: Vec2
}
#[derive(Component)]
struct Bird{
pub velocity: f32,
}
#[derive(Component)]
struct Obstacle{
pub pipe_direction: f32,
}
fn setup_level(
mut commands: Commands,
asset_server: Res<AssetServer>,
windows_query: Query<&Window, With<PrimaryWindow>>
){
let pipe_image = asset_server.load("pipe.png");
let window = windows_query.get_single().unwrap();
commands.insert_resource(GameManager{
pipe_image: pipe_image.clone(),
window_dimensions: Vec2::new(window.width(), window.height())
});
commands.insert_resource(ClearColor(Color::srgb(0.5, 0.7, 0.8)));
commands.spawn(Camera2d::default());
commands.spawn((
Sprite{
image: asset_server.load("bird.png"),
..Default::default()
},
Transform::IDENTITY.with_scale(Vec3::splat(PIXEL_RATIO)),
Bird {velocity: 0.}
));
let mut rand = thread_rng();
spawn_obstacles(&mut commands, &mut rand, window.width(), &pipe_image);
}
fn get_centered_pipe_position() -> f32 {
return (OBSTACLE_HEIGHT/2. + OBSTACLE_GAP_SIZE) * PIXEL_RATIO;
}
fn update_obstacles(
time: Res<Time>,
game_manager: Res<GameManager>,
mut obstacle_query: Query<(&mut Obstacle, &mut Transform)>
){
let mut rand = thread_rng();
let y_offset = generate_offset(&mut rand);
for (obstacle, mut transform) in obstacle_query.iter_mut() {
transform.translation.x -= time.delta_secs() * OBSTACLE_SCROLL_SPEED;
if transform.translation.x + OBSTACLE_WIDTH * PIXEL_RATIO /2. < - game_manager.window_dimensions.x / 2. {
transform.translation.x += OBSTACLE_AMOUNT as f32 * OBSTACLE_SPACING * PIXEL_RATIO;
transform.translation.y = get_centered_pipe_position() * obstacle.pipe_direction + y_offset;
}
}
}
fn spawn_obstacles(
mut commands: &mut Commands,
rand: &mut ThreadRng,
window_width: f32,
pipe_image: &Handle<Image>
){
for i in 0..OBSTACLE_AMOUNT{
let y_offset = generate_offset(rand);
let x_pos = window_width/2. + (OBSTACLE_SPACING * PIXEL_RATIO * i as f32);
spawn_obstacle(
Vec3::X * x_pos + Vec3::Y * (get_centered_pipe_position() + y_offset),
1.0,
commands,
pipe_image
);
spawn_obstacle(
Vec3::X * x_pos + Vec3::Y * (-get_centered_pipe_position() + y_offset),
-1.0,
commands,
pipe_image
);
}
}
fn spawn_obstacle(
translation: Vec3,
pipe_direction: f32,
commands: &mut Commands,
pipe_image: &Handle<Image>
){
commands.spawn((
Sprite{
image: pipe_image.clone(),
..Default::default()
},
Transform::from_translation(translation)
.with_scale(Vec3::new(PIXEL_RATIO, PIXEL_RATIO * pipe_direction, PIXEL_RATIO)),
Obstacle{pipe_direction}
));
}
fn generate_offset(rand: &mut ThreadRng) -> f32{
return rand.gen_range(-OBSTACLE_VERTICLE_OFFSET..OBSTACLE_VERTICLE_OFFSET) * PIXEL_RATIO;
}
fn update_bird(
mut commands: Commands,
mut bird_query: Query<(&mut Bird, &mut Transform), Without<Obstacle>>,
mut obstacle_query: Query<(&Transform, Entity), With<Obstacle>>,
time: Res<Time>,
keys: Res<ButtonInput<KeyCode>>,
game_manager: Res<GameManager>,
){
if let Ok((mut bird, mut transform)) = bird_query.get_single_mut(){
if keys.just_pressed(KeyCode::Space) {
bird.velocity = FLAP_FORCE;
}
bird.velocity -= time.delta_secs() * GRAVITY;
transform.translation.y += bird.velocity * time.delta_secs();
transform.rotation = Quat::from_axis_angle(Vec3::Z,
f32::clamp(bird.velocity / VELOCITY_TO_ROTATION_RATIO, -90., 90.).to_radians());
let mut dead = false;
if transform.translation.y <= -game_manager.window_dimensions.y / 2. {
dead = true;
}
else{
for (pipe_tranformation, _entity) in obstacle_query.iter() {
if (pipe_tranformation.translation.y - transform.translation.y).abs() < OBSTACLE_HEIGHT * PIXEL_RATIO / 2. &&
(pipe_tranformation.translation.x - transform.translation.x).abs() < OBSTACLE_WIDTH * PIXEL_RATIO / 2.{
dead = true;
break;
}
}
}
if dead {
transform.translation = Vec3::ZERO;
bird.velocity = 0.;
for (_trans, entity) in obstacle_query.iter_mut() {
commands.entity(entity).despawn();
}
let mut rand = thread_rng();
spawn_obstacles(&mut commands, &mut rand, game_manager.window_dimensions.x, &game_manager.pipe_image);
}
}
}