1use bevy::input::mouse::{MouseMotion, MouseWheel};
8use bevy::input::touch::TouchPhase;
9use bevy::prelude::*;
10
11pub struct CameraPlugin;
13
14impl Plugin for CameraPlugin {
15 fn build(&self, app: &mut App) {
16 app.init_resource::<TouchState>()
17 .add_systems(Startup, spawn_camera)
18 .add_systems(
19 Update,
20 (
21 camera_look,
22 camera_move,
23 camera_zoom,
24 camera_reset,
25 touch_camera_control,
26 ),
27 );
28 }
29}
30
31#[derive(Resource, Default)]
33struct TouchState {
34 touches: Vec<(u64, Vec2)>,
36 prev_touches: Vec<(u64, Vec2)>,
38 prev_pinch_distance: Option<f32>,
40 prev_two_finger_center: Option<Vec2>,
42}
43
44const 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)]
51pub struct FirstPersonCamera {
52 pub speed: f32,
54 pub sensitivity: f32,
56 pub pitch: f32,
58 pub yaw: f32,
60}
61
62impl Default for FirstPersonCamera {
63 fn default() -> Self {
64 Self {
65 speed: 3.0,
66 sensitivity: 0.003,
67 pitch: 0.0,
68 yaw: 0.8, }
70 }
71}
72
73fn direction_to_yaw_pitch(dir: Vec3) -> (f32, f32) {
75 let yaw = dir.z.atan2(dir.x) - std::f32::consts::FRAC_PI_2;
76 let pitch = (-dir.y).asin();
77 (yaw, pitch)
78}
79
80fn default_yaw_pitch() -> (f32, f32) {
82 let dir = (DEFAULT_LOOK_AT - DEFAULT_CAM_POS).normalize();
83 direction_to_yaw_pitch(dir)
84}
85
86fn spawn_camera(mut commands: Commands) {
87 let (yaw, pitch) = default_yaw_pitch();
88 commands.spawn((
89 Camera3d::default(),
90 Transform::from_translation(DEFAULT_CAM_POS).with_rotation(Quat::from_euler(
91 EulerRot::YXZ,
92 yaw,
93 pitch,
94 0.0,
95 )),
96 FirstPersonCamera {
97 yaw,
98 pitch,
99 ..default()
100 },
101 ));
102}
103
104fn camera_reset(
106 mut query: Query<(&mut Transform, &mut FirstPersonCamera)>,
107 keyboard: Res<ButtonInput<KeyCode>>,
108) {
109 if keyboard.just_pressed(KeyCode::KeyR) || keyboard.just_pressed(KeyCode::Home) {
110 let (yaw, pitch) = default_yaw_pitch();
111 for (mut transform, mut camera) in query.iter_mut() {
112 transform.translation = DEFAULT_CAM_POS;
113 camera.yaw = yaw;
114 camera.pitch = pitch;
115 transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, 0.0);
116 }
117 }
118}
119
120fn camera_look(
122 mut query: Query<(&mut Transform, &mut FirstPersonCamera)>,
123 mut mouse_motion: MessageReader<MouseMotion>,
124 mouse_button: Res<ButtonInput<MouseButton>>,
125) {
126 if !mouse_button.pressed(MouseButton::Right) {
128 mouse_motion.clear();
129 return;
130 }
131
132 let mut delta = Vec2::ZERO;
133 for event in mouse_motion.read() {
134 delta += event.delta;
135 }
136
137 if delta == Vec2::ZERO {
138 return;
139 }
140
141 for (mut transform, mut camera) in query.iter_mut() {
142 camera.yaw -= delta.x * camera.sensitivity;
143 camera.pitch -= delta.y * camera.sensitivity;
144 camera.pitch = camera.pitch.clamp(-1.5, 1.5);
145
146 transform.rotation = Quat::from_euler(EulerRot::YXZ, camera.yaw, camera.pitch, 0.0);
147 }
148}
149
150fn camera_move(
152 mut query: Query<(&mut Transform, &FirstPersonCamera)>,
153 keyboard: Res<ButtonInput<KeyCode>>,
154 time: Res<Time>,
155) {
156 for (mut transform, camera) in query.iter_mut() {
157 let mut direction = Vec3::ZERO;
158
159 let forward = transform.forward();
161 let forward_flat = Vec3::new(forward.x, 0.0, forward.z).normalize_or_zero();
162 let right = transform.right();
163 let right_flat = Vec3::new(right.x, 0.0, right.z).normalize_or_zero();
164
165 if keyboard.pressed(KeyCode::KeyW) || keyboard.pressed(KeyCode::ArrowUp) {
167 direction += forward_flat;
168 }
169 if keyboard.pressed(KeyCode::KeyS) || keyboard.pressed(KeyCode::ArrowDown) {
170 direction -= forward_flat;
171 }
172 if keyboard.pressed(KeyCode::KeyA) || keyboard.pressed(KeyCode::ArrowLeft) {
173 direction -= right_flat;
174 }
175 if keyboard.pressed(KeyCode::KeyD) || keyboard.pressed(KeyCode::ArrowRight) {
176 direction += right_flat;
177 }
178
179 if keyboard.pressed(KeyCode::KeyQ) {
181 direction += Vec3::Y;
182 }
183 if keyboard.pressed(KeyCode::KeyE) {
184 direction -= Vec3::Y;
185 }
186
187 if direction != Vec3::ZERO {
189 direction = direction.normalize();
190 transform.translation += direction * camera.speed * time.delta_secs();
191 }
192 }
193}
194
195fn camera_zoom(
197 mut query: Query<(&mut Transform, &FirstPersonCamera)>,
198 mut scroll_events: MessageReader<MouseWheel>,
199) {
200 let mut scroll_delta: f32 = 0.0;
201 for event in scroll_events.read() {
202 scroll_delta += event.y;
203 }
204
205 if scroll_delta.abs() > 0.0 {
206 for (mut transform, camera) in query.iter_mut() {
207 let forward = transform.forward();
208 let zoom_speed = camera.speed * 0.5; transform.translation += forward * scroll_delta * zoom_speed;
210 }
211 }
212}
213
214fn touch_camera_control(
221 mut query: Query<(&mut Transform, &mut FirstPersonCamera)>,
222 mut touch_events: MessageReader<TouchInput>,
223 mut touch_state: ResMut<TouchState>,
224) {
225 for event in touch_events.read() {
227 match event.phase {
228 TouchPhase::Started => {
229 touch_state.touches.push((event.id, event.position));
231 }
232 TouchPhase::Moved => {
233 if let Some(touch) = touch_state
235 .touches
236 .iter_mut()
237 .find(|(id, _)| *id == event.id)
238 {
239 touch.1 = event.position;
240 }
241 }
242 TouchPhase::Ended | TouchPhase::Canceled => {
243 touch_state.touches.retain(|(id, _)| *id != event.id);
245 touch_state.prev_pinch_distance = None;
247 touch_state.prev_two_finger_center = None;
248 }
249 }
250 }
251
252 let num_touches = touch_state.touches.len();
254
255 for (mut transform, mut camera) in query.iter_mut() {
256 if num_touches == 1 {
257 let current_pos = touch_state.touches[0].1;
259
260 if let Some((_, prev_pos)) = touch_state
262 .prev_touches
263 .iter()
264 .find(|(id, _)| *id == touch_state.touches[0].0)
265 {
266 let delta = current_pos - *prev_pos;
267
268 let touch_sensitivity = camera.sensitivity * 0.5;
270 camera.yaw -= delta.x * touch_sensitivity;
271 camera.pitch -= delta.y * touch_sensitivity;
272 camera.pitch = camera.pitch.clamp(-1.5, 1.5);
273
274 transform.rotation = Quat::from_euler(EulerRot::YXZ, camera.yaw, camera.pitch, 0.0);
275 }
276
277 touch_state.prev_pinch_distance = None;
279 touch_state.prev_two_finger_center = None;
280 } else if num_touches == 2 {
281 let pos1 = touch_state.touches[0].1;
283 let pos2 = touch_state.touches[1].1;
284
285 let current_distance = pos1.distance(pos2);
286 let current_center = (pos1 + pos2) / 2.0;
287
288 if let Some(prev_distance) = touch_state.prev_pinch_distance {
290 let zoom_delta = current_distance - prev_distance;
291 let zoom_speed = 0.01; let forward = transform.forward();
294 transform.translation += forward * zoom_delta * zoom_speed;
295 }
296
297 if let Some(prev_center) = touch_state.prev_two_finger_center {
299 let pan_delta = current_center - prev_center;
300 let pan_speed = 0.005 * camera.speed; let right = transform.right();
303 let up = Vec3::Y;
304
305 transform.translation -= right * pan_delta.x * pan_speed;
307 transform.translation += up * pan_delta.y * pan_speed;
308 }
309
310 touch_state.prev_pinch_distance = Some(current_distance);
311 touch_state.prev_two_finger_center = Some(current_center);
312 } else {
313 touch_state.prev_pinch_distance = None;
315 touch_state.prev_two_finger_center = None;
316 }
317 }
318
319 touch_state.prev_touches = touch_state.touches.clone();
321}