国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

一個簡單的 rust 項(xiàng)目 使用 bevy 引擎 復(fù)刻 Flappy Bird 小游戲

這篇具有很好參考價值的文章主要介紹了一個簡單的 rust 項(xiàng)目 使用 bevy 引擎 復(fù)刻 Flappy Bird 小游戲。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報違法"按鈕提交疑問。

Rust + Bevy 實(shí)現(xiàn)的 Flappy Bird 游戲

簡介

一個使用 bevy 引擎復(fù)刻的 Flappy Bird 經(jīng)典小游戲。
通過該項(xiàng)目我們可以學(xué)到:bevy 的自定義組件,自定義插件,自定義資源,sprite 的旋轉(zhuǎn),sprite 的移動,sprite sheet 動畫的定義使用,狀態(tài)管理,等內(nèi)容…

簡單介紹一下包含的內(nèi)容:

  • 游戲狀態(tài)管理 Menu、InGame、Paused、GameOver。
  • 小鳥碰撞檢測。
  • 地面移動。
  • 小鳥飛翔動畫。
  • 小鳥飛行方向變化。
  • 小鳥重力系統(tǒng)。
  • 障礙物隨機(jī)生成。

通過空格向上飛行。
按 P 暫停游戲,按 R 恢復(fù)游戲。

代碼結(jié)構(gòu)

·
├── assets/
│?? ├──audios/
│?? ├──fonts/
│?? └──images/
├── src/
│?? ├── build.rs
│?? ├── components.rs
│?? ├── constants.rs
│?? ├── main.rs
│?? ├── obstacle.rs
│?? ├── player.rs
│?? ├── resource.rs
│?? └── state.rs
├── Cargo.lock
└── Cargo.toml
  • assets/audios 聲音資源文件。
  • assets/fonts 字體資源文件。
  • assets/images 圖片資源文件。
  • build.rs 構(gòu)建之前執(zhí)行的腳本文件。
  • components.rs 游戲組件定義。
  • constants.rs 負(fù)責(zé)存儲游戲中用到的常量。
  • main.rs 負(fù)責(zé)游戲的邏輯、插件交互、等內(nèi)容。
  • obstacle.rs 障礙物生成、初始化。
  • player.rs 玩家角色插件,生成、移動、鍵盤處理的實(shí)現(xiàn)。
  • resource.rs 游戲資源定義。
  • state.rs 游戲狀態(tài)管理。

build.rs

use std::{
    env, fs,
    path::{Path, PathBuf},
};

const COPY_DIR: &'static str = "assets";

/// A helper function for recursively copying a directory.
fn copy_dir<P, Q>(from: P, to: Q)
where
    P: AsRef<Path>,
    Q: AsRef<Path>,
{
    let to = to.as_ref().to_path_buf();

    for path in fs::read_dir(from).unwrap() {
        let path = path.unwrap().path();
        let to = to.clone().join(path.file_name().unwrap());

        if path.is_file() {
            fs::copy(&path, to).unwrap();
        } else if path.is_dir() {
            if !to.exists() {
                fs::create_dir(&to).unwrap();
            }

            copy_dir(&path, to);
        } else { /* Skip other content */
        }
    }
}

fn main() {
    // Request the output directory
    let out = env::var("PROFILE").unwrap();
    let out = PathBuf::from(format!("target/{}/{}", out, COPY_DIR));

    // If it is already in the output directory, delete it and start over
    if out.exists() {
        fs::remove_dir_all(&out).unwrap();
    }

    // Create the out directory
    fs::create_dir(&out).unwrap();

    // Copy the directory
    copy_dir(COPY_DIR, &out);
}

components.rs

use bevy::{
    prelude::Component,
    time::{Timer, TimerMode},
};

/// 玩家組件
#[derive(Component)]
pub struct Player;

/// 玩家動畫播放計時器
#[derive(Component)]
pub struct PlayerAnimationTimer(pub Timer);

impl Default for PlayerAnimationTimer {
    fn default() -> Self {
        Self(Timer::from_seconds(0.1, TimerMode::Repeating))
    }
}

/// 障礙物組件
#[derive(Component)]
pub struct Obstacle;

/// 移動組件
#[derive(Component)]
pub struct Movable {
    /// 移動時是否需要旋轉(zhuǎn)
    pub need_rotation: bool,
}

impl Default for Movable {
    fn default() -> Self {
        Self {
            need_rotation: false,
        }
    }
}

/// 速度組件
#[derive(Component)]
pub struct Velocity {
    pub x: f32,
    pub y: f32,
}

impl Default for Velocity {
    fn default() -> Self {
        Self { x: 0., y: 0. }
    }
}

/// 分?jǐn)?shù)顯示組件
#[derive(Component)]
pub struct DisplayScore;

/// 菜單顯示組件
#[derive(Component)]
pub struct DisplayMenu;

/// 地面組件
#[derive(Component)]
pub struct Ground(pub f32);

/// 游戲結(jié)束組件
#[derive(Component)]
pub struct DisplayGameOver;

constants.rs

/// 小鳥圖片路徑
pub const BIRD_IMG_PATH: &str = "images/bird_columns.png";
/// 小鳥圖片大小
pub const BIRD_IMG_SIZE: (f32, f32) = (34., 24.);
/// 小鳥動畫幀數(shù)
pub const BIRD_ANIMATION_LEN: usize = 3;

pub const WINDOW_WIDTH: f32 = 576.;
pub const WINDOW_HEIGHT: f32 = 624.;

/// 背景圖片路徑
pub const BACKGROUND_IMG_PATH: &str = "images/background.png";
/// 背景圖片大小
pub const BACKGROUND_IMG_SIZE: (f32, f32) = (288., 512.);
/// 地面圖片路徑
pub const GROUND_IMG_PATH: &str = "images/ground.png";
/// 地面圖片大小
pub const GROUND_IMG_SIZE: (f32, f32) = (336., 112.);
/// 一個單位地面的大小
pub const GROUND_ITEM_SIZE: f32 = 48.;
/// 管道圖片路徑
pub const PIPE_IMG_PATH: &str = "images/pipe.png";
/// 管道圖片大小
pub const PIPE_IMG_SIZE: (f32, f32) = (52., 320.);
/// 飛翔聲音路徑
pub const FLAY_AUDIO_PATH: &str = "audios/wing.ogg";
/// 得分聲音
pub const POINT_AUDIO_PATH: &str = "audios/point.ogg";
/// 死亡聲音
pub const DIE_AUDIO_PATH: &str = "audios/die.ogg";
/// 被撞擊聲音
pub const HIT_AUDIO_PATH: &str = "audios/hit.ogg";
/// kenney future 字體路徑
pub const KENNEY_FUTURE_FONT_PATH: &str = "fonts/KenneyFuture.ttf";

/// x 軸前進(jìn)速度
pub const SPAWN_OBSTACLE_TICK: f32 = 4.;
/// x 軸前進(jìn)速度
pub const PLAYER_X_MAX_VELOCITY: f32 = 48.;
/// y 軸最大上升速度
pub const PLAYER_Y_MAX_UP_VELOCITY: f32 = 20.;
/// y 軸每次上升像素
pub const PLAYER_Y_UP_PIXEL: f32 = 10.;
/// y 軸最大下落速度
pub const PLAYER_Y_MAX_VELOCITY: f32 = 200.;
/// y 軸下落加速度,每秒增加
pub const GRAVITY_VELOCITY: f32 = 80.;
/// 步長 (幀數(shù))
pub const TIME_STEP: f32 = 1. / 60.;

/// 最大通過空間
pub const GAP_MAX: f32 = 300.;
/// 最小通過空間
pub const GAP_MIN: f32 = 50.;

main.rs

use bevy::{
    prelude::*,
    sprite::collide_aabb::collide,
    window::{Window, WindowPlugin, WindowPosition},
};
use obstacle::ObstaclePlugin;

use components::{DisplayScore, Ground, Movable, Obstacle, Player, PlayerAnimationTimer, Velocity};
use constants::*;
use player::PlayerPlugin;
use resource::{GameData, StaticAssets, WinSize};
use state::{GameState, StatesPlugin};

mod components;
mod constants;
mod obstacle;
mod player;
mod resource;
mod state;

fn main() {
    App::new()
        .add_state::<GameState>()
        .insert_resource(ClearColor(Color::rgb_u8(205, 201, 201)))
        .add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                title: "Flappy Bird".to_owned(),
                resolution: (WINDOW_WIDTH, WINDOW_HEIGHT).into(),
                position: WindowPosition::At(IVec2::new(2282, 0)),
                resizable: false,
                ..Default::default()
            }),
            ..Default::default()
        }))
        .add_system(system_startup.on_startup())
        .add_plugin(StatesPlugin)
        .add_plugin(PlayerPlugin)
        .add_plugin(ObstaclePlugin)
        .add_systems(
            (
                score_display_update_system,
                player_animation_system,
                player_score_system,
                movable_system,
                ground_move_system,
                player_collision_check_system,
            )
                .in_set(OnUpdate(GameState::InGame)),
        )
        .add_system(bevy::window::close_on_esc)
        .run();
}

/// 玩家碰撞檢測系統(tǒng)
fn player_collision_check_system(
    win_size: Res<WinSize>,
    static_assets: Res<StaticAssets>,
    audio_player: Res<Audio>,
    mut next_state: ResMut<NextState<GameState>>,
    obstacle_query: Query<(Entity, &Transform), With<Obstacle>>,
    player_query: Query<(Entity, &Transform), With<Player>>,
) {
    let player_result = player_query.get_single();
    match player_result {
        Ok((_, player_tf)) => {
            let mut is_collision = false;
            // 先進(jìn)行邊緣碰撞檢測
            if player_tf.translation.y >= win_size.height / 2.
                || player_tf.translation.y <= -(win_size.height / 2. - GROUND_IMG_SIZE.1)
            {
                is_collision = true;
            }

            for (_, obstacle_tf) in obstacle_query.iter() {
                let collision = collide(
                    player_tf.translation,
                    Vec2 {
                        x: BIRD_IMG_SIZE.0,
                        y: BIRD_IMG_SIZE.1,
                    },
                    obstacle_tf.translation,
                    Vec2 {
                        x: PIPE_IMG_SIZE.0,
                        y: PIPE_IMG_SIZE.1,
                    },
                );
                if let Some(_) = collision {
                    is_collision = true;
                    break;
                }
            }
            // 判斷是否已經(jīng)發(fā)生碰撞
            if is_collision {
                // 增加得分并播放聲音
                audio_player.play(static_assets.hit_audio.clone());
                audio_player.play(static_assets.die_audio.clone());
                next_state.set(GameState::GameOver);
            }
        }
        _ => (),
    }
}

/// 玩家得分檢測
fn player_score_system(
    mut commands: Commands,
    mut game_data: ResMut<GameData>,
    static_assets: Res<StaticAssets>,
    audio_player: Res<Audio>,
    obstacle_query: Query<(Entity, &Transform), With<Obstacle>>,
    player_query: Query<(Entity, &Transform), With<Player>>,
) {
    let player_result = player_query.get_single();
    match player_result {
        Ok((_, player_tf)) => {
            let mut need_add_score = false;
            for (entity, obstacle_tf) in obstacle_query.iter() {
                // 鳥的 尾巴通過管道的右邊緣
                if player_tf.translation.x - BIRD_IMG_SIZE.0 / 2.
                    > obstacle_tf.translation.x + PIPE_IMG_SIZE.0 / 2.
                {
                    // 通過的話,將需要得分記為 true 并銷毀管道
                    need_add_score = true;
                    commands.entity(entity).despawn();
                }
            }
            // 判斷是否需要增加得分
            if need_add_score {
                // 增加得分并播放聲音
                game_data.add_score();
                audio_player.play(static_assets.point_audio.clone());
                game_data.call_obstacle_spawn();
            }
        }
        _ => (),
    }
}

/// 移動系統(tǒng)
///
/// * 不考慮正負(fù)值,只做加法,需要具體的實(shí)體通過移動的方向自行考慮正負(fù)值
fn movable_system(
    mut query: Query<(&mut Transform, &Velocity, &Movable), (With<Movable>, With<Velocity>)>,
) {
    for (mut transform, velocity, movable) in query.iter_mut() {
        let x = velocity.x * TIME_STEP;
        let y = velocity.y * TIME_STEP;
        transform.translation.x += x;
        transform.translation.y += y;
        // 判斷是否需要旋轉(zhuǎn)
        if movable.need_rotation {
            if velocity.y > 0. {
                transform.rotation = Quat::from_rotation_z(velocity.y / PLAYER_Y_MAX_UP_VELOCITY);
            } else {
                transform.rotation = Quat::from_rotation_z(velocity.y / PLAYER_Y_MAX_VELOCITY);
            };
        }
    }
}

/// 地面移動組件
fn ground_move_system(mut query: Query<(&mut Transform, &mut Ground)>) {
    let result = query.get_single_mut();
    match result {
        Ok((mut transform, mut ground)) => {
            ground.0 += 1.;
            transform.translation.x = -ground.0;
            ground.0 = ground.0 % GROUND_ITEM_SIZE;
        }
        _ => (),
    }
}

/// 角色動畫系統(tǒng)
fn player_animation_system(
    time: Res<Time>,
    mut query: Query<(&mut PlayerAnimationTimer, &mut TextureAtlasSprite)>,
) {
    for (mut timer, mut texture_atlas_sprite) in query.iter_mut() {
        timer.0.tick(time.delta());
        if timer.0.just_finished() {
            let next_index = (texture_atlas_sprite.index + 1) % BIRD_ANIMATION_LEN;
            texture_atlas_sprite.index = next_index;
        }
    }
}

/// 分?jǐn)?shù)更新系統(tǒng)
fn score_display_update_system(
    game_data: Res<GameData>,
    mut query: Query<&mut Text, With<DisplayScore>>,
) {
    for mut text in &mut query {
        text.sections[1].value = game_data.get_score().to_string();
    }
}

fn system_startup(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut texture_atlases: ResMut<Assets<TextureAtlas>>,
    windows: Query<&Window>,
) {
    commands.spawn(Camera2dBundle::default());

    let game_data = GameData::new();
    commands.insert_resource(game_data);

    let window = windows.single();
    let (window_w, window_h) = (window.width(), window.height());
    let win_size = WinSize {
        width: window_w,
        height: window_h,
    };
    commands.insert_resource(win_size);

    let player_handle = asset_server.load(BIRD_IMG_PATH);

    // 將 player_handle 加載的圖片,用 BIRD_IMG_SIZE 的大小,按照 1 列,3 行,切圖。
    let texture_atlas =
        TextureAtlas::from_grid(player_handle, Vec2::from(BIRD_IMG_SIZE), 1, 3, None, None);
    let player = texture_atlases.add(texture_atlas);

    let background = asset_server.load(BACKGROUND_IMG_PATH);
    let pipe = asset_server.load(PIPE_IMG_PATH);
    let ground = asset_server.load(GROUND_IMG_PATH);
    let fly_audio = asset_server.load(FLAY_AUDIO_PATH);
    let die_audio = asset_server.load(DIE_AUDIO_PATH);
    let point_audio = asset_server.load(POINT_AUDIO_PATH);
    let hit_audio = asset_server.load(HIT_AUDIO_PATH);
    let kenney_future_font = asset_server.load(KENNEY_FUTURE_FONT_PATH);

    let static_assets = StaticAssets {
        player,
        background,
        pipe,
        ground,
        fly_audio,
        die_audio,
        point_audio,
        hit_audio,
        kenney_future_font,
    };
    commands.insert_resource(static_assets);

    let (background_w, background_h) = BACKGROUND_IMG_SIZE;
    let (ground_w, ground_h) = GROUND_IMG_SIZE;
    commands.spawn(SpriteBundle {
        texture: asset_server.load(BACKGROUND_IMG_PATH),
        sprite: Sprite {
            custom_size: Some(Vec2 {
                x: background_w * 2.,
                y: background_h,
            }),
            ..Default::default()
        },
        transform: Transform {
            translation: Vec3 {
                x: 0.,
                y: ground_h / 2.,
                z: 1.,
            },
            ..Default::default()
        },
        ..Default::default()
    });

    commands.spawn((
        SpriteBundle {
            texture: asset_server.load(GROUND_IMG_PATH),
            sprite: Sprite {
                custom_size: Some(Vec2 {
                    x: ground_w * 2.,
                    y: ground_h,
                }),
                ..Default::default()
            },
            transform: Transform {
                translation: Vec3 {
                    x: 0.,
                    y: window_h / 2. - background_h - ground_h / 2.,
                    z: 4.,
                },
                ..Default::default()
            },

            ..Default::default()
        },
        Ground(GROUND_ITEM_SIZE),
    ));
}

obstacle.rs

use rand::{thread_rng, Rng};
use std::time::Duration;

use crate::{
    components::{Movable, Obstacle, Velocity},
    constants::{
        BACKGROUND_IMG_SIZE, GAP_MAX, GAP_MIN, GROUND_IMG_SIZE, PIPE_IMG_SIZE,
        PLAYER_X_MAX_VELOCITY, SPAWN_OBSTACLE_TICK,
    },
    resource::{GameData, StaticAssets, WinSize},
    state::GameState,
};

use bevy::{
    prelude::{
        Commands, Entity, IntoSystemAppConfig, IntoSystemConfig, OnEnter, OnUpdate, Plugin, Query,
        Res, ResMut, Transform, Vec3, With,
    },
    sprite::{Sprite, SpriteBundle},
    time::common_conditions::on_timer,
};

/// 障礙物插件
pub struct ObstaclePlugin;

impl Plugin for ObstaclePlugin {
    fn build(&self, app: &mut bevy::prelude::App) {
        app.add_system(obstacle_init_system.in_schedule(OnEnter(GameState::InGame)))
            .add_system(
                spawn_obstacle_system
                    .run_if(on_timer(Duration::from_secs_f32(0.2)))
                    .in_set(OnUpdate(GameState::InGame)),
            );
    }
}

/// 障礙物初始化
fn obstacle_init_system(
    mut commands: Commands,
    static_assets: Res<StaticAssets>,
    win_size: Res<WinSize>,
    game_data: Res<GameData>,
    query: Query<Entity, With<Obstacle>>,
) {
    let count = query.iter().count();
    if count >= 4 {
        return;
    }

    let mut rng = thread_rng();
    // 初始 x 坐標(biāo)
    let x = win_size.width / 2. + PIPE_IMG_SIZE.0 / 2.;
    // 初始化 管道區(qū)域的中心點(diǎn)。因?yàn)橐懦孛娴母叨?    let center_y = (win_size.height - BACKGROUND_IMG_SIZE.1) / 2.;
    // 定義合理范圍
    let reasonable_y_max = win_size.height / 2. - 100.;
    let reasonable_y_min = -(win_size.height / 2. - 100. - GROUND_IMG_SIZE.1);

    let size = SPAWN_OBSTACLE_TICK * PLAYER_X_MAX_VELOCITY;

    for i in 0..2 {
        let x = x - PIPE_IMG_SIZE.0 - size * i as f32;
        // y軸 隨機(jī)中心點(diǎn)
        // 隨機(jī)可通過區(qū)域的中心點(diǎn)
        let point_y = rng.gen_range(reasonable_y_min..reasonable_y_max);
        let half_distance = (center_y - point_y).abs() / 2.;

        // 獲取得分 , 并根據(jù)得分獲取一個隨機(jī)的可通過區(qū)域的大小
        let score = game_data.get_score();
        let max = GAP_MAX - score as f32 / 10.;
        // 不讓 max 小于最小值
        // 這里也可以做些其他的判斷。改變下別的數(shù)據(jù)。比如說 讓管道的移動速度變快!
        let max = max.max(GAP_MIN);
        let min = GAP_MIN;
        let gap = rng.gen_range(min.min(max)..min.max(max));
        let rand_half_gap = gap * rng.gen_range(0.3..0.7);
        // 通過中心點(diǎn),可通過區(qū)域,以及管道的高來計算 上下兩個管道各自中心點(diǎn)的 y 坐標(biāo)
        let half_pipe = PIPE_IMG_SIZE.1 / 2.;
        let pipe_upper = center_y + half_distance + (rand_half_gap + half_pipe);
        let pipe_down = center_y - half_distance - (gap - rand_half_gap + half_pipe);

        // 下方水管
        commands.spawn((
            SpriteBundle {
                texture: static_assets.pipe.clone(),
                transform: Transform {
                    translation: Vec3 {
                        x,
                        y: pipe_down,
                        z: 2.,
                    },
                    ..Default::default()
                },
                ..Default::default()
            },
            Velocity {
                x: -PLAYER_X_MAX_VELOCITY,
                y: 0.,
            },
            Movable {
                need_rotation: false,
            },
            Obstacle,
        ));

        // 上方水管
        commands.spawn((
            SpriteBundle {
                texture: static_assets.pipe.clone(),
                transform: Transform {
                    translation: Vec3 {
                        x,
                        y: pipe_upper,
                        z: 2.,
                    },
                    ..Default::default()
                },
                sprite: Sprite {
                    flip_y: true,
                    ..Default::default()
                },
                ..Default::default()
            },
            Velocity {
                x: -PLAYER_X_MAX_VELOCITY,
                y: 0.,
            },
            Movable {
                need_rotation: false,
            },
            Obstacle,
        ));
    }
}

fn spawn_obstacle_system(
    mut commands: Commands,
    mut game_data: ResMut<GameData>,
    static_assets: Res<StaticAssets>,
    win_size: Res<WinSize>,
) {
    if !game_data.need_spawn_obstacle() {
        return;
    }
    game_data.obstacle_call_back();
    let mut rng = thread_rng();
    // 初始 x 坐標(biāo)
    let x = win_size.width / 2. + PIPE_IMG_SIZE.0 / 2.;
    // 初始化 管道區(qū)域的中心點(diǎn)。因?yàn)橐懦孛娴母叨?    let center_y = (win_size.height - BACKGROUND_IMG_SIZE.1) / 2.;

    // y軸 隨機(jī)中心點(diǎn)
    // 定義合理范圍
    let reasonable_y_max = win_size.height / 2. - 100.;
    let reasonable_y_min = -(win_size.height / 2. - 100. - GROUND_IMG_SIZE.1);
    // 隨機(jī)可通過區(qū)域的中心點(diǎn)
    let point_y = rng.gen_range(reasonable_y_min..reasonable_y_max);
    let half_distance = (center_y - point_y).abs() / 2.;

    // 獲取得分 , 并根據(jù)得分獲取一個隨機(jī)的可通過區(qū)域的大小
    let score = game_data.get_score();
    let max = GAP_MAX - score as f32 / 10.;
    // 不讓 max 小于最小值
    // 這里也可以做些其他的判斷。改變下別的數(shù)據(jù)。比如說 讓管道的移動速度變快!
    let max = max.max(GAP_MIN);
    let min = GAP_MIN;
    let gap = rng.gen_range(min.min(max)..min.max(max));
    let rand_half_gap = gap * rng.gen_range(0.3..0.7);
    // 通過中心點(diǎn),可通過區(qū)域,以及管道的高來計算 上下兩個管道各自中心點(diǎn)的 y 坐標(biāo)
    let half_pipe = PIPE_IMG_SIZE.1 / 2.;
    let pipe_upper = center_y + half_distance + (rand_half_gap + half_pipe);
    let pipe_down = center_y - half_distance - (gap - rand_half_gap + half_pipe);

    // 下方水管
    commands.spawn((
        SpriteBundle {
            texture: static_assets.pipe.clone(),
            transform: Transform {
                translation: Vec3 {
                    x,
                    y: pipe_down,
                    z: 2.,
                },
                ..Default::default()
            },
            ..Default::default()
        },
        Velocity {
            x: -PLAYER_X_MAX_VELOCITY,
            y: 0.,
        },
        Movable {
            need_rotation: false,
        },
        Obstacle,
    ));

    // 上方水管
    commands.spawn((
        SpriteBundle {
            texture: static_assets.pipe.clone(),
            transform: Transform {
                translation: Vec3 {
                    x,
                    y: pipe_upper,
                    z: 2.,
                },
                ..Default::default()
            },
            sprite: Sprite {
                flip_y: true,
                ..Default::default()
            },
            ..Default::default()
        },
        Velocity {
            x: -PLAYER_X_MAX_VELOCITY,
            y: 0.,
        },
        Movable {
            need_rotation: false,
        },
        Obstacle,
    ));
}

player.rs

use bevy::{
    prelude::{
        Audio, Commands, Input, IntoSystemAppConfig, IntoSystemConfigs, KeyCode, OnEnter, OnUpdate,
        Plugin, Query, Res, ResMut, Transform, Vec3, With,
    },
    sprite::{SpriteSheetBundle, TextureAtlasSprite},
    time::{Timer, TimerMode},
};

use crate::{
    components::{Movable, Player, PlayerAnimationTimer, Velocity},
    constants::{
        GRAVITY_VELOCITY, PLAYER_Y_MAX_UP_VELOCITY, PLAYER_Y_MAX_VELOCITY, PLAYER_Y_UP_PIXEL,
        TIME_STEP,
    },
    resource::{GameData, StaticAssets, WinSize},
    state::GameState,
};

pub struct PlayerPlugin;

impl Plugin for PlayerPlugin {
    fn build(&self, app: &mut bevy::prelude::App) {
        app.add_systems(
            (input_key_system, bird_automatic_system).in_set(OnUpdate(GameState::InGame)),
        )
        .add_system(spawn_bird_system.in_schedule(OnEnter(GameState::InGame)));
    }
}

/// 產(chǎn)生玩家
fn spawn_bird_system(
    mut commands: Commands,
    win_size: Res<WinSize>,
    static_assets: Res<StaticAssets>,
    mut game_data: ResMut<GameData>,
) {
    if !game_data.player_alive() {
        let bird = static_assets.player.clone();
        let (x, y) = (-win_size.width / 4. / 2., win_size.height / 2. / 3.);
        commands.spawn((
            SpriteSheetBundle {
                texture_atlas: bird,
                transform: Transform {
                    translation: Vec3 { x, y, z: 2. },
                    ..Default::default()
                },
                sprite: TextureAtlasSprite::new(0),
                ..Default::default()
            },
            Player,
            Velocity { x: 0., y: 0. },
            Movable {
                need_rotation: true,
            },
            PlayerAnimationTimer(Timer::from_seconds(0.2, TimerMode::Repeating)),
        ));
        game_data.alive();
    }
}

/// 游戲中鍵盤事件系統(tǒng)
fn input_key_system(
    kb: Res<Input<KeyCode>>,
    static_assets: Res<StaticAssets>,
    audio_player: Res<Audio>,
    mut query: Query<(&mut Velocity, &mut Transform), With<Player>>,
) {
    if kb.just_released(KeyCode::Space) {
        let vt = query.get_single_mut();
        // 松開空格后,直接向上20像素,并且給一個向上的速度。
        match vt {
            Ok((mut velocity, mut transform)) => {
                transform.translation.y += PLAYER_Y_UP_PIXEL;
                velocity.y = PLAYER_Y_MAX_UP_VELOCITY;
            }
            _ => (),
        }
        audio_player.play(static_assets.fly_audio.clone());
    }
}

/// 小鳥重力系統(tǒng)
fn bird_automatic_system(mut query: Query<&mut Velocity, (With<Player>, With<Movable>)>) {
    for mut velocity in query.iter_mut() {
        velocity.y = velocity.y - GRAVITY_VELOCITY * TIME_STEP;
        if velocity.y < -PLAYER_Y_MAX_VELOCITY {
            velocity.y = -PLAYER_Y_MAX_VELOCITY;
        }
    }
}

resource.rs

use bevy::{
    prelude::{AudioSource, Handle, Image, Resource},
    sprite::TextureAtlas,
    text::Font,
};

/// 游戲數(shù)據(jù)資源
#[derive(Resource)]
pub struct GameData {
    score: u8,
    alive: bool,
    need_add_obstacle: bool,
}
impl GameData {
    pub fn new() -> Self {
        Self {
            score: 0,
            alive: false,
            need_add_obstacle: false,
        }
    }

    pub fn need_spawn_obstacle(&self) -> bool {
        self.need_add_obstacle
    }

    pub fn obstacle_call_back(&mut self) {
        self.need_add_obstacle = false;
    }

    pub fn call_obstacle_spawn(&mut self) {
        self.need_add_obstacle = true;
    }

    pub fn alive(&mut self) {
        self.alive = true;
    }

    pub fn death(&mut self) {
        self.alive = false;
        self.score = 0;
    }

    pub fn get_score(&self) -> u8 {
        self.score
    }

    pub fn add_score(&mut self) {
        self.score += 1;
    }

    pub fn player_alive(&self) -> bool {
        self.alive
    }
}

/// 窗口大小資源
#[derive(Resource)]
pub struct WinSize {
    pub width: f32,
    pub height: f32,
}

/// 靜態(tài)資源
#[derive(Resource)]
pub struct StaticAssets {
    /* 圖片 */
    /// 玩家動畫
    pub player: Handle<TextureAtlas>,
    /// 管道圖片
    pub pipe: Handle<Image>,
    /// 背景圖片
    pub background: Handle<Image>,
    /// 地面圖片
    pub ground: Handle<Image>,

    /* 聲音 */
    /// 飛行聲音
    pub fly_audio: Handle<AudioSource>,
    /// 死亡聲音
    pub die_audio: Handle<AudioSource>,
    /// 得分聲音
    pub point_audio: Handle<AudioSource>,
    /// 被撞擊聲音
    pub hit_audio: Handle<AudioSource>,

    /* 字體 */
    /// 游戲字體
    pub kenney_future_font: Handle<Font>,
}

state.rs

use bevy::{
    prelude::{
        Color, Commands, Entity, Input, IntoSystemAppConfig, IntoSystemConfig, KeyCode, NextState,
        OnEnter, OnExit, OnUpdate, Plugin, Query, Res, ResMut, States, Transform, Vec3, With,
    },
    text::{Text, Text2dBundle, TextAlignment, TextSection, TextStyle},
};

use crate::{
    components::{DisplayGameOver, DisplayMenu, DisplayScore, Obstacle, Player},
    constants::GROUND_IMG_SIZE,
    resource::{GameData, StaticAssets, WinSize},
};

#[derive(Debug, Default, States, PartialEq, Eq, Clone, Hash)]
pub enum GameState {
    #[default]
    Menu,
    InGame,
    Paused,
    GameOver,
}

pub struct StatesPlugin;

impl Plugin for StatesPlugin {
    fn build(&self, app: &mut bevy::prelude::App) {
        app
            //菜單狀態(tài)
            .add_system(menu_display_system.in_schedule(OnEnter(GameState::Menu)))
            .add_system(enter_game_system.in_set(OnUpdate(GameState::Menu)))
            .add_system(exit_menu.in_schedule(OnExit(GameState::Menu)))
            // 暫停狀態(tài)
            .add_system(enter_paused_system.in_schedule(OnEnter(GameState::Paused)))
            .add_system(paused_input_system.in_set(OnUpdate(GameState::Paused)))
            .add_system(paused_exit_system.in_schedule(OnExit(GameState::Paused)))
            // 游戲中狀態(tài)
            .add_system(in_game_display_system.in_schedule(OnEnter(GameState::InGame)))
            .add_system(in_game_input_system.in_set(OnUpdate(GameState::InGame)))
            .add_system(exit_game_system.in_schedule(OnExit(GameState::InGame)))
            // 游戲結(jié)束狀態(tài)
            .add_system(game_over_enter_system.in_schedule(OnEnter(GameState::GameOver)))
            .add_system(in_game_over_system.in_set(OnUpdate(GameState::GameOver)))
            .add_system(game_over_exit_system.in_schedule(OnExit(GameState::GameOver)));
    }
}

//// 進(jìn)入菜單頁面
fn menu_display_system(mut commands: Commands, static_assets: Res<StaticAssets>) {
    let font = static_assets.kenney_future_font.clone();
    let common_style = TextStyle {
        font: font.clone(),
        font_size: 32.,
        color: Color::BLUE,
        ..Default::default()
    };
    let special_style = TextStyle {
        font: font.clone(),
        font_size: 38.,
        color: Color::RED,
        ..Default::default()
    };

    let align = TextAlignment::Center;
    commands.spawn((
        Text2dBundle {
            text: Text::from_sections(vec![
                TextSection::new("PRESS \r\n".to_owned(), common_style.clone()),
                TextSection::new(" SPACE \r\n".to_owned(), special_style.clone()),
                TextSection::new("START GAME!\r\n".to_owned(), common_style.clone()),
                TextSection::new(" P \r\n".to_owned(), special_style.clone()),
                TextSection::new("PAUSED GAME!\r\n".to_owned(), common_style.clone()),
            ])
            .with_alignment(align),
            transform: Transform {
                translation: Vec3::new(0., 0., 4.),
                ..Default::default()
            },
            ..Default::default()
        },
        DisplayMenu,
    ));
}

//// 進(jìn)入游戲顯示系統(tǒng)
fn in_game_display_system(
    mut commands: Commands,
    win_size: Res<WinSize>,
    static_assets: Res<StaticAssets>,
) {
    let font = static_assets.kenney_future_font.clone();
    let common_style = TextStyle {
        font: font.clone(),
        font_size: 32.,
        color: Color::BLUE,
        ..Default::default()
    };
    let special_style = TextStyle {
        font: font.clone(),
        font_size: 38.,
        color: Color::RED,
        ..Default::default()
    };
    let y = -(win_size.height / 2. - GROUND_IMG_SIZE.1 + special_style.font_size * 1.5);
    let align = TextAlignment::Center;
    commands.spawn((
        Text2dBundle {
            text: Text::from_sections(vec![
                TextSection::new("SCORE: ".to_owned(), common_style),
                TextSection::new("0".to_owned(), special_style),
            ])
            .with_alignment(align),
            transform: Transform {
                translation: Vec3::new(0., y, 6.),
                ..Default::default()
            },
            ..Default::default()
        },
        DisplayScore,
    ));
}

/// 進(jìn)入游戲
fn enter_game_system(kb: Res<Input<KeyCode>>, mut state: ResMut<NextState<GameState>>) {
    if kb.just_released(KeyCode::Space) {
        state.set(GameState::InGame)
    }
}

/// 退出游戲
fn exit_game_system(
    mut commands: Commands,
    query: Query<Entity, (With<Text>, With<DisplayScore>)>,
) {
    for entity in query.iter() {
        commands.entity(entity).despawn();
    }
}
/// 退出菜單
fn exit_menu(mut commands: Commands, query: Query<Entity, (With<Text>, With<DisplayMenu>)>) {
    for entity in query.iter() {
        commands.entity(entity).despawn();
    }
}

/// 進(jìn)入暫停狀態(tài)下運(yùn)行的系統(tǒng)
pub fn enter_paused_system(mut commands: Commands, static_assets: Res<StaticAssets>) {
    // 字體引入
    let font = static_assets.kenney_future_font.clone();
    let common_style = TextStyle {
        font: font.clone(),
        font_size: 32.,
        color: Color::BLUE,
        ..Default::default()
    };
    let special_style = TextStyle {
        font: font.clone(),
        font_size: 38.,
        color: Color::RED,
        ..Default::default()
    };

    let align = TextAlignment::Center;
    commands.spawn((
        Text2dBundle {
            text: Text::from_sections(vec![
                TextSection::new("PAUSED  \r\n".to_owned(), common_style.clone()),
                TextSection::new(" R \r\n".to_owned(), special_style.clone()),
                TextSection::new("RETURN GAME!".to_owned(), common_style.clone()),
            ])
            .with_alignment(align),
            transform: Transform {
                translation: Vec3::new(0., 0., 4.),
                ..Default::default()
            },
            ..Default::default()
        },
        DisplayMenu,
    ));
}

/// 暫停狀態(tài)狀態(tài)下的鍵盤監(jiān)聽系統(tǒng)
pub fn paused_input_system(kb: Res<Input<KeyCode>>, mut next_state: ResMut<NextState<GameState>>) {
    if kb.pressed(KeyCode::R) {
        next_state.set(GameState::InGame);
    }
}

/// 退出暫停狀態(tài)時執(zhí)行的系統(tǒng)
pub fn paused_exit_system(
    mut commands: Commands,
    query: Query<Entity, (With<Text>, With<DisplayMenu>)>,
) {
    for entity in query.iter() {
        commands.entity(entity).despawn();
    }
}

/// 游戲中監(jiān)聽暫停
pub fn in_game_input_system(kb: Res<Input<KeyCode>>, mut next_state: ResMut<NextState<GameState>>) {
    if kb.pressed(KeyCode::P) {
        next_state.set(GameState::Paused);
    }
}

/// 游戲結(jié)束狀態(tài)下運(yùn)行的系統(tǒng)
pub fn game_over_enter_system(
    mut commands: Commands,
    game_data: Res<GameData>,
    static_assets: Res<StaticAssets>,
) {
    // 字體引入
    let font = static_assets.kenney_future_font.clone();
    let common_style = TextStyle {
        font: font.clone(),
        font_size: 32.,
        color: Color::BLUE,
        ..Default::default()
    };
    let special_style = TextStyle {
        font: font.clone(),
        font_size: 38.,
        color: Color::RED,
        ..Default::default()
    };

    let align = TextAlignment::Center;
    commands.spawn((
        Text2dBundle {
            text: Text::from_sections(vec![
                TextSection::new(
                    "GAME OVER !  \r\n You got ".to_owned(),
                    common_style.clone(),
                ),
                TextSection::new(game_data.get_score().to_string(), special_style.clone()),
                TextSection::new(" score. \r\n  ".to_owned(), common_style.clone()),
                TextSection::new("SPACE ".to_owned(), special_style.clone()),
                TextSection::new("RESTART GAME! \r\n".to_owned(), common_style.clone()),
                TextSection::new("M ".to_owned(), special_style.clone()),
                TextSection::new("TO MENU".to_owned(), common_style.clone()),
            ])
            .with_alignment(align),
            transform: Transform {
                translation: Vec3::new(0., 80., 4.),
                ..Default::default()
            },
            ..Default::default()
        },
        DisplayGameOver,
    ));
}

/// 退出游戲狀態(tài)時執(zhí)行的系統(tǒng)
pub fn game_over_exit_system(
    mut commands: Commands,
    query: Query<Entity, (With<Text>, With<DisplayGameOver>)>,
    obstacle_query: Query<Entity, With<Obstacle>>,
    player_query: Query<Entity, With<Player>>,
) {
    for entity in query.iter() {
        commands.entity(entity).despawn();
    }
    for entity in obstacle_query.iter() {
        commands.entity(entity).despawn();
    }
    for entity in player_query.iter() {
        commands.entity(entity).despawn();
    }
}

/// 退出游戲狀態(tài)監(jiān)聽
pub fn in_game_over_system(
    kb: Res<Input<KeyCode>>,
    mut game_data: ResMut<GameData>,
    mut next_state: ResMut<NextState<GameState>>,
) {
    game_data.death();
    if kb.pressed(KeyCode::M) {
        next_state.set(GameState::Menu);
    } else if kb.pressed(KeyCode::Space) {
        next_state.set(GameState::InGame);
    }
}

Cargo.toml

[package]
name = "flappy_bird_bevy"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bevy = { version = "0.10.1" }
rand = "0.8.5"

[workspace]
resolver = "2"

about me

目前失業(yè),在家學(xué)習(xí) rust 。

我的 bilibili,我的 Github。

Rust官網(wǎng)
Rust 中文社區(qū)文章來源地址http://www.zghlxwxcb.cn/news/detail-423773.html

到了這里,關(guān)于一個簡單的 rust 項(xiàng)目 使用 bevy 引擎 復(fù)刻 Flappy Bird 小游戲的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 使用Eclipse創(chuàng)建一個簡單的servlet項(xiàng)目

    使用Eclipse創(chuàng)建一個簡單的servlet項(xiàng)目

    ? 再找個位置配置你下載的Tomcat的版本? ?點(diǎn)擊next ? ?Tomcat存放的位置 本地JRE? 點(diǎn)擊finish結(jié)束 ? 新建完成 ? ?三、實(shí)現(xiàn)一個表單提交驗(yàn)證 1.在Java Resources下的src下通過new創(chuàng)建一個包,并在該包下創(chuàng)建一個servlet類 ? ?點(diǎn)擊next (輸入描述后點(diǎn)擊next)? (初學(xué)的話,不需要更改)

    2023年04月20日
    瀏覽(21)
  • 使用Jenkins構(gòu)建發(fā)布一個簡單的maven項(xiàng)目

    使用Jenkins構(gòu)建發(fā)布一個簡單的maven項(xiàng)目

    上一章,完成了jenkins在ubuntu上的安裝,這一章將使用單個Jenkins服務(wù)完成一個maven項(xiàng)目的打包和發(fā)布。 用到的插件有:Maven Integration、Git、Publish Over SSH三個,在Dashboard - Manage Jenkins - Plugins -Available plugins里搜索并安裝。 2.1、配置好JDK 在Dashboard - Manage Jenkins - Tools里將JAVA_HOME的目

    2024年02月16日
    瀏覽(18)
  • 如何搭建一個簡單的springCloudAlibaba項(xiàng)目,并實(shí)現(xiàn)基本的使用

    如何搭建一個簡單的springCloudAlibaba項(xiàng)目,并實(shí)現(xiàn)基本的使用

    微服務(wù)是一種軟件架構(gòu)風(fēng)格,它將一個大型應(yīng)用程序拆分成一組更小、獨(dú)立的服務(wù),每個服務(wù)都可以獨(dú)立開發(fā)、部署和擴(kuò)展。每個服務(wù)都有自己的業(yè)務(wù)邏輯和數(shù)據(jù)庫,并且通過輕量級通信機(jī)制(如RESTful API)來相互通信。 微服務(wù)架構(gòu)的優(yōu)點(diǎn)包括 可擴(kuò)展性:由于每個服務(wù)都是獨(dú)

    2024年01月21日
    瀏覽(25)
  • 使用node.js 搭建一個簡單的HelloWorld Web項(xiàng)目

    使用node.js 搭建一個簡單的HelloWorld Web項(xiàng)目

    文檔結(jié)構(gòu) config.ini one.js 使用方法 啟動內(nèi)網(wǎng)穿透 在控制臺啟動js文件 訪問網(wǎng)頁 修改為8081 登錄natapp官網(wǎng) 成功訪問

    2024年02月14日
    瀏覽(34)
  • 【Rust】Rust學(xué)習(xí) 第十二章一個 I/O 項(xiàng)目:構(gòu)建一個命令行程序

    【Rust】Rust學(xué)習(xí) 第十二章一個 I/O 項(xiàng)目:構(gòu)建一個命令行程序

    本章既是一個目前所學(xué)的很多技能的概括,也是一個更多標(biāo)準(zhǔn)庫功能的探索。我們將構(gòu)建一個與文件和命令行輸入/輸出交互的命令行工具來練習(xí)現(xiàn)在一些你已經(jīng)掌握的 Rust 技能。 Rust 的運(yùn)行速度、安全性、單二進(jìn)制文件輸出和跨平臺支持使其成為創(chuàng)建命令行程序的絕佳選擇,

    2024年02月12日
    瀏覽(23)
  • 【Pytorch】第 9 章 :Capstone 項(xiàng)目——用 DQN 玩 Flappy Bird

    【Pytorch】第 9 章 :Capstone 項(xiàng)目——用 DQN 玩 Flappy Bird

    ?????????大家好,我是Sonhhxg_柒,希望你看完之后,能對你有所幫助,不足請指正!共同學(xué)習(xí)交流?? ??個人主頁-Sonhhxg_柒的博客_CSDN博客??? ??歡迎各位→點(diǎn)贊?? + 收藏?? + 留言??? ??系列專欄 - 機(jī)器學(xué)習(xí)【ML】?自然語言處理【NLP】? 深度學(xué)習(xí)【DL】 ?? ???

    2024年02月16日
    瀏覽(22)
  • 詳細(xì)地講解使用MyEclipse創(chuàng)建一個簡單的html與servlet交互的JavaWeb項(xiàng)目

    詳細(xì)地講解使用MyEclipse創(chuàng)建一個簡單的html與servlet交互的JavaWeb項(xiàng)目

    如圖:在用戶名和密碼輸入內(nèi)容后,點(diǎn)擊登錄跳轉(zhuǎn)到下一個頁面 這個圖片里面的驗(yàn)證碼、下拉框什么的可以忽略,我們只做用戶名和密碼,因?yàn)椴簧婕斑B接到數(shù)據(jù)庫,我們的密碼是隨便輸入的。 下面我們來開始創(chuàng)建項(xiàng)目 打開MyEclipse,點(diǎn)擊左上角的File,選擇new,再選擇Web P

    2024年02月06日
    瀏覽(25)
  • Python (Pygame) 游戲開發(fā)項(xiàng)目實(shí)戰(zhàn): 飛揚(yáng)的小鳥 (Flappy Bird, 像素鳥)

    Python (Pygame) 游戲開發(fā)項(xiàng)目實(shí)戰(zhàn): 飛揚(yáng)的小鳥 (Flappy Bird, 像素鳥)

    原文鏈接:https://xiets.blog.csdn.net/article/details/131791045 版權(quán)聲明:原創(chuàng)文章禁止轉(zhuǎn)載 專欄目錄:Pygame 專欄(總目錄) 使用 Python Pygame 開發(fā)一個 Flappy Bird 小游戲,也叫 飛揚(yáng)的小鳥、像素鳥。 Flappy Bird 是一款簡單而富有挑戰(zhàn)性的益智休閑游戲。玩家只需要點(diǎn)擊屏幕即可操作。點(diǎn)

    2024年02月13日
    瀏覽(16)
  • Rust程序語言設(shè)計 第十二章 一個 I/O 項(xiàng)目:構(gòu)建一個命令行程序

    本章既是一個目前所學(xué)的很多技能的概括,也是一個更多標(biāo)準(zhǔn)庫功能的探索。我們將構(gòu)建一個與文件和命令行輸入/輸出交互的命令行工具來練習(xí)現(xiàn)在一些你已經(jīng)掌握的 Rust 技能。 Rust 的運(yùn)行速度、安全性、單二進(jìn)制文件輸出和跨平臺支持使其成為創(chuàng)建命令行程序的絕佳選擇,

    2024年02月13日
    瀏覽(37)
  • 【Go語言開發(fā)】簡單了解一下搜索引擎并用go寫一個demo

    【Go語言開發(fā)】簡單了解一下搜索引擎并用go寫一個demo

    這篇文章我們一起來了解一下搜索引擎的原理,以及用go寫一個小demo來體驗(yàn)一下搜索引擎。 搜索引擎一般簡化為三個步驟 爬蟲:爬取數(shù)據(jù)源,用做搜索數(shù)據(jù)支持。 索引:根據(jù)爬蟲爬取到的數(shù)據(jù)進(jìn)行索引的建立。 排序:對搜索的結(jié)果進(jìn)行排序。 然后我們再對幾個專業(yè)名詞做

    2024年02月16日
    瀏覽(26)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包