Rust 实现的飞机游戏
简介
一个使用 bevy 引擎制作的飞机游戏。
原视频教程地址,github 地址。
因为 bevy 已经升级到 0.10.1 了,所以重新做一遍。顺带手出个教程。
下面是做的部分变动:
- 将激光以及玩家的移动模块进行了拆分。
- 新增了背景图片。
- 新增了游戏状态管理 Welcome/InGame/Paused。
- 新增了声音播放模块。
- 新增了游戏记分板。
通过左右方向键进行控制,使用空格发射激光。
按 P 暂停游戏,按 S 恢复游戏。
更新后的GitHub地址
代码结构
- ·
- ├── assets/
- │ ├──audios/
- │ ├──images/
- ├── src/
- │ ├──enemy/
- │ │ ├── formation.rs
- │ │ └── mod.rs
- │ ├── components.rs
- │ ├── constants.rs
- │ ├── main.rs
- │ ├── player.rs
- │ ├── resource.rs
- │ └── state.rs
- ├── Cargo.lock
- └── 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
- use bevy::prelude::{Component, Resource};
- use rand::{thread_rng, Rng};
- use crate::{WinSize, BASE_SPEED, FORMATION_MEMBER_MAX};
- /// 敌人阵型
- #[derive(Component, Clone)]
- pub struct Formation {
- /// 启始位置
- pub start: (f32, f32),
- /// 半径
- pub radius: (f32, f32),
- /// 原点
- pub pivot: (f32, f32),
- /// 速度
- pub speed: f32,
- /// 角度
- pub angle: f32,
- }
- /// 阵型资源
- #[derive(Resource, Default)]
- pub struct FormationMaker {
- /// 当前阵型
- current_template: Option<Formation>,
- /// 当前数量
- current_members: u32,
- }
- impl FormationMaker {
- pub fn make(&mut self, win_size: &WinSize) -> Formation {
- match (
- &self.current_template,
- self.current_members >= FORMATION_MEMBER_MAX,
- ) {
- // 当前阵型还有空位 直接加入
- (Some(template), false) => {
- self.current_members += 1;
- template.clone()
- }
- // 当前阵型没有空位,或还没有阵型,需要创建新的阵型
- _ => {
- let mut rng = thread_rng();
- // 生成 起点坐标
- let w_spawn = win_size.w / 2. + 100.;
- let h_spawn = win_size.h / 2. + 100.;
- let x = if rng.gen_bool(0.5) { w_spawn } else { -w_spawn };
- let y = rng.gen_range(-h_spawn..h_spawn);
- let start = (x, y);
- // 生成原点坐标
- let w_spawn = win_size.w / 4.;
- let h_spawn = win_size.h / 3. + 50.;
- let pivot = (
- rng.gen_range(-w_spawn..w_spawn),
- rng.gen_range(0. ..h_spawn),
- );
- // 生成半径
- let radius = (rng.gen_range(80. ..150.), 100.);
- // 计算初始角度
- let angle = (y - pivot.1).atan2(x - pivot.0);
- // 速度
- let speed = BASE_SPEED;
- let formation = Formation {
- start,
- pivot,
- radius,
- angle,
- speed,
- };
- self.current_template = Some(formation.clone());
- self.current_members = 1;
- formation
- }
- }
- }
- }
复制代码 enemy/mod.rs
- use std::{f32::consts::PI, time::Duration};
- use crate::{
- components::{Enemy, FromEnemy, Laser, Movable, SpriteSize, Velocity},
- resource::GameState,
- GameTextures, MaxEnemy, WinSize, ENEMY_LASER_SIZE, ENEMY_SIZE, MAX_ENEMY, SPRITE_SCALE,
- TIME_STEP,
- };
- use bevy::{prelude::*, time::common_conditions::on_timer};
- use rand::{thread_rng, Rng};
- use self::formation::{Formation, FormationMaker};
- mod formation;
- #[derive(Component)]
- pub struct EnemyPlugin;
- impl Plugin for EnemyPlugin {
- fn build(&self, app: &mut App) {
- // 间隔执行
- app.insert_resource(FormationMaker::default())
- .add_system(
- enemy_spawn_system
- .run_if(on_timer(Duration::from_secs_f32(0.5)))
- .in_set(OnUpdate(GameState::InGame)),
- )
- .add_system(
- enemy_fire_system
- .run_if(enemy_fire_criteria)
- .in_set(OnUpdate(GameState::InGame)),
- )
- .add_system(enemy_movement_system.in_set(OnUpdate(GameState::InGame)));
- }
- }
- /// 敌人生成系统
- fn enemy_spawn_system(
- mut commands: Commands,
- mut max_enemy: ResMut<MaxEnemy>,
- mut formation_maker: ResMut<FormationMaker>,
- game_textures: Res<GameTextures>,
- win_size: Res<WinSize>,
- ) {
- // 如果当前的敌人数量大于等于最大敌人数量,则不再产生新的敌人
- if max_enemy.0 >= MAX_ENEMY {
- return;
- }
- // 随机生成
- // let mut rng = thread_rng();
- // let w_span = win_size.w / 2. - 100.;
- // let h_span = win_size.h / 2. - 100.;
- // let x = rng.gen_range(-w_span..w_span);
- // let y = rng.gen_range(-h_span..h_span);
- // 使用 阵型
- let formation = formation_maker.make(&win_size);
- let (x, y) = formation.start;
- commands
- .spawn(SpriteBundle {
- texture: game_textures.enemy.clone(),
- transform: Transform {
- // 坐标
- translation: Vec3::new(x, y, 10.),
- // 缩放
- scale: Vec3::new(SPRITE_SCALE, SPRITE_SCALE, 1.),
- // 旋转
- rotation: Quat::IDENTITY,
- },
- ..Default::default()
- })
- .insert(Enemy)
- .insert(formation)
- .insert(SpriteSize::from(ENEMY_SIZE));
- max_enemy.0 += 1;
- }
- /// 敌人射击系统
- fn enemy_fire_system(
- mut commands: Commands,
- game_textures: Res<GameTextures>,
- query: Query<&Transform, With<Enemy>>,
- ) {
- for &enemy_tf in query.iter() {
- let (x, y) = (enemy_tf.translation.x, enemy_tf.translation.y);
- commands
- .spawn(SpriteBundle {
- texture: game_textures.enemy_laser.clone(),
- transform: Transform {
- translation: Vec3::new(x, y, 1.),
- scale: Vec3::new(SPRITE_SCALE, SPRITE_SCALE, 1.),
- rotation: Quat::from_rotation_x(PI),
- },
- ..Default::default()
- })
- .insert(Laser)
- .insert(SpriteSize::from(ENEMY_LASER_SIZE))
- .insert(FromEnemy)
- .insert(Movable { auto_despawn: true })
- .insert(Velocity::new(0., -1.));
- }
- }
- /// 是否发射攻击
- fn enemy_fire_criteria() -> bool {
- if thread_rng().gen_bool(1. / 60.) {
- true
- } else {
- false
- }
- }
- /// 敌人移动系统
- ///
- /// 两点间的距离公式 $|AB|=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}$
- fn enemy_movement_system(mut query: Query<(&mut Transform, &mut Formation), With<Enemy>>) {
- // 当前时间
- // let now = time.elapsed_seconds();
- for (mut transform, mut formation) in query.iter_mut() {
- // 当前坐标
- let (x_org, y_org) = (transform.translation.x, transform.translation.y);
- // let (x_org, y_org) = formation.start;
- // 单位时间内最大移动距离
- // let max_distance = BASE_SPEED * TIME_STEP;
- let max_distance = formation.speed * TIME_STEP;
- // 方向 1 顺时针 -1 逆时针
- // let dir = -1.;
- let dir = if formation.start.0 < 0. { 1. } else { -1. };
- // 中心点
- // let (x_pivot, y_pivot) = (0., 0.);
- let (x_pivot, y_pivot) = formation.pivot;
- // 半径
- // let (x_radius, y_radius) = (200., 130.);
- let (x_radius, y_radius) = formation.radius;
- // 基于当前时间计算的角度
- // let angel = dir * BASE_SPEED * TIME_STEP * now % 360. / PI;
- let angel = formation.angle
- + dir * formation.speed * TIME_STEP / (x_radius.min(y_radius) * PI / 2.);
- // 计算目标点位
- let x_dst = x_radius * angel.cos() + x_pivot;
- let y_dst = y_radius * angel.sin() + y_pivot;
- // 计算距离
- // 两点间的距离公式 根号下 a.x - b.x
- let dx = x_org - x_dst;
- let dy = y_org - y_dst;
- let distance = (dx * dx + dy * dy).sqrt();
- let distance_radio = if distance != 0. {
- max_distance / distance
- } else {
- 0.
- };
- // 计算 x y 的最终坐标
- let x = x_org - dx * distance_radio;
- let x = if dx > 0. { x.max(x_dst) } else { x.min(x_dst) };
- let y = y_org - dy * distance_radio;
- let y = if dy > 0. { y.max(y_dst) } else { y.min(y_dst) };
- // 图片资源在椭圆上 或接近椭圆时开始加入旋转
- if distance < max_distance * formation.speed / 20. {
- formation.angle = angel;
- }
- let translation = &mut transform.translation;
- (translation.x, translation.y) = (x, y);
- }
- }
复制代码 components.rs
- use bevy::{
- prelude::{Component, Vec2, Vec3},
- time::{Timer, TimerMode},
- };
- // 通用控制组件
- #[derive(Component)]
- pub struct Velocity {
- pub x: f32,
- pub y: f32,
- }
- impl Velocity {
- pub fn new(x: f32, y: f32) -> Self {
- Self { x, y }
- }
- }
- /// 移动能力组件
- #[derive(Component)]
- pub struct Movable {
- /// 自动销毁
- pub auto_despawn: bool,
- }
- /// 玩家组件
- #[derive(Component)]
- pub struct Player;
- /// 玩家信息组件
- #[derive(Component)]
- pub struct FromPlayer;
- /// 敌人组件
- #[derive(Component)]
- pub struct Enemy;
- /// 敌人信息组件
- #[derive(Component)]
- pub struct FromEnemy;
- /// 激光组件
- #[derive(Component)]
- pub struct Laser;
- /// 图片大小组件
- #[derive(Component)]
- pub struct SpriteSize(pub Vec2);
- /// 实现 (f32,f32) 转 SpritSize
- impl From<(f32, f32)> for SpriteSize {
- fn from(value: (f32, f32)) -> Self {
- Self(Vec2::new(value.0, value.1))
- }
- }
- /// 爆炸组件
- #[derive(Component)]
- pub struct Explosion;
- /// 产生爆炸组件
- #[derive(Component)]
- pub struct ExplosionToSpawn(pub Vec3);
- /// 爆炸事件组件
- #[derive(Component)]
- pub struct ExplosionTimer(pub Timer);
- impl Default for ExplosionTimer {
- fn default() -> Self {
- Self(Timer::from_seconds(0.05, TimerMode::Once))
- }
- }
- /// 分数显示组件
- #[derive(Component)]
- pub struct DisplayScore;
- /// 欢迎组件
- #[derive(Component)]
- pub struct WelcomeText;
- /// 暂停组件
- #[derive(Component)]
- pub struct PausedText;
复制代码 constants.rs
- /// 游戏背景图片路径
- pub const BACKGROUND_SPRITE: &str = "images/planet05.png";
- /// 玩家图片路径
- pub const PLAYER_SPRITE: &str = "images/player_a_01.png";
- /// 玩家大小
- pub const PLAYER_SIZE: (f32, f32) = (144., 75.);
- /// 玩家攻击图片路径
- pub const PLAYER_LASER_SPRITE: &str = "images/laser_a_01.png";
- /// 玩家攻击图片大小
- pub const PLAYER_LASER_SIZE: (f32, f32) = (9., 54.);
- /// 敌人图片路径
- pub const ENEMY_SPRITE: &str = "images/enemy_a_01.png";
- /// 敌人大小
- pub const ENEMY_SIZE: (f32, f32) = (144., 75.);
- /// 敌人攻击图片路径
- pub const ENEMY_LASER_SPRITE: &str = "images/laser_b_01.png";
- /// 敌人攻击图片大小
- pub const ENEMY_LASER_SIZE: (f32, f32) = (17., 55.);
- /// 爆炸图片路径
- pub const EXPLOSION_SHEET: &str = "images/explosion_a_sheet.png";
- /// 爆炸图片大小
- pub const EXPLOSION_SIZE: (f32, f32) = (64., 64.);
- /// 爆炸画面帧数
- pub const EXPLOSION_ANIMATION_LEN: usize = 16;
- /// 图片缩放比例
- pub const SPRITE_SCALE: f32 = 0.5;
- /// 步长 (帧数)
- pub const TIME_STEP: f32 = 1. / 60.;
- /// 基础速度
- pub const BASE_SPEED: f32 = 500.;
- /// 敌人最大数量
- pub const MAX_ENEMY: u32 = 2;
- /// 玩家自动重生时间
- pub const PLAYER_RESPAWN_DELAY: f64 = 2.;
- /// 阵型内敌人最大数量
- pub const FORMATION_MEMBER_MAX: u32 = 2;
- /// 敌人被摧毁声音
- pub const ENEMY_EXPLOSION_AUDIO: &str = "audios/enemy_explosion.ogg";
- /// 玩家被摧毁的声音
- pub const PLAYER_EXPLOSION_AUDIO: &str = "audios/player_explosion.ogg";
- /// 玩家发射激光的声音
- pub const PLAYER_LASER_AUDIO: &str = "audios/player_laser.ogg";
- /// 字体路径
- pub const KENNEY_BLOCK_FONT: &str = "fonts/kenney_blocks.ttf";
复制代码 main.rs
- use bevy::{math::Vec3Swizzles, prelude::*, sprite::collide_aabb::collide, utils::HashSet};
- use components::*;
- use constants::*;
- use enemy::EnemyPlugin;
- use player::PlayerPlugin;
- use resource::{GameAudio, GameData, GameState, GameTextures, MaxEnemy, PlayerState, WinSize};
- use state::StatePlugin;
- mod components;
- mod constants;
- mod enemy;
- mod player;
- mod resource;
- mod state;
- fn main() {
- // add_startup_system 启动生命周期时只运行一次 ,
- // add_system 每帧都会被调用方法
- App::new()
- .add_state::<GameState>()
- .insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04)))
- .add_plugins(DefaultPlugins.set(WindowPlugin {
- primary_window: Some(Window {
- title: "Invaders".to_owned(),
- resolution: (598., 676.).into(),
- position: WindowPosition::At(IVec2::new(2282, 0)),
- ..Window::default()
- }),
- ..WindowPlugin::default()
- }))
- .add_plugin(PlayerPlugin)
- .add_plugin(EnemyPlugin)
- .add_plugin(StatePlugin)
- .add_startup_system(setup_system)
- // InGame 状态下执行的函数
- .add_systems(
- (
- laser_movable_system,
- player_laser_hit_enemy_system,
- explosion_to_spawn_system,
- explosion_animation_system,
- enemy_laser_hit_player_system,
- score_display_update_system,
- )
- .in_set(OnUpdate(GameState::InGame)),
- )
- // 启动 esc 键退出程序
- .add_system(bevy::window::close_on_esc)
- .run();
- }
- /// 资源加载
- fn setup_system(
- mut commands: Commands,
- asset_server: Res<AssetServer>,
- mut texture_atlases: ResMut<Assets<TextureAtlas>>,
- mut windows: Query<&mut Window>,
- ) {
- // 创建2d镜头
- commands.spawn(Camera2dBundle::default());
- // 获取当前窗口
- let window = windows.single_mut();
- let win_w = window.width();
- let win_h = window.height();
- // 添加 WinSize 资源
- let win_size = WinSize { w: win_w, h: win_h };
- commands.insert_resource(win_size);
- // 创建爆炸动画
- let texture_handle = asset_server.load(EXPLOSION_SHEET);
- let texture_atlas =
- TextureAtlas::from_grid(texture_handle, Vec2::from(EXPLOSION_SIZE), 4, 4, None, None);
- let explosion = texture_atlases.add(texture_atlas);
- // 添加 GameTextures
- let game_texture = GameTextures {
- background: asset_server.load(BACKGROUND_SPRITE),
- player: asset_server.load(PLAYER_SPRITE),
- player_laser: asset_server.load(PLAYER_LASER_SPRITE),
- enemy: asset_server.load(ENEMY_SPRITE),
- enemy_laser: asset_server.load(ENEMY_LASER_SPRITE),
- font: asset_server.load(KENNEY_BLOCK_FONT),
- explosion,
- };
- // 声音资源引入
- let game_audio = GameAudio {
- player_laser: asset_server.load(PLAYER_LASER_AUDIO),
- player_explosion: asset_server.load(PLAYER_EXPLOSION_AUDIO),
- enemy_explosion: asset_server.load(ENEMY_EXPLOSION_AUDIO),
- };
- // 背景图片
- commands.spawn(SpriteBundle {
- texture: game_texture.background.clone(),
- sprite: Sprite {
- custom_size: Some(Vec2 { x: win_w, y: win_h }),
- ..Default::default()
- },
- transform: Transform::from_scale(Vec3::new(1.5, 1.5, 0.0)),
- ..Default::default()
- });
- // 字体引入
- let font = game_texture.font.clone();
- let text_style = TextStyle {
- font: font.clone(),
- font_size: 32.,
- color: Color::ANTIQUE_WHITE,
- };
- let text_alignment = TextAlignment::Center;
- // 分数展示控件
- commands.spawn((
- Text2dBundle {
- text: Text::from_section("SCORE:0", text_style).with_alignment(text_alignment),
- transform: Transform {
- translation: Vec3 {
- x: 0.,
- y: win_h / 2. - 20.,
- z: 11.,
- },
- ..Default::default()
- },
- ..Default::default()
- },
- DisplayScore,
- ));
- let game_data = GameData::new();
- commands.insert_resource(game_data);
- commands.insert_resource(game_audio);
- commands.insert_resource(game_texture);
- commands.insert_resource(MaxEnemy(0));
- }
- /// 激光移动系统
- fn laser_movable_system(
- mut commands: Commands,
- win_size: Res<WinSize>,
- mut query: Query<(Entity, &Velocity, &mut Transform, &Movable), With<Laser>>,
- ) {
- for (entity, velocity, mut transform, movable) in query.iter_mut() {
- // 移动位置
- let translation = &mut transform.translation;
- translation.x += velocity.x * BASE_SPEED * TIME_STEP;
- translation.y += velocity.y * BASE_SPEED * TIME_STEP;
- // 自动销毁
- if movable.auto_despawn {
- const MARGIN: f32 = 200.;
- if translation.y > win_size.h / 2. + MARGIN
- || translation.y < -win_size.h / 2. - MARGIN
- || translation.x > win_size.w / 2. + MARGIN
- || translation.x < -win_size.w / 2. - MARGIN
- {
- commands.entity(entity).despawn();
- }
- }
- }
- }
- /// 敌人激光攻击玩家判定系统
- fn enemy_laser_hit_player_system(
- mut commands: Commands,
- mut player_state: ResMut<PlayerState>,
- time: Res<Time>,
- audio_source: Res<GameAudio>,
- audio: Res<Audio>,
- mut game_data: ResMut<GameData>,
- mut next_state: ResMut<NextState<GameState>>,
- laser_query: Query<(Entity, &Transform, &SpriteSize), (With<Laser>, With<FromEnemy>)>,
- player_query: Query<(Entity, &Transform, &SpriteSize), With<Player>>,
- ) {
- if let Ok((player_entity, player_tf, player_size)) = player_query.get_single() {
- let player_scale = Vec2::from(player_tf.scale.xy());
- for (laser, laser_tf, laser_size) in laser_query.into_iter() {
- let laser_scale = Vec2::from(laser_tf.scale.xy());
- let collision = collide(
- player_tf.translation,
- player_size.0 * player_scale,
- laser_tf.translation,
- laser_size.0 * laser_scale,
- );
- if let Some(_) = collision {
- // 播放音乐
- audio.play(audio_source.player_explosion.clone());
- // 重置分数
- game_data.reset_score();
- next_state.set(GameState::Welcome);
- // 销毁角色
- commands.entity(player_entity).despawn();
- // 记录被命中的时刻
- player_state.shot(time.elapsed_seconds_f64());
- // 销毁激光
- commands.entity(laser).despawn();
- // 产生爆炸动画
- commands.spawn(ExplosionToSpawn(player_tf.translation.clone()));
- break;
- }
- }
- }
- }
- /// 玩家攻击敌人判定系统
- fn player_laser_hit_enemy_system(
- mut commands: Commands,
- audio_source: Res<GameAudio>,
- audio: Res<Audio>,
- mut max_enemy: ResMut<MaxEnemy>,
- mut game_data: ResMut<GameData>,
- laser_query: Query<(Entity, &Transform, &SpriteSize), (With<Laser>, With<FromPlayer>)>,
- enemy_query: Query<(Entity, &Transform, &SpriteSize), With<Enemy>>,
- ) {
- // 重复删除检测
- let mut despawn_entities: HashSet<Entity> = HashSet::new();
- // 玩家激光
- for (laser_entity, laser_tf, laser_size) in laser_query.iter() {
- if despawn_entities.contains(&laser_entity) {
- continue;
- }
- // 玩家激光的坐标
- let laser_scale = Vec2::from(laser_tf.scale.xy());
- // 敌人
- for (enemy_entity, enemy_tf, enemy_size) in enemy_query.iter() {
- if despawn_entities.contains(&enemy_entity) || despawn_entities.contains(&laser_entity)
- {
- continue;
- }
- // 敌人坐标
- let enemy_scale = Vec2::from(enemy_tf.scale.xy());
- // collide 定义两个元素的碰撞,a 点坐标,a 的大小,b 点坐标,b 的大小,如果未发生碰撞返回 None
- let collision = collide(
- laser_tf.translation,
- laser_size.0 * laser_scale,
- enemy_tf.translation,
- enemy_size.0 * enemy_scale,
- );
- // 碰撞检测
- if let Some(_) = collision {
- // 敌人数量 -1
- if max_enemy.0 != 0 {
- max_enemy.0 -= 1;
- }
- game_data.add_score();
- audio.play(audio_source.enemy_explosion.clone());
- // 销毁敌人
- commands.entity(enemy_entity).despawn();
- despawn_entities.insert(enemy_entity);
- // 销毁激光
- commands.entity(laser_entity).despawn();
- despawn_entities.insert(laser_entity);
- // 播放爆炸动画
- commands.spawn(ExplosionToSpawn(enemy_tf.translation.clone()));
- }
- }
- }
- }
- /// 爆炸画面生成系统
- fn explosion_to_spawn_system(
- mut commands: Commands,
- game_textures: Res<GameTextures>,
- query: Query<(Entity, &ExplosionToSpawn)>,
- ) {
- for (explosion_spawn_entity, explosion_to_spawn) in query.iter() {
- commands
- .spawn(SpriteSheetBundle {
- texture_atlas: game_textures.explosion.clone(),
- transform: Transform {
- translation: explosion_to_spawn.0,
- ..Default::default()
- },
- ..Default::default()
- })
- .insert(Explosion)
- .insert(ExplosionTimer::default());
- commands.entity(explosion_spawn_entity).despawn();
- }
- }
- /// 爆炸动画系统
- fn explosion_animation_system(
- mut commands: Commands,
- time: Res<Time>,
- mut query: Query<(Entity, &mut ExplosionTimer, &mut TextureAtlasSprite), With<Explosion>>,
- ) {
- for (entity, mut timer, mut texture_atlas_sprite) in query.iter_mut() {
- timer.0.tick(time.delta());
- if timer.0.finished() {
- texture_atlas_sprite.index += 1;
- if texture_atlas_sprite.index >= EXPLOSION_ANIMATION_LEN {
- commands.entity(entity).despawn();
- }
- }
- }
- }
- /// 分数更新系统
- fn score_display_update_system(
- game_data: Res<GameData>,
- mut query: Query<&mut Text, With<DisplayScore>>,
- ) {
- for mut text in &mut query {
- let new_str: String = format!("SCORE:{}", game_data.get_score());
- text.sections[0].value = new_str;
- }
- }
复制代码 player.rs
- use bevy::{prelude::*, time::common_conditions::on_timer};
- use std::time::Duration;
- use crate::{
- components::{FromPlayer, Laser, Movable, Player, SpriteSize, Velocity},
- resource::GameAudio,
- resource::PlayerState,
- resource::WinSize,
- resource::{GameState, GameTextures},
- BASE_SPEED, PLAYER_LASER_SIZE, PLAYER_RESPAWN_DELAY, PLAYER_SIZE, SPRITE_SCALE, TIME_STEP,
- };
- pub struct PlayerPlugin;
- impl Plugin for PlayerPlugin {
- fn build(&self, app: &mut App) {
- // add_startup_system 应用程序生命周期开始时运行一次
- // StartupSet::PostStartup 在 StartupSet::Startup 后运行一次
- // add_startup_system(player_spawn_system.in_base_set(StartupSet::PostStartup))
- // add_system 每帧都运行 , 可以在函数后通过 run_if 传入 bool 类型的条件进行限制
- app.insert_resource(PlayerState::default())
- .add_system(
- player_spawn_system
- .run_if(on_timer(Duration::from_secs_f32(0.5)))
- .in_set(OnUpdate(GameState::InGame)),
- )
- .add_systems(
- (
- player_keyboard_event_system,
- player_movable_system,
- player_fire_system,
- )
- .in_set(OnUpdate(GameState::InGame)),
- );
- }
- }
- /// 玩家角色生成系统
- fn player_spawn_system(
- mut commands: Commands,
- mut player_state: ResMut<PlayerState>,
- time: Res<Time>,
- game_textures: Res<GameTextures>,
- win_size: Res<WinSize>,
- ) {
- let now = time.elapsed_seconds_f64();
- let last_shot = player_state.last_shot;
- if !player_state.on && (player_state.last_shot == -1. || now - PLAYER_RESPAWN_DELAY > last_shot)
- {
- let bottom = -win_size.h / 2.;
- // 创建组件实体,并返回对应的 EntityCommand
- commands
- .spawn(SpriteBundle {
- texture: game_textures.player.clone(),
- transform: Transform {
- translation: Vec3::new(
- 0.,
- bottom + PLAYER_SIZE.1 / 2. * SPRITE_SCALE + 5.0,
- 10.,
- ),
- scale: Vec3::new(SPRITE_SCALE, SPRITE_SCALE, 1.0),
- ..default()
- },
- ..SpriteBundle::default()
- })
- .insert(Velocity::new(0., 0.))
- .insert(Movable {
- auto_despawn: false,
- })
- .insert(SpriteSize::from(PLAYER_SIZE))
- .insert(Player);
- player_state.spawned();
- }
- }
- /// 玩家攻击系统
- fn player_fire_system(
- mut commands: Commands,
- audio_source: Res<GameAudio>,
- audio: Res<Audio>,
- kb: Res<Input<KeyCode>>,
- game_textures: Res<GameTextures>,
- query: Query<&Transform, With<Player>>,
- ) {
- if let Ok(player_tf) = query.get_single() {
- // just_released 松开按键
- if kb.just_released(KeyCode::Space) {
- audio.play(audio_source.player_laser.clone());
- let (x, y) = (player_tf.translation.x, player_tf.translation.y);
- let x_offset = PLAYER_SIZE.0 / 2. * SPRITE_SCALE - 5.;
- // 激光生成闭包 因为这里使用了 commands 生成新的包 所以这里的闭包需要定义为 mut 类型
- let mut spawn_laser = |x_offset: f32| {
- commands
- .spawn(SpriteBundle {
- texture: game_textures.player_laser.clone(),
- transform: Transform {
- translation: Vec3::new(x + x_offset, y + 15., 1.),
- scale: Vec3::new(SPRITE_SCALE, SPRITE_SCALE, 0.),
- ..Default::default()
- },
- ..Default::default()
- })
- .insert(Laser)
- .insert(FromPlayer)
- .insert(SpriteSize::from(PLAYER_LASER_SIZE))
- .insert(Movable { auto_despawn: true })
- .insert(Velocity::new(0., 1.));
- };
- spawn_laser(x_offset);
- spawn_laser(-x_offset);
- }
- }
- }
- /// 键盘事件系统
- fn player_keyboard_event_system(
- kb: Res<Input<KeyCode>>,
- mut next_state: ResMut<NextState<GameState>>,
- mut query: Query<&mut Velocity, With<Player>>,
- ) {
- if let Ok(mut velocity) = query.get_single_mut() {
- // pressed 按下按键
- if kb.pressed(KeyCode::Left) {
- velocity.x = -1.
- } else if kb.pressed(KeyCode::Right) {
- velocity.x = 1.
- } else if kb.just_pressed(KeyCode::P) {
- next_state.set(GameState::Paused);
- } else {
- velocity.x = 0.
- }
- };
- }
- /// 玩家移动系统
- fn player_movable_system(
- win_size: Res<WinSize>,
- mut query: Query<(&Velocity, &mut Transform), With<Player>>,
- ) {
- let max_w = win_size.w / 2.;
- for (velocity, mut transform) in query.iter_mut() {
- let distance = velocity.x * BASE_SPEED * TIME_STEP;
- let new_x = transform.translation.x + distance;
- if -max_w <= new_x && new_x <= max_w {
- // 移动位置
- transform.translation.x += distance;
- }
- }
- }
复制代码 state.rs
- use bevy::{
- prelude::{AudioSource, Handle, Image, Resource, States},
- sprite::TextureAtlas,
- text::Font,
- };
- /// 游戏窗口大小资源
- #[derive(Resource)]
- pub struct WinSize {
- pub w: f32,
- pub h: f32,
- }
- /// 游戏图像资源
- #[derive(Resource)]
- pub struct GameTextures {
- pub background: Handle<Image>,
- pub player: Handle<Image>,
- pub player_laser: Handle<Image>,
- pub enemy: Handle<Image>,
- pub enemy_laser: Handle<Image>,
- pub explosion: Handle<TextureAtlas>,
- pub font: Handle<Font>,
- }
- /// 敌人最大数量
- #[derive(Resource)]
- pub struct MaxEnemy(pub u32);
- /// 玩家状态
- #[derive(Resource)]
- pub struct PlayerState {
- pub on: bool,
- pub last_shot: f64,
- }
- impl Default for PlayerState {
- fn default() -> Self {
- Self {
- on: false,
- last_shot: -1.,
- }
- }
- }
- impl PlayerState {
- /// 被命中
- pub fn shot(&mut self, time: f64) {
- self.on = false;
- self.last_shot = time;
- }
- /// 重生
- pub fn spawned(&mut self) {
- self.on = true;
- self.last_shot = -1.;
- }
- }
- #[derive(Resource)]
- pub struct GameAudio {
- pub enemy_explosion: Handle<AudioSource>,
- pub player_explosion: Handle<AudioSource>,
- pub player_laser: Handle<AudioSource>,
- }
- /// 游戏状态
- #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
- pub enum GameState {
- /// 欢迎
- #[default]
- Welcome,
- /// 游戏中
- InGame,
- /// 暂停
- Paused,
- }
- /// 游戏数据
- #[derive(Resource)]
- pub struct GameData {
- score: u32,
- }
- impl GameData {
- pub fn new() -> Self {
- Self { score: 0 }
- }
- /// 获取当前得分
- pub fn get_score(&self) -> u32 {
- self.score
- }
- /// 增加得分
- pub fn add_score(&mut self) {
- self.score += 1;
- }
- /// 增加得分
- pub fn reset_score(&mut self) {
- self.score = 0;
- }
- }
复制代码 about me
目前失业,在家学习 rust 。
我的 bilibili,我的 GitHub。
Rust官网
Rust 中文社区
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |