use bevy::{math::ops, prelude::*};
const BOUNDS: Vec2 = Vec2::new(1200.0, 640.0);
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(Time::<Fixed>::from_hz(60.0))
.add_systems(Startup, setup)
.add_systems(
FixedUpdate,
(
player_movement_system,
snap_to_player_system,
rotate_to_player_system,
),
)
.run();
}
#[derive(Component)]
struct Player {
movement_speed: f32,
rotation_speed: f32,
}
#[derive(Component)]
struct SnapToPlayer;
#[derive(Component)]
struct RotateToPlayer {
rotation_speed: f32,
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let ship_handle = asset_server.load("textures/simplespace/ship_C.png");
let enemy_a_handle = asset_server.load("textures/simplespace/enemy_A.png");
let enemy_b_handle = asset_server.load("textures/simplespace/enemy_B.png");
commands.spawn(Camera2d);
let horizontal_margin = BOUNDS.x / 4.0;
let vertical_margin = BOUNDS.y / 4.0;
commands.spawn((
Sprite::from_image(ship_handle),
Player {
movement_speed: 500.0, rotation_speed: f32::to_radians(360.0), },
));
commands.spawn((
Sprite::from_image(enemy_a_handle.clone()),
Transform::from_xyz(0.0 - horizontal_margin, 0.0, 0.0),
SnapToPlayer,
));
commands.spawn((
Sprite::from_image(enemy_a_handle),
Transform::from_xyz(0.0, 0.0 - vertical_margin, 0.0),
SnapToPlayer,
));
commands.spawn((
Sprite::from_image(enemy_b_handle.clone()),
Transform::from_xyz(0.0 + horizontal_margin, 0.0, 0.0),
RotateToPlayer {
rotation_speed: f32::to_radians(45.0), },
));
commands.spawn((
Sprite::from_image(enemy_b_handle),
Transform::from_xyz(0.0, 0.0 + vertical_margin, 0.0),
RotateToPlayer {
rotation_speed: f32::to_radians(90.0), },
));
}
fn player_movement_system(
time: Res<Time>,
keyboard_input: Res<ButtonInput<KeyCode>>,
query: Single<(&Player, &mut Transform)>,
) {
let (ship, mut transform) = query.into_inner();
let mut rotation_factor = 0.0;
let mut movement_factor = 0.0;
if keyboard_input.pressed(KeyCode::ArrowLeft) {
rotation_factor += 1.0;
}
if keyboard_input.pressed(KeyCode::ArrowRight) {
rotation_factor -= 1.0;
}
if keyboard_input.pressed(KeyCode::ArrowUp) {
movement_factor += 1.0;
}
transform.rotate_z(rotation_factor * ship.rotation_speed * time.delta_secs());
let movement_direction = transform.rotation * Vec3::Y;
let movement_distance = movement_factor * ship.movement_speed * time.delta_secs();
let translation_delta = movement_direction * movement_distance;
transform.translation += translation_delta;
let extents = Vec3::from((BOUNDS / 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_transform: Single<&Transform, With<Player>>,
) {
let player_translation = player_transform.translation.xy();
for mut enemy_transform in &mut query {
let to_player = (player_translation - enemy_transform.translation.xy()).normalize();
let rotate_to_player = Quat::from_rotation_arc(Vec3::Y, to_player.extend(0.));
enemy_transform.rotation = rotate_to_player;
}
}
fn rotate_to_player_system(
time: Res<Time>,
mut query: Query<(&RotateToPlayer, &mut Transform), Without<Player>>,
player_transform: Single<&Transform, With<Player>>,
) {
let player_translation = player_transform.translation.xy();
for (config, mut enemy_transform) in &mut query {
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 = ops::acos(forward_dot_player.clamp(-1.0, 1.0));
let rotation_angle =
rotation_sign * (config.rotation_speed * time.delta_secs()).min(max_angle);
enemy_transform.rotate_z(rotation_angle);
}
}