use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::input::touch::TouchPhase;
use bevy::prelude::*;
pub struct CameraPlugin;
impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<TouchState>()
.add_systems(Startup, spawn_camera)
.add_systems(
Update,
(
camera_look,
camera_move,
camera_zoom,
camera_reset,
touch_camera_control,
),
);
}
}
#[derive(Resource, Default)]
struct TouchState {
touches: Vec<(u64, Vec2)>,
prev_touches: Vec<(u64, Vec2)>,
prev_pinch_distance: Option<f32>,
prev_two_finger_center: Option<Vec2>,
}
const DEFAULT_CAM_POS: Vec3 = Vec3::new(1.0, 1.7, 5.0); const DEFAULT_LOOK_AT: Vec3 = Vec3::new(5.0, 4.0, 15.0);
#[derive(Component)]
pub struct FirstPersonCamera {
pub speed: f32,
pub sensitivity: f32,
pub pitch: f32,
pub yaw: f32,
}
impl Default for FirstPersonCamera {
fn default() -> Self {
Self {
speed: 3.0,
sensitivity: 0.003,
pitch: 0.0,
yaw: 0.8, }
}
}
fn direction_to_yaw_pitch(dir: Vec3) -> (f32, f32) {
let yaw = dir.z.atan2(dir.x) - std::f32::consts::FRAC_PI_2;
let pitch = (-dir.y).asin();
(yaw, pitch)
}
fn default_yaw_pitch() -> (f32, f32) {
let dir = (DEFAULT_LOOK_AT - DEFAULT_CAM_POS).normalize();
direction_to_yaw_pitch(dir)
}
fn spawn_camera(mut commands: Commands) {
let (yaw, pitch) = default_yaw_pitch();
commands.spawn((
Camera3d::default(),
Transform::from_translation(DEFAULT_CAM_POS).with_rotation(Quat::from_euler(
EulerRot::YXZ,
yaw,
pitch,
0.0,
)),
FirstPersonCamera {
yaw,
pitch,
..default()
},
));
}
fn camera_reset(
mut query: Query<(&mut Transform, &mut FirstPersonCamera)>,
keyboard: Res<ButtonInput<KeyCode>>,
) {
if keyboard.just_pressed(KeyCode::KeyR) || keyboard.just_pressed(KeyCode::Home) {
let (yaw, pitch) = default_yaw_pitch();
for (mut transform, mut camera) in query.iter_mut() {
transform.translation = DEFAULT_CAM_POS;
camera.yaw = yaw;
camera.pitch = pitch;
transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, 0.0);
}
}
}
fn camera_look(
mut query: Query<(&mut Transform, &mut FirstPersonCamera)>,
mut mouse_motion: MessageReader<MouseMotion>,
mouse_button: Res<ButtonInput<MouseButton>>,
) {
if !mouse_button.pressed(MouseButton::Right) {
mouse_motion.clear();
return;
}
let mut delta = Vec2::ZERO;
for event in mouse_motion.read() {
delta += event.delta;
}
if delta == Vec2::ZERO {
return;
}
for (mut transform, mut camera) in query.iter_mut() {
camera.yaw -= delta.x * camera.sensitivity;
camera.pitch -= delta.y * camera.sensitivity;
camera.pitch = camera.pitch.clamp(-1.5, 1.5);
transform.rotation = Quat::from_euler(EulerRot::YXZ, camera.yaw, camera.pitch, 0.0);
}
}
fn camera_move(
mut query: Query<(&mut Transform, &FirstPersonCamera)>,
keyboard: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
for (mut transform, camera) in query.iter_mut() {
let mut direction = Vec3::ZERO;
let forward = transform.forward();
let forward_flat = Vec3::new(forward.x, 0.0, forward.z).normalize_or_zero();
let right = transform.right();
let right_flat = Vec3::new(right.x, 0.0, right.z).normalize_or_zero();
if keyboard.pressed(KeyCode::KeyW) || keyboard.pressed(KeyCode::ArrowUp) {
direction += forward_flat;
}
if keyboard.pressed(KeyCode::KeyS) || keyboard.pressed(KeyCode::ArrowDown) {
direction -= forward_flat;
}
if keyboard.pressed(KeyCode::KeyA) || keyboard.pressed(KeyCode::ArrowLeft) {
direction -= right_flat;
}
if keyboard.pressed(KeyCode::KeyD) || keyboard.pressed(KeyCode::ArrowRight) {
direction += right_flat;
}
if keyboard.pressed(KeyCode::KeyQ) {
direction += Vec3::Y;
}
if keyboard.pressed(KeyCode::KeyE) {
direction -= Vec3::Y;
}
if direction != Vec3::ZERO {
direction = direction.normalize();
transform.translation += direction * camera.speed * time.delta_secs();
}
}
}
fn camera_zoom(
mut query: Query<(&mut Transform, &FirstPersonCamera)>,
mut scroll_events: MessageReader<MouseWheel>,
) {
let mut scroll_delta: f32 = 0.0;
for event in scroll_events.read() {
scroll_delta += event.y;
}
if scroll_delta.abs() > 0.0 {
for (mut transform, camera) in query.iter_mut() {
let forward = transform.forward();
let zoom_speed = camera.speed * 0.5; transform.translation += forward * scroll_delta * zoom_speed;
}
}
}
fn touch_camera_control(
mut query: Query<(&mut Transform, &mut FirstPersonCamera)>,
mut touch_events: MessageReader<TouchInput>,
mut touch_state: ResMut<TouchState>,
) {
for event in touch_events.read() {
match event.phase {
TouchPhase::Started => {
touch_state.touches.push((event.id, event.position));
}
TouchPhase::Moved => {
if let Some(touch) = touch_state
.touches
.iter_mut()
.find(|(id, _)| *id == event.id)
{
touch.1 = event.position;
}
}
TouchPhase::Ended | TouchPhase::Canceled => {
touch_state.touches.retain(|(id, _)| *id != event.id);
touch_state.prev_pinch_distance = None;
touch_state.prev_two_finger_center = None;
}
}
}
let num_touches = touch_state.touches.len();
for (mut transform, mut camera) in query.iter_mut() {
if num_touches == 1 {
let current_pos = touch_state.touches[0].1;
if let Some((_, prev_pos)) = touch_state
.prev_touches
.iter()
.find(|(id, _)| *id == touch_state.touches[0].0)
{
let delta = current_pos - *prev_pos;
let touch_sensitivity = camera.sensitivity * 0.5;
camera.yaw -= delta.x * touch_sensitivity;
camera.pitch -= delta.y * touch_sensitivity;
camera.pitch = camera.pitch.clamp(-1.5, 1.5);
transform.rotation = Quat::from_euler(EulerRot::YXZ, camera.yaw, camera.pitch, 0.0);
}
touch_state.prev_pinch_distance = None;
touch_state.prev_two_finger_center = None;
} else if num_touches == 2 {
let pos1 = touch_state.touches[0].1;
let pos2 = touch_state.touches[1].1;
let current_distance = pos1.distance(pos2);
let current_center = (pos1 + pos2) / 2.0;
if let Some(prev_distance) = touch_state.prev_pinch_distance {
let zoom_delta = current_distance - prev_distance;
let zoom_speed = 0.01;
let forward = transform.forward();
transform.translation += forward * zoom_delta * zoom_speed;
}
if let Some(prev_center) = touch_state.prev_two_finger_center {
let pan_delta = current_center - prev_center;
let pan_speed = 0.005 * camera.speed;
let right = transform.right();
let up = Vec3::Y;
transform.translation -= right * pan_delta.x * pan_speed;
transform.translation += up * pan_delta.y * pan_speed;
}
touch_state.prev_pinch_distance = Some(current_distance);
touch_state.prev_two_finger_center = Some(current_center);
} else {
touch_state.prev_pinch_distance = None;
touch_state.prev_two_finger_center = None;
}
}
touch_state.prev_touches = touch_state.touches.clone();
}