smooth_bevy_cameras/controllers/
fps.rs1use crate::{LookAngles, LookTransform, LookTransformBundle, Smoother};
2
3use bevy::{
4 app::prelude::*,
5 ecs::prelude::*,
6 input::{mouse::MouseMotion, prelude::*},
7 math::prelude::*,
8 prelude::ReflectDefault,
9 reflect::Reflect,
10 time::Time,
11 transform::components::Transform,
12};
13
14#[derive(Default)]
15pub struct FpsCameraPlugin {
16 pub override_input_system: bool,
17}
18
19impl FpsCameraPlugin {
20 pub fn new(override_input_system: bool) -> Self {
21 Self {
22 override_input_system,
23 }
24 }
25}
26
27impl Plugin for FpsCameraPlugin {
28 fn build(&self, app: &mut App) {
29 let app = app
30 .add_systems(PreUpdate, on_controller_enabled_changed)
31 .add_systems(Update, control_system)
32 .add_event::<ControlEvent>();
33
34 if !self.override_input_system {
35 app.add_systems(Update, default_input_map);
36 }
37 }
38}
39
40#[derive(Bundle)]
41pub struct FpsCameraBundle {
42 controller: FpsCameraController,
43 look_transform: LookTransformBundle,
44 transform: Transform,
45}
46
47impl FpsCameraBundle {
48 pub fn new(controller: FpsCameraController, eye: Vec3, target: Vec3, up: Vec3) -> Self {
49 let transform = Transform::from_translation(eye).looking_at(target, up);
51
52 Self {
53 controller,
54 look_transform: LookTransformBundle {
55 transform: LookTransform::new(eye, target, up),
56 smoother: Smoother::new(controller.smoothing_weight),
57 },
58 transform,
59 }
60 }
61}
62
63#[derive(Clone, Component, Copy, Debug, Reflect)]
65#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
66#[reflect(Component, Default, Debug)]
67pub struct FpsCameraController {
68 pub enabled: bool,
69 pub mouse_rotate_sensitivity: Vec2,
70 pub translate_sensitivity: f32,
71 pub smoothing_weight: f32,
72}
73
74impl Default for FpsCameraController {
75 fn default() -> Self {
76 Self {
77 enabled: true,
78 mouse_rotate_sensitivity: Vec2::splat(0.2),
79 translate_sensitivity: 2.0,
80 smoothing_weight: 0.9,
81 }
82 }
83}
84
85#[derive(Event)]
86pub enum ControlEvent {
87 Rotate(Vec2),
88 TranslateEye(Vec3),
89}
90
91define_on_controller_enabled_changed!(FpsCameraController);
92
93pub fn default_input_map(
94 mut events: EventWriter<ControlEvent>,
95 keyboard: Res<ButtonInput<KeyCode>>,
96 mut mouse_motion_events: EventReader<MouseMotion>,
97 controllers: Query<&FpsCameraController>,
98) {
99 let controller = if let Some(controller) = controllers.iter().find(|c| c.enabled) {
101 controller
102 } else {
103 return;
104 };
105 let FpsCameraController {
106 translate_sensitivity,
107 mouse_rotate_sensitivity,
108 ..
109 } = *controller;
110
111 let mut cursor_delta = Vec2::ZERO;
112 for event in mouse_motion_events.read() {
113 cursor_delta += event.delta;
114 }
115
116 events.write(ControlEvent::Rotate(
117 mouse_rotate_sensitivity * cursor_delta,
118 ));
119
120 for (key, dir) in [
121 (KeyCode::KeyW, Vec3::Z),
122 (KeyCode::KeyA, Vec3::X),
123 (KeyCode::KeyS, -Vec3::Z),
124 (KeyCode::KeyD, -Vec3::X),
125 (KeyCode::ShiftLeft, -Vec3::Y),
126 (KeyCode::Space, Vec3::Y),
127 ]
128 .iter()
129 .cloned()
130 {
131 if keyboard.pressed(key) {
132 events.write(ControlEvent::TranslateEye(translate_sensitivity * dir));
133 }
134 }
135}
136
137pub fn control_system(
138 mut events: EventReader<ControlEvent>,
139 mut cameras: Query<(&FpsCameraController, &mut LookTransform)>,
140 time: Res<Time>,
141) {
142 let mut transform = if let Some((_, transform)) = cameras.iter_mut().find(|c| c.0.enabled) {
144 transform
145 } else {
146 return;
147 };
148
149 let look_vector = transform.look_direction().unwrap();
150 let mut look_angles = LookAngles::from_vector(look_vector);
151
152 let yaw_rot = Quat::from_axis_angle(Vec3::Y, look_angles.get_yaw());
153 let rot_x = yaw_rot * Vec3::X;
154 let rot_y = yaw_rot * Vec3::Y;
155 let rot_z = yaw_rot * Vec3::Z;
156
157 let dt = time.delta_secs();
158 for event in events.read() {
159 match event {
160 ControlEvent::Rotate(delta) => {
161 look_angles.add_yaw(dt * -delta.x);
163 look_angles.add_pitch(dt * -delta.y);
164 }
165 ControlEvent::TranslateEye(delta) => {
166 transform.eye += dt * delta.x * rot_x + dt * delta.y * rot_y + dt * delta.z * rot_z;
168 }
169 }
170 }
171
172 look_angles.assert_not_looking_up();
173
174 transform.target = transform.eye + transform.radius() * look_angles.unit_vector();
175}