Latest commit 01aedc8
用四元数旋转 2D 实体。
use bevy::math::Vec3Swizzles; use bevy::prelude::*; use bevy::time::FixedTimestep; const TIME_STEP: f32 = 1.0 / 60.0; const BOUNDS: Vec2 = Vec2::new(1200.0, 640.0); fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); app.add_startup_system(初始设置); app.add_system_set( SystemSet::new() .with_run_criteria(FixedTimestep::step(TIME_STEP as f64)) .with_system(玩家运动) .with_system(snap_to_player_system) .with_system(rotate_to_player_system) ); app.run(); } #[derive(Component)] struct Player { movement_speed: f32, rotation_speed: f32, } // 使敌人飞船即时面向玩家所在的位置 #[derive(Component)] struct SnapToPlayer; // 使敌人飞船根据旋转速度慢慢地面向玩家所在的位置 #[derive(Component)] struct RotateToPlayer { rotation_speed: f32, } /// 添加实体、创建摄像机 /// X 轴指向右,Y 轴指向上,Z 轴指向观察者,原点位于屏幕中心点 fn 初始设置(mut commands: Commands, asset_server: Res<AssetServer>) { // 摄像机 commands.spawn(Camera2dBundle::default()); let ship_handle = asset_server.load("ship.png"); let enemy_a_handle = asset_server.load("enemy.png"); let enemy_b_handle = asset_server.load("enemy.png"); let horizontal_margin = BOUNDS.x / 4.0; let vertical_margin = BOUNDS.y / 4.0; // 玩家控制的飞船 commands.spawn(( SpriteBundle { texture: ship_handle, // todo: 不用 into() 吗? ..default() }, Player { movement_speed: 500.0, rotation_speed: f32::to_radians(360.0), }, )); // 在左边和下边生成两个敌人 commands.spawn(( SpriteBundle { texture: enemy_a_handle.clone(), transform: Transform::from_xyz(0.0 - horizontal_margin, 0.0, 0.0), ..default() }, SnapToPlayer, )); commands.spawn(( SpriteBundle { texture: enemy_a_handle, transform: Transform::from_xyz(0.0, 0.0 - vertical_margin, 0.0), ..default() }, SnapToPlayer, )); // 在右边和上边生成两个敌人 commands.spawn(( SpriteBundle { texture: enemy_b_handle.clone(), transform: Transform::from_xyz(0.0 + horizontal_margin, 0.0, 0.0), ..default() }, RotateToPlayer { rotation_speed: f32::to_radians(45.0), }, )); commands.spawn(( SpriteBundle { texture: enemy_b_handle, transform: Transform::from_xyz(0.0, 0.0 + vertical_margin, 0.0), ..default() }, RotateToPlayer { rotation_speed: f32::to_radians(90.0), }, )); } fn 玩家运动(mut query: Query<(&Player, &mut Transform)>, keyboard_input: Res<Input<KeyCode>>) { let (player, mut transform) = query.single_mut(); let mut rotation_factor = 0.0; let mut movement_factor = 0.0; if keyboard_input.pressed(KeyCode::Left) { rotation_factor += 1.0; } if keyboard_input.pressed(KeyCode::Right) { rotation_factor += -1.0; } if keyboard_input.pressed(KeyCode::Up) { movement_factor += 1.0; } // 使飞船绕 Z 轴旋转(也就是在 2D 平面上旋转) // 按下左键获得正值,逆时针旋转;按下右键获得负值,顺时针旋转 transform.rotate_z(rotation_factor * player.rotation_speed * TIME_STEP); // 用飞船当前的旋转(四元数)乘 Y 轴正方向,得到一个新的向量,作为前进方向 let movement_direction = transform.rotation * Vec3::Y; // 移动距离 let movement_distance = movement_factor * player.movement_speed * TIME_STEP; // 下一帧的位置 = 方向(Vec3) * 距离(f32) let translation_delta = movement_direction * movement_distance; transform.translation += translation_delta; // 超出屏幕时,位置设置在屏幕内 // let extents = Vec3::from((BOUNDS / 2.0, 0.0)); let extents = Vec3::new(BOUNDS.x / 2.0, BOUNDS.y / 2.0, 0.0); transform.translation = transform.translation.min(extents).max(-extents); } // 敌人立即转向玩家所在位置 fn snap_to_player_system( mut query: Query<&mut Transform, (With<SnapToPlayer>, Without<Player>)>, player_query: Query<&Transform, With<Player>>, ) { let player_transform = player_query.single(); // 用 Vec2 来进行相关计算 let player_translation = player_transform.translation.xy(); for mut enemy_transform in &mut query { // 获取从敌人面向玩家的方向 let to_player = (player_translation - enemy_transform.translation.xy()).normalize(); // 获取从“敌人的的朝向”直接旋转到“面向玩家的方向”的四元数 // 素材的飞船面向正上方,因此用 Vec::Y let rotate_to_player = Quat::from_rotation_arc(Vec3::Y, to_player.extend(0.0)); enemy_transform.rotation = rotate_to_player; } } /// 敌人以固定的旋转速度转向玩家所在位置,根据点积的值来选择旋转方式 /// /// 值为 1.0,两向量方向相同,夹角为 0 度; /// 值为 0.0,两向量垂直,夹角为 90 度; /// 值为 -1.0,两向量反向平行,夹角为 180 度; /// 略... fn rotate_to_player_system( mut query: Query<(&RotateToPlayer, &mut Transform), Without<Player>>, player_query: Query<&Transform, With<Player>>, ) { let player_transform = player_query.single(); // 用 Vec2 来进行相关计算 let player_translation = player_transform.translation.xy(); for (config, mut enemy_transform) in &mut query { // 获取敌人的朝向、从敌人到玩家的方向,计算点积, // 如果点积约等于 1.0,说明敌人已经面向玩家,不再继续旋转 let enemy_forward = (enemy_transform.rotation * Vec3::Y).xy(); let to_player = (player_translation - enemy_transform.translation.xy()).normalize(); let forward_dot_player = enemy_forward.dot(to_player); if (forward_dot_player - 1.0).abs() < f32::EPSILON { continue; } // 获取敌人默认方向、从敌人到玩家的方向,计算点积, // 如果点积是负数,需要逆时针旋转,如果是正数,需要顺时针旋转 let enemy_right = (enemy_transform.rotation * Vec3::X).xy(); let right_dot_player = enemy_right.dot(to_player); // 确定旋转方向,要取反方向 let rotation_sign = -f32::copysign(1.0, right_dot_player); // 限制旋转 let max_angle = forward_dot_player.clamp(-1.0, 1.0).acos(); let rotation_angle = rotation_sign * (config.rotation_speed * TIME_STEP).min(max_angle); // 旋转 enemy_transform.rotate_z(rotation_angle); } }