一个简单的 rust 项目 飞机大战

打印 上一主题 下一主题

主题 912|帖子 912|积分 2736

Rust 实现的飞机游戏

简介

一个使用 bevy 引擎制作的飞机游戏。
视频教程地址,github 地址
因为 bevy 已经升级到 0.10.1 了,所以重新做一遍。顺带手出个教程。
下面是做的部分变动:

  • 将激光以及玩家的移动模块进行了拆分。
  • 新增了背景图片。
  • 新增了游戏状态管理 Welcome/InGame/Paused。
  • 新增了声音播放模块。
  • 新增了游戏记分板。
通过左右方向键进行控制,使用空格发射激光。
按 P 暂停游戏,按 S 恢复游戏。
更新后的GitHub地址
代码结构
  1. ·
  2. ├── assets/
  3. │   ├──audios/
  4. │   ├──images/
  5. ├── src/
  6. │   ├──enemy/
  7. │   │  ├── formation.rs
  8. │   │  └── mod.rs
  9. │   ├── components.rs
  10. │   ├── constants.rs
  11. │   ├── main.rs
  12. │   ├── player.rs
  13. │   ├── resource.rs
  14. │   └── state.rs
  15. ├── Cargo.lock
  16. └── Cargo.toml
复制代码

  • assets/audios 声音资源文件。
  • assets/images 图片资源文件。
  • enemy/formation.rs 敌人阵型系统的实现。
  • enemy/mod.rs 敌人插件,生成、移动、攻击的实现。
  • components.rs 负责游戏的逻辑、控制、等内容。
  • constants.rs 负责存储游戏中用到的常量。
  • main.rs 负责游戏的逻辑、控制、等内容。
  • player.rs 玩家角色插件,生成、移动、攻击、键盘处理的实现。
  • resource.rs 游戏资源定义。
  • state.rs 游戏组件定义。
两点间的距离公式 \(|AB|=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\)
enemy/formation.rs
  1. use bevy::prelude::{Component, Resource};
  2. use rand::{thread_rng, Rng};
  3. use crate::{WinSize, BASE_SPEED, FORMATION_MEMBER_MAX};
  4. /// 敌人阵型
  5. #[derive(Component, Clone)]
  6. pub struct Formation {
  7.     /// 启始位置
  8.     pub start: (f32, f32),
  9.     /// 半径
  10.     pub radius: (f32, f32),
  11.     /// 原点
  12.     pub pivot: (f32, f32),
  13.     /// 速度
  14.     pub speed: f32,
  15.     /// 角度
  16.     pub angle: f32,
  17. }
  18. /// 阵型资源
  19. #[derive(Resource, Default)]
  20. pub struct FormationMaker {
  21.     /// 当前阵型
  22.     current_template: Option<Formation>,
  23.     /// 当前数量
  24.     current_members: u32,
  25. }
  26. impl FormationMaker {
  27.     pub fn make(&mut self, win_size: &WinSize) -> Formation {
  28.         match (
  29.             &self.current_template,
  30.             self.current_members >= FORMATION_MEMBER_MAX,
  31.         ) {
  32.             // 当前阵型还有空位 直接加入
  33.             (Some(template), false) => {
  34.                 self.current_members += 1;
  35.                 template.clone()
  36.             }
  37.             // 当前阵型没有空位,或还没有阵型,需要创建新的阵型
  38.             _ => {
  39.                 let mut rng = thread_rng();
  40.                 // 生成 起点坐标
  41.                 let w_spawn = win_size.w / 2. + 100.;
  42.                 let h_spawn = win_size.h / 2. + 100.;
  43.                 let x = if rng.gen_bool(0.5) { w_spawn } else { -w_spawn };
  44.                 let y = rng.gen_range(-h_spawn..h_spawn);
  45.                 let start = (x, y);
  46.                 // 生成原点坐标
  47.                 let w_spawn = win_size.w / 4.;
  48.                 let h_spawn = win_size.h / 3. + 50.;
  49.                 let pivot = (
  50.                     rng.gen_range(-w_spawn..w_spawn),
  51.                     rng.gen_range(0. ..h_spawn),
  52.                 );
  53.                 // 生成半径
  54.                 let radius = (rng.gen_range(80. ..150.), 100.);
  55.                 // 计算初始角度
  56.                 let angle = (y - pivot.1).atan2(x - pivot.0);
  57.                 // 速度
  58.                 let speed = BASE_SPEED;
  59.                 let formation = Formation {
  60.                     start,
  61.                     pivot,
  62.                     radius,
  63.                     angle,
  64.                     speed,
  65.                 };
  66.                 self.current_template = Some(formation.clone());
  67.                 self.current_members = 1;
  68.                 formation
  69.             }
  70.         }
  71.     }
  72. }
复制代码
enemy/mod.rs
  1. use std::{f32::consts::PI, time::Duration};
  2. use crate::{
  3.     components::{Enemy, FromEnemy, Laser, Movable, SpriteSize, Velocity},
  4.     resource::GameState,
  5.     GameTextures, MaxEnemy, WinSize, ENEMY_LASER_SIZE, ENEMY_SIZE, MAX_ENEMY, SPRITE_SCALE,
  6.     TIME_STEP,
  7. };
  8. use bevy::{prelude::*, time::common_conditions::on_timer};
  9. use rand::{thread_rng, Rng};
  10. use self::formation::{Formation, FormationMaker};
  11. mod formation;
  12. #[derive(Component)]
  13. pub struct EnemyPlugin;
  14. impl Plugin for EnemyPlugin {
  15.     fn build(&self, app: &mut App) {
  16.         // 间隔执行
  17.         app.insert_resource(FormationMaker::default())
  18.             .add_system(
  19.                 enemy_spawn_system
  20.                     .run_if(on_timer(Duration::from_secs_f32(0.5)))
  21.                     .in_set(OnUpdate(GameState::InGame)),
  22.             )
  23.             .add_system(
  24.                 enemy_fire_system
  25.                     .run_if(enemy_fire_criteria)
  26.                     .in_set(OnUpdate(GameState::InGame)),
  27.             )
  28.             .add_system(enemy_movement_system.in_set(OnUpdate(GameState::InGame)));
  29.     }
  30. }
  31. /// 敌人生成系统
  32. fn enemy_spawn_system(
  33.     mut commands: Commands,
  34.     mut max_enemy: ResMut<MaxEnemy>,
  35.     mut formation_maker: ResMut<FormationMaker>,
  36.     game_textures: Res<GameTextures>,
  37.     win_size: Res<WinSize>,
  38. ) {
  39.     // 如果当前的敌人数量大于等于最大敌人数量,则不再产生新的敌人
  40.     if max_enemy.0 >= MAX_ENEMY {
  41.         return;
  42.     }
  43.     // 随机生成
  44.     // let mut rng = thread_rng();
  45.     // let w_span = win_size.w / 2. - 100.;
  46.     // let h_span = win_size.h / 2. - 100.;
  47.     // let x = rng.gen_range(-w_span..w_span);
  48.     // let y = rng.gen_range(-h_span..h_span);
  49.     // 使用 阵型
  50.     let formation = formation_maker.make(&win_size);
  51.     let (x, y) = formation.start;
  52.     commands
  53.         .spawn(SpriteBundle {
  54.             texture: game_textures.enemy.clone(),
  55.             transform: Transform {
  56.                 // 坐标
  57.                 translation: Vec3::new(x, y, 10.),
  58.                 // 缩放
  59.                 scale: Vec3::new(SPRITE_SCALE, SPRITE_SCALE, 1.),
  60.                 // 旋转
  61.                 rotation: Quat::IDENTITY,
  62.             },
  63.             ..Default::default()
  64.         })
  65.         .insert(Enemy)
  66.         .insert(formation)
  67.         .insert(SpriteSize::from(ENEMY_SIZE));
  68.     max_enemy.0 += 1;
  69. }
  70. /// 敌人射击系统
  71. fn enemy_fire_system(
  72.     mut commands: Commands,
  73.     game_textures: Res<GameTextures>,
  74.     query: Query<&Transform, With<Enemy>>,
  75. ) {
  76.     for &enemy_tf in query.iter() {
  77.         let (x, y) = (enemy_tf.translation.x, enemy_tf.translation.y);
  78.         commands
  79.             .spawn(SpriteBundle {
  80.                 texture: game_textures.enemy_laser.clone(),
  81.                 transform: Transform {
  82.                     translation: Vec3::new(x, y, 1.),
  83.                     scale: Vec3::new(SPRITE_SCALE, SPRITE_SCALE, 1.),
  84.                     rotation: Quat::from_rotation_x(PI),
  85.                 },
  86.                 ..Default::default()
  87.             })
  88.             .insert(Laser)
  89.             .insert(SpriteSize::from(ENEMY_LASER_SIZE))
  90.             .insert(FromEnemy)
  91.             .insert(Movable { auto_despawn: true })
  92.             .insert(Velocity::new(0., -1.));
  93.     }
  94. }
  95. /// 是否发射攻击
  96. fn enemy_fire_criteria() -> bool {
  97.     if thread_rng().gen_bool(1. / 60.) {
  98.         true
  99.     } else {
  100.         false
  101.     }
  102. }
  103. /// 敌人移动系统
  104. ///
  105. /// 两点间的距离公式 $|AB|=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}$
  106. fn enemy_movement_system(mut query: Query<(&mut Transform, &mut Formation), With<Enemy>>) {
  107.     // 当前时间
  108.     // let now = time.elapsed_seconds();
  109.     for (mut transform, mut formation) in query.iter_mut() {
  110.         // 当前坐标
  111.         let (x_org, y_org) = (transform.translation.x, transform.translation.y);
  112.         // let (x_org, y_org) = formation.start;
  113.         // 单位时间内最大移动距离
  114.         // let max_distance = BASE_SPEED * TIME_STEP;
  115.         let max_distance = formation.speed * TIME_STEP;
  116.         // 方向 1 顺时针 -1 逆时针
  117.         // let dir = -1.;
  118.         let dir = if formation.start.0 < 0. { 1. } else { -1. };
  119.         // 中心点
  120.         // let (x_pivot, y_pivot) = (0., 0.);
  121.         let (x_pivot, y_pivot) = formation.pivot;
  122.         // 半径
  123.         // let (x_radius, y_radius) = (200., 130.);
  124.         let (x_radius, y_radius) = formation.radius;
  125.         // 基于当前时间计算的角度
  126.         // let angel = dir * BASE_SPEED * TIME_STEP * now % 360. / PI;
  127.         let angel = formation.angle
  128.             + dir * formation.speed * TIME_STEP / (x_radius.min(y_radius) * PI / 2.);
  129.         // 计算目标点位
  130.         let x_dst = x_radius * angel.cos() + x_pivot;
  131.         let y_dst = y_radius * angel.sin() + y_pivot;
  132.         // 计算距离
  133.         // 两点间的距离公式 根号下 a.x - b.x
  134.         let dx = x_org - x_dst;
  135.         let dy = y_org - y_dst;
  136.         let distance = (dx * dx + dy * dy).sqrt();
  137.         let distance_radio = if distance != 0. {
  138.             max_distance / distance
  139.         } else {
  140.             0.
  141.         };
  142.         // 计算 x y 的最终坐标
  143.         let x = x_org - dx * distance_radio;
  144.         let x = if dx > 0. { x.max(x_dst) } else { x.min(x_dst) };
  145.         let y = y_org - dy * distance_radio;
  146.         let y = if dy > 0. { y.max(y_dst) } else { y.min(y_dst) };
  147.         // 图片资源在椭圆上 或接近椭圆时开始加入旋转
  148.         if distance < max_distance * formation.speed / 20. {
  149.             formation.angle = angel;
  150.         }
  151.         let translation = &mut transform.translation;
  152.         (translation.x, translation.y) = (x, y);
  153.     }
  154. }
复制代码
components.rs
  1. use bevy::{
  2.     prelude::{Component, Vec2, Vec3},
  3.     time::{Timer, TimerMode},
  4. };
  5. // 通用控制组件
  6. #[derive(Component)]
  7. pub struct Velocity {
  8.     pub x: f32,
  9.     pub y: f32,
  10. }
  11. impl Velocity {
  12.     pub fn new(x: f32, y: f32) -> Self {
  13.         Self { x, y }
  14.     }
  15. }
  16. /// 移动能力组件
  17. #[derive(Component)]
  18. pub struct Movable {
  19.     /// 自动销毁
  20.     pub auto_despawn: bool,
  21. }
  22. /// 玩家组件
  23. #[derive(Component)]
  24. pub struct Player;
  25. /// 玩家信息组件
  26. #[derive(Component)]
  27. pub struct FromPlayer;
  28. /// 敌人组件
  29. #[derive(Component)]
  30. pub struct Enemy;
  31. /// 敌人信息组件
  32. #[derive(Component)]
  33. pub struct FromEnemy;
  34. /// 激光组件
  35. #[derive(Component)]
  36. pub struct Laser;
  37. /// 图片大小组件
  38. #[derive(Component)]
  39. pub struct SpriteSize(pub Vec2);
  40. /// 实现 (f32,f32) 转 SpritSize
  41. impl From<(f32, f32)> for SpriteSize {
  42.     fn from(value: (f32, f32)) -> Self {
  43.         Self(Vec2::new(value.0, value.1))
  44.     }
  45. }
  46. /// 爆炸组件
  47. #[derive(Component)]
  48. pub struct Explosion;
  49. /// 产生爆炸组件
  50. #[derive(Component)]
  51. pub struct ExplosionToSpawn(pub Vec3);
  52. /// 爆炸事件组件
  53. #[derive(Component)]
  54. pub struct ExplosionTimer(pub Timer);
  55. impl Default for ExplosionTimer {
  56.     fn default() -> Self {
  57.         Self(Timer::from_seconds(0.05, TimerMode::Once))
  58.     }
  59. }
  60. /// 分数显示组件
  61. #[derive(Component)]
  62. pub struct DisplayScore;
  63. /// 欢迎组件
  64. #[derive(Component)]
  65. pub struct WelcomeText;
  66. /// 暂停组件
  67. #[derive(Component)]
  68. pub struct PausedText;
复制代码
constants.rs
  1. /// 游戏背景图片路径
  2. pub const BACKGROUND_SPRITE: &str = "images/planet05.png";
  3. /// 玩家图片路径
  4. pub const PLAYER_SPRITE: &str = "images/player_a_01.png";
  5. /// 玩家大小
  6. pub const PLAYER_SIZE: (f32, f32) = (144., 75.);
  7. /// 玩家攻击图片路径
  8. pub const PLAYER_LASER_SPRITE: &str = "images/laser_a_01.png";
  9. /// 玩家攻击图片大小
  10. pub const PLAYER_LASER_SIZE: (f32, f32) = (9., 54.);
  11. /// 敌人图片路径
  12. pub const ENEMY_SPRITE: &str = "images/enemy_a_01.png";
  13. /// 敌人大小
  14. pub const ENEMY_SIZE: (f32, f32) = (144., 75.);
  15. /// 敌人攻击图片路径
  16. pub const ENEMY_LASER_SPRITE: &str = "images/laser_b_01.png";
  17. /// 敌人攻击图片大小
  18. pub const ENEMY_LASER_SIZE: (f32, f32) = (17., 55.);
  19. /// 爆炸图片路径
  20. pub const EXPLOSION_SHEET: &str = "images/explosion_a_sheet.png";
  21. /// 爆炸图片大小
  22. pub const EXPLOSION_SIZE: (f32, f32) = (64., 64.);
  23. /// 爆炸画面帧数
  24. pub const EXPLOSION_ANIMATION_LEN: usize = 16;
  25. /// 图片缩放比例
  26. pub const SPRITE_SCALE: f32 = 0.5;
  27. /// 步长 (帧数)
  28. pub const TIME_STEP: f32 = 1. / 60.;
  29. /// 基础速度
  30. pub const BASE_SPEED: f32 = 500.;
  31. /// 敌人最大数量
  32. pub const MAX_ENEMY: u32 = 2;
  33. /// 玩家自动重生时间
  34. pub const PLAYER_RESPAWN_DELAY: f64 = 2.;
  35. /// 阵型内敌人最大数量
  36. pub const FORMATION_MEMBER_MAX: u32 = 2;
  37. /// 敌人被摧毁声音
  38. pub const ENEMY_EXPLOSION_AUDIO: &str = "audios/enemy_explosion.ogg";
  39. /// 玩家被摧毁的声音
  40. pub const PLAYER_EXPLOSION_AUDIO: &str = "audios/player_explosion.ogg";
  41. /// 玩家发射激光的声音
  42. pub const PLAYER_LASER_AUDIO: &str = "audios/player_laser.ogg";
  43. /// 字体路径
  44. pub const KENNEY_BLOCK_FONT: &str = "fonts/kenney_blocks.ttf";
复制代码
main.rs
  1. use bevy::{math::Vec3Swizzles, prelude::*, sprite::collide_aabb::collide, utils::HashSet};
  2. use components::*;
  3. use constants::*;
  4. use enemy::EnemyPlugin;
  5. use player::PlayerPlugin;
  6. use resource::{GameAudio, GameData, GameState, GameTextures, MaxEnemy, PlayerState, WinSize};
  7. use state::StatePlugin;
  8. mod components;
  9. mod constants;
  10. mod enemy;
  11. mod player;
  12. mod resource;
  13. mod state;
  14. fn main() {
  15.     // add_startup_system 启动生命周期时只运行一次 ,
  16.     // add_system 每帧都会被调用方法
  17.     App::new()
  18.         .add_state::<GameState>()
  19.         .insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04)))
  20.         .add_plugins(DefaultPlugins.set(WindowPlugin {
  21.             primary_window: Some(Window {
  22.                 title: "Invaders".to_owned(),
  23.                 resolution: (598., 676.).into(),
  24.                 position: WindowPosition::At(IVec2::new(2282, 0)),
  25.                 ..Window::default()
  26.             }),
  27.             ..WindowPlugin::default()
  28.         }))
  29.         .add_plugin(PlayerPlugin)
  30.         .add_plugin(EnemyPlugin)
  31.         .add_plugin(StatePlugin)
  32.         .add_startup_system(setup_system)
  33.         // InGame 状态下执行的函数
  34.         .add_systems(
  35.             (
  36.                 laser_movable_system,
  37.                 player_laser_hit_enemy_system,
  38.                 explosion_to_spawn_system,
  39.                 explosion_animation_system,
  40.                 enemy_laser_hit_player_system,
  41.                 score_display_update_system,
  42.             )
  43.                 .in_set(OnUpdate(GameState::InGame)),
  44.         )
  45.         // 启动 esc 键退出程序
  46.         .add_system(bevy::window::close_on_esc)
  47.         .run();
  48. }
  49. /// 资源加载
  50. fn setup_system(
  51.     mut commands: Commands,
  52.     asset_server: Res<AssetServer>,
  53.     mut texture_atlases: ResMut<Assets<TextureAtlas>>,
  54.     mut windows: Query<&mut Window>,
  55. ) {
  56.     // 创建2d镜头
  57.     commands.spawn(Camera2dBundle::default());
  58.     // 获取当前窗口
  59.     let window = windows.single_mut();
  60.     let win_w = window.width();
  61.     let win_h = window.height();
  62.     //  添加 WinSize 资源
  63.     let win_size = WinSize { w: win_w, h: win_h };
  64.     commands.insert_resource(win_size);
  65.     // 创建爆炸动画
  66.     let texture_handle = asset_server.load(EXPLOSION_SHEET);
  67.     let texture_atlas =
  68.         TextureAtlas::from_grid(texture_handle, Vec2::from(EXPLOSION_SIZE), 4, 4, None, None);
  69.     let explosion = texture_atlases.add(texture_atlas);
  70.     // 添加 GameTextures
  71.     let game_texture = GameTextures {
  72.         background: asset_server.load(BACKGROUND_SPRITE),
  73.         player: asset_server.load(PLAYER_SPRITE),
  74.         player_laser: asset_server.load(PLAYER_LASER_SPRITE),
  75.         enemy: asset_server.load(ENEMY_SPRITE),
  76.         enemy_laser: asset_server.load(ENEMY_LASER_SPRITE),
  77.         font: asset_server.load(KENNEY_BLOCK_FONT),
  78.         explosion,
  79.     };
  80.     // 声音资源引入
  81.     let game_audio = GameAudio {
  82.         player_laser: asset_server.load(PLAYER_LASER_AUDIO),
  83.         player_explosion: asset_server.load(PLAYER_EXPLOSION_AUDIO),
  84.         enemy_explosion: asset_server.load(ENEMY_EXPLOSION_AUDIO),
  85.     };
  86.     // 背景图片
  87.     commands.spawn(SpriteBundle {
  88.         texture: game_texture.background.clone(),
  89.         sprite: Sprite {
  90.             custom_size: Some(Vec2 { x: win_w, y: win_h }),
  91.             ..Default::default()
  92.         },
  93.         transform: Transform::from_scale(Vec3::new(1.5, 1.5, 0.0)),
  94.         ..Default::default()
  95.     });
  96.     // 字体引入
  97.     let font = game_texture.font.clone();
  98.     let text_style = TextStyle {
  99.         font: font.clone(),
  100.         font_size: 32.,
  101.         color: Color::ANTIQUE_WHITE,
  102.     };
  103.     let text_alignment = TextAlignment::Center;
  104.     // 分数展示控件
  105.     commands.spawn((
  106.         Text2dBundle {
  107.             text: Text::from_section("SCORE:0", text_style).with_alignment(text_alignment),
  108.             transform: Transform {
  109.                 translation: Vec3 {
  110.                     x: 0.,
  111.                     y: win_h / 2. - 20.,
  112.                     z: 11.,
  113.                 },
  114.                 ..Default::default()
  115.             },
  116.             ..Default::default()
  117.         },
  118.         DisplayScore,
  119.     ));
  120.     let game_data = GameData::new();
  121.     commands.insert_resource(game_data);
  122.     commands.insert_resource(game_audio);
  123.     commands.insert_resource(game_texture);
  124.     commands.insert_resource(MaxEnemy(0));
  125. }
  126. /// 激光移动系统
  127. fn laser_movable_system(
  128.     mut commands: Commands,
  129.     win_size: Res<WinSize>,
  130.     mut query: Query<(Entity, &Velocity, &mut Transform, &Movable), With<Laser>>,
  131. ) {
  132.     for (entity, velocity, mut transform, movable) in query.iter_mut() {
  133.         // 移动位置
  134.         let translation = &mut transform.translation;
  135.         translation.x += velocity.x * BASE_SPEED * TIME_STEP;
  136.         translation.y += velocity.y * BASE_SPEED * TIME_STEP;
  137.         // 自动销毁
  138.         if movable.auto_despawn {
  139.             const MARGIN: f32 = 200.;
  140.             if translation.y > win_size.h / 2. + MARGIN
  141.                 || translation.y < -win_size.h / 2. - MARGIN
  142.                 || translation.x > win_size.w / 2. + MARGIN
  143.                 || translation.x < -win_size.w / 2. - MARGIN
  144.             {
  145.                 commands.entity(entity).despawn();
  146.             }
  147.         }
  148.     }
  149. }
  150. /// 敌人激光攻击玩家判定系统
  151. fn enemy_laser_hit_player_system(
  152.     mut commands: Commands,
  153.     mut player_state: ResMut<PlayerState>,
  154.     time: Res<Time>,
  155.     audio_source: Res<GameAudio>,
  156.     audio: Res<Audio>,
  157.     mut game_data: ResMut<GameData>,
  158.     mut next_state: ResMut<NextState<GameState>>,
  159.     laser_query: Query<(Entity, &Transform, &SpriteSize), (With<Laser>, With<FromEnemy>)>,
  160.     player_query: Query<(Entity, &Transform, &SpriteSize), With<Player>>,
  161. ) {
  162.     if let Ok((player_entity, player_tf, player_size)) = player_query.get_single() {
  163.         let player_scale = Vec2::from(player_tf.scale.xy());
  164.         for (laser, laser_tf, laser_size) in laser_query.into_iter() {
  165.             let laser_scale = Vec2::from(laser_tf.scale.xy());
  166.             let collision = collide(
  167.                 player_tf.translation,
  168.                 player_size.0 * player_scale,
  169.                 laser_tf.translation,
  170.                 laser_size.0 * laser_scale,
  171.             );
  172.             if let Some(_) = collision {
  173.                 // 播放音乐
  174.                 audio.play(audio_source.player_explosion.clone());
  175.                 // 重置分数
  176.                 game_data.reset_score();
  177.                 next_state.set(GameState::Welcome);
  178.                 // 销毁角色
  179.                 commands.entity(player_entity).despawn();
  180.                 // 记录被命中的时刻
  181.                 player_state.shot(time.elapsed_seconds_f64());
  182.                 // 销毁激光
  183.                 commands.entity(laser).despawn();
  184.                 // 产生爆炸动画
  185.                 commands.spawn(ExplosionToSpawn(player_tf.translation.clone()));
  186.                 break;
  187.             }
  188.         }
  189.     }
  190. }
  191. /// 玩家攻击敌人判定系统
  192. fn player_laser_hit_enemy_system(
  193.     mut commands: Commands,
  194.     audio_source: Res<GameAudio>,
  195.     audio: Res<Audio>,
  196.     mut max_enemy: ResMut<MaxEnemy>,
  197.     mut game_data: ResMut<GameData>,
  198.     laser_query: Query<(Entity, &Transform, &SpriteSize), (With<Laser>, With<FromPlayer>)>,
  199.     enemy_query: Query<(Entity, &Transform, &SpriteSize), With<Enemy>>,
  200. ) {
  201.     // 重复删除检测
  202.     let mut despawn_entities: HashSet<Entity> = HashSet::new();
  203.     // 玩家激光
  204.     for (laser_entity, laser_tf, laser_size) in laser_query.iter() {
  205.         if despawn_entities.contains(&laser_entity) {
  206.             continue;
  207.         }
  208.         // 玩家激光的坐标
  209.         let laser_scale = Vec2::from(laser_tf.scale.xy());
  210.         // 敌人
  211.         for (enemy_entity, enemy_tf, enemy_size) in enemy_query.iter() {
  212.             if despawn_entities.contains(&enemy_entity) || despawn_entities.contains(&laser_entity)
  213.             {
  214.                 continue;
  215.             }
  216.             // 敌人坐标
  217.             let enemy_scale = Vec2::from(enemy_tf.scale.xy());
  218.             // collide 定义两个元素的碰撞,a 点坐标,a 的大小,b 点坐标,b 的大小,如果未发生碰撞返回 None
  219.             let collision = collide(
  220.                 laser_tf.translation,
  221.                 laser_size.0 * laser_scale,
  222.                 enemy_tf.translation,
  223.                 enemy_size.0 * enemy_scale,
  224.             );
  225.             // 碰撞检测
  226.             if let Some(_) = collision {
  227.                 // 敌人数量 -1
  228.                 if max_enemy.0 != 0 {
  229.                     max_enemy.0 -= 1;
  230.                 }
  231.                 game_data.add_score();
  232.                 audio.play(audio_source.enemy_explosion.clone());
  233.                 // 销毁敌人
  234.                 commands.entity(enemy_entity).despawn();
  235.                 despawn_entities.insert(enemy_entity);
  236.                 // 销毁激光
  237.                 commands.entity(laser_entity).despawn();
  238.                 despawn_entities.insert(laser_entity);
  239.                 // 播放爆炸动画
  240.                 commands.spawn(ExplosionToSpawn(enemy_tf.translation.clone()));
  241.             }
  242.         }
  243.     }
  244. }
  245. /// 爆炸画面生成系统
  246. fn explosion_to_spawn_system(
  247.     mut commands: Commands,
  248.     game_textures: Res<GameTextures>,
  249.     query: Query<(Entity, &ExplosionToSpawn)>,
  250. ) {
  251.     for (explosion_spawn_entity, explosion_to_spawn) in query.iter() {
  252.         commands
  253.             .spawn(SpriteSheetBundle {
  254.                 texture_atlas: game_textures.explosion.clone(),
  255.                 transform: Transform {
  256.                     translation: explosion_to_spawn.0,
  257.                     ..Default::default()
  258.                 },
  259.                 ..Default::default()
  260.             })
  261.             .insert(Explosion)
  262.             .insert(ExplosionTimer::default());
  263.         commands.entity(explosion_spawn_entity).despawn();
  264.     }
  265. }
  266. /// 爆炸动画系统
  267. fn explosion_animation_system(
  268.     mut commands: Commands,
  269.     time: Res<Time>,
  270.     mut query: Query<(Entity, &mut ExplosionTimer, &mut TextureAtlasSprite), With<Explosion>>,
  271. ) {
  272.     for (entity, mut timer, mut texture_atlas_sprite) in query.iter_mut() {
  273.         timer.0.tick(time.delta());
  274.         if timer.0.finished() {
  275.             texture_atlas_sprite.index += 1;
  276.             if texture_atlas_sprite.index >= EXPLOSION_ANIMATION_LEN {
  277.                 commands.entity(entity).despawn();
  278.             }
  279.         }
  280.     }
  281. }
  282. /// 分数更新系统
  283. fn score_display_update_system(
  284.     game_data: Res<GameData>,
  285.     mut query: Query<&mut Text, With<DisplayScore>>,
  286. ) {
  287.     for mut text in &mut query {
  288.         let new_str: String = format!("SCORE:{}", game_data.get_score());
  289.         text.sections[0].value = new_str;
  290.     }
  291. }
复制代码
player.rs
  1. use bevy::{prelude::*, time::common_conditions::on_timer};
  2. use std::time::Duration;
  3. use crate::{
  4.     components::{FromPlayer, Laser, Movable, Player, SpriteSize, Velocity},
  5.     resource::GameAudio,
  6.     resource::PlayerState,
  7.     resource::WinSize,
  8.     resource::{GameState, GameTextures},
  9.     BASE_SPEED, PLAYER_LASER_SIZE, PLAYER_RESPAWN_DELAY, PLAYER_SIZE, SPRITE_SCALE, TIME_STEP,
  10. };
  11. pub struct PlayerPlugin;
  12. impl Plugin for PlayerPlugin {
  13.     fn build(&self, app: &mut App) {
  14.         // add_startup_system 应用程序生命周期开始时运行一次
  15.         // StartupSet::PostStartup 在 StartupSet::Startup 后运行一次
  16.         // add_startup_system(player_spawn_system.in_base_set(StartupSet::PostStartup))
  17.         // add_system 每帧都运行 , 可以在函数后通过 run_if 传入 bool 类型的条件进行限制
  18.         app.insert_resource(PlayerState::default())
  19.             .add_system(
  20.                 player_spawn_system
  21.                     .run_if(on_timer(Duration::from_secs_f32(0.5)))
  22.                     .in_set(OnUpdate(GameState::InGame)),
  23.             )
  24.             .add_systems(
  25.                 (
  26.                     player_keyboard_event_system,
  27.                     player_movable_system,
  28.                     player_fire_system,
  29.                 )
  30.                     .in_set(OnUpdate(GameState::InGame)),
  31.             );
  32.     }
  33. }
  34. /// 玩家角色生成系统
  35. fn player_spawn_system(
  36.     mut commands: Commands,
  37.     mut player_state: ResMut<PlayerState>,
  38.     time: Res<Time>,
  39.     game_textures: Res<GameTextures>,
  40.     win_size: Res<WinSize>,
  41. ) {
  42.     let now = time.elapsed_seconds_f64();
  43.     let last_shot = player_state.last_shot;
  44.     if !player_state.on && (player_state.last_shot == -1. || now - PLAYER_RESPAWN_DELAY > last_shot)
  45.     {
  46.         let bottom = -win_size.h / 2.;
  47.         // 创建组件实体,并返回对应的 EntityCommand
  48.         commands
  49.             .spawn(SpriteBundle {
  50.                 texture: game_textures.player.clone(),
  51.                 transform: Transform {
  52.                     translation: Vec3::new(
  53.                         0.,
  54.                         bottom + PLAYER_SIZE.1 / 2. * SPRITE_SCALE + 5.0,
  55.                         10.,
  56.                     ),
  57.                     scale: Vec3::new(SPRITE_SCALE, SPRITE_SCALE, 1.0),
  58.                     ..default()
  59.                 },
  60.                 ..SpriteBundle::default()
  61.             })
  62.             .insert(Velocity::new(0., 0.))
  63.             .insert(Movable {
  64.                 auto_despawn: false,
  65.             })
  66.             .insert(SpriteSize::from(PLAYER_SIZE))
  67.             .insert(Player);
  68.         player_state.spawned();
  69.     }
  70. }
  71. /// 玩家攻击系统
  72. fn player_fire_system(
  73.     mut commands: Commands,
  74.     audio_source: Res<GameAudio>,
  75.     audio: Res<Audio>,
  76.     kb: Res<Input<KeyCode>>,
  77.     game_textures: Res<GameTextures>,
  78.     query: Query<&Transform, With<Player>>,
  79. ) {
  80.     if let Ok(player_tf) = query.get_single() {
  81.         // just_released 松开按键
  82.         if kb.just_released(KeyCode::Space) {
  83.             audio.play(audio_source.player_laser.clone());
  84.             let (x, y) = (player_tf.translation.x, player_tf.translation.y);
  85.             let x_offset = PLAYER_SIZE.0 / 2. * SPRITE_SCALE - 5.;
  86.             // 激光生成闭包 因为这里使用了 commands 生成新的包 所以这里的闭包需要定义为 mut 类型
  87.             let mut spawn_laser = |x_offset: f32| {
  88.                 commands
  89.                     .spawn(SpriteBundle {
  90.                         texture: game_textures.player_laser.clone(),
  91.                         transform: Transform {
  92.                             translation: Vec3::new(x + x_offset, y + 15., 1.),
  93.                             scale: Vec3::new(SPRITE_SCALE, SPRITE_SCALE, 0.),
  94.                             ..Default::default()
  95.                         },
  96.                         ..Default::default()
  97.                     })
  98.                     .insert(Laser)
  99.                     .insert(FromPlayer)
  100.                     .insert(SpriteSize::from(PLAYER_LASER_SIZE))
  101.                     .insert(Movable { auto_despawn: true })
  102.                     .insert(Velocity::new(0., 1.));
  103.             };
  104.             spawn_laser(x_offset);
  105.             spawn_laser(-x_offset);
  106.         }
  107.     }
  108. }
  109. /// 键盘事件系统
  110. fn player_keyboard_event_system(
  111.     kb: Res<Input<KeyCode>>,
  112.     mut next_state: ResMut<NextState<GameState>>,
  113.     mut query: Query<&mut Velocity, With<Player>>,
  114. ) {
  115.     if let Ok(mut velocity) = query.get_single_mut() {
  116.         // pressed 按下按键
  117.         if kb.pressed(KeyCode::Left) {
  118.             velocity.x = -1.
  119.         } else if kb.pressed(KeyCode::Right) {
  120.             velocity.x = 1.
  121.         } else if kb.just_pressed(KeyCode::P) {
  122.             next_state.set(GameState::Paused);
  123.         } else {
  124.             velocity.x = 0.
  125.         }
  126.     };
  127. }
  128. /// 玩家移动系统
  129. fn player_movable_system(
  130.     win_size: Res<WinSize>,
  131.     mut query: Query<(&Velocity, &mut Transform), With<Player>>,
  132. ) {
  133.     let max_w = win_size.w / 2.;
  134.     for (velocity, mut transform) in query.iter_mut() {
  135.         let distance = velocity.x * BASE_SPEED * TIME_STEP;
  136.         let new_x = transform.translation.x + distance;
  137.         if -max_w <= new_x && new_x <= max_w {
  138.             // 移动位置
  139.             transform.translation.x += distance;
  140.         }
  141.     }
  142. }
复制代码
state.rs
  1. use bevy::{
  2.     prelude::{AudioSource, Handle, Image, Resource, States},
  3.     sprite::TextureAtlas,
  4.     text::Font,
  5. };
  6. /// 游戏窗口大小资源
  7. #[derive(Resource)]
  8. pub struct WinSize {
  9.     pub w: f32,
  10.     pub h: f32,
  11. }
  12. /// 游戏图像资源
  13. #[derive(Resource)]
  14. pub struct GameTextures {
  15.     pub background: Handle<Image>,
  16.     pub player: Handle<Image>,
  17.     pub player_laser: Handle<Image>,
  18.     pub enemy: Handle<Image>,
  19.     pub enemy_laser: Handle<Image>,
  20.     pub explosion: Handle<TextureAtlas>,
  21.     pub font: Handle<Font>,
  22. }
  23. /// 敌人最大数量
  24. #[derive(Resource)]
  25. pub struct MaxEnemy(pub u32);
  26. /// 玩家状态
  27. #[derive(Resource)]
  28. pub struct PlayerState {
  29.     pub on: bool,
  30.     pub last_shot: f64,
  31. }
  32. impl Default for PlayerState {
  33.     fn default() -> Self {
  34.         Self {
  35.             on: false,
  36.             last_shot: -1.,
  37.         }
  38.     }
  39. }
  40. impl PlayerState {
  41.     /// 被命中
  42.     pub fn shot(&mut self, time: f64) {
  43.         self.on = false;
  44.         self.last_shot = time;
  45.     }
  46.     /// 重生
  47.     pub fn spawned(&mut self) {
  48.         self.on = true;
  49.         self.last_shot = -1.;
  50.     }
  51. }
  52. #[derive(Resource)]
  53. pub struct GameAudio {
  54.     pub enemy_explosion: Handle<AudioSource>,
  55.     pub player_explosion: Handle<AudioSource>,
  56.     pub player_laser: Handle<AudioSource>,
  57. }
  58. /// 游戏状态   
  59. #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
  60. pub enum GameState {
  61.     /// 欢迎
  62.     #[default]
  63.     Welcome,
  64.     /// 游戏中
  65.     InGame,
  66.     /// 暂停
  67.     Paused,
  68. }
  69. /// 游戏数据
  70. #[derive(Resource)]
  71. pub struct GameData {
  72.     score: u32,
  73. }
  74. impl GameData {
  75.     pub fn new() -> Self {
  76.         Self { score: 0 }
  77.     }
  78.     /// 获取当前得分
  79.     pub fn get_score(&self) -> u32 {
  80.         self.score
  81.     }
  82.     /// 增加得分
  83.     pub fn add_score(&mut self) {
  84.         self.score += 1;
  85.     }
  86.     /// 增加得分
  87.     pub fn reset_score(&mut self) {
  88.         self.score = 0;
  89.     }
  90. }
复制代码
about me

目前失业,在家学习 rust 。
我的 bilibili,我的 GitHub
Rust官网
Rust 中文社区

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

慢吞云雾缓吐愁

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表