smooth_bevy_cameras/controllers/
unreal.rs

1use crate::{LookAngles, LookTransform, LookTransformBundle, Smoother};
2
3use bevy::{
4    app::prelude::*,
5    ecs::{bundle::Bundle, prelude::*},
6    input::{
7        mouse::{MouseMotion, MouseWheel},
8        prelude::*,
9    },
10    math::prelude::*,
11    prelude::ReflectDefault,
12    reflect::Reflect,
13    time::Time,
14    transform::components::Transform,
15};
16
17#[derive(Default)]
18pub struct UnrealCameraPlugin {
19    pub override_input_system: bool,
20}
21
22impl UnrealCameraPlugin {
23    pub fn new(override_input_system: bool) -> Self {
24        Self {
25            override_input_system,
26        }
27    }
28}
29
30impl Plugin for UnrealCameraPlugin {
31    fn build(&self, app: &mut App) {
32        let app = app
33            .add_systems(PreUpdate, on_controller_enabled_changed)
34            .add_systems(Update, control_system)
35            .add_event::<ControlEvent>();
36        if !self.override_input_system {
37            app.add_systems(Update, default_input_map);
38        }
39    }
40}
41
42#[derive(Bundle)]
43pub struct UnrealCameraBundle {
44    controller: UnrealCameraController,
45    look_transform: LookTransformBundle,
46    transform: Transform,
47}
48
49impl UnrealCameraBundle {
50    pub fn new(controller: UnrealCameraController, eye: Vec3, target: Vec3, up: Vec3) -> Self {
51        // Make sure the transform is consistent with the controller to start.
52        let transform = Transform::from_translation(eye).looking_at(target, up);
53
54        Self {
55            controller,
56            look_transform: LookTransformBundle {
57                transform: LookTransform::new(eye, target, up),
58                smoother: Smoother::new(controller.smoothing_weight),
59            },
60            transform,
61        }
62    }
63}
64
65/// A camera controlled with the mouse in the same way as Unreal Engine's viewport controller.
66#[derive(Clone, Component, Copy, Debug, Reflect)]
67#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
68#[reflect(Component, Default, Debug)]
69pub struct UnrealCameraController {
70    /// Whether to process input or ignore it
71    pub enabled: bool,
72
73    /// How many radians per frame for each rotation axis (yaw, pitch) when rotating with the mouse
74    pub rotate_sensitivity: Vec2,
75
76    /// How many units per frame for each direction when translating using Middle or L+R panning
77    pub mouse_translate_sensitivity: Vec2,
78
79    /// How many units per frame when translating using scroll wheel
80    pub wheel_translate_sensitivity: f32,
81
82    /// How many units per frame when translating using W/S/Q/E
83    /// Updated with scroll wheel while dragging with any mouse button
84    pub keyboard_mvmt_sensitivity: f32,
85
86    /// Wheel sensitivity for modulating keyboard movement speed
87    pub keyboard_mvmt_wheel_sensitivity: f32,
88
89    /// The greater, the slower to follow input
90    pub smoothing_weight: f32,
91}
92
93impl Default for UnrealCameraController {
94    fn default() -> Self {
95        Self {
96            enabled: true,
97            rotate_sensitivity: Vec2::splat(0.2),
98            mouse_translate_sensitivity: Vec2::splat(2.0),
99            wheel_translate_sensitivity: 50.0,
100            keyboard_mvmt_sensitivity: 10.0,
101            keyboard_mvmt_wheel_sensitivity: 5.0,
102            smoothing_weight: 0.7,
103        }
104    }
105}
106
107#[derive(Event)]
108pub enum ControlEvent {
109    Locomotion(Vec2),
110    Rotate(Vec2),
111    TranslateEye(Vec2),
112}
113
114define_on_controller_enabled_changed!(UnrealCameraController);
115
116pub fn default_input_map(
117    mut events: EventWriter<ControlEvent>,
118    mut mouse_wheel_reader: EventReader<MouseWheel>,
119    mut mouse_motion_events: EventReader<MouseMotion>,
120    keyboard: Res<ButtonInput<KeyCode>>,
121    mouse_buttons: Res<ButtonInput<MouseButton>>,
122    mut controllers: Query<&mut UnrealCameraController>,
123) {
124    // Can only control one camera at a time.
125    let mut controller = if let Some(controller) = controllers.iter_mut().find(|c| c.enabled) {
126        controller
127    } else {
128        return;
129    };
130    let UnrealCameraController {
131        rotate_sensitivity: mouse_rotate_sensitivity,
132        mouse_translate_sensitivity,
133        wheel_translate_sensitivity,
134        mut keyboard_mvmt_sensitivity,
135        keyboard_mvmt_wheel_sensitivity,
136        ..
137    } = *controller;
138
139    let left_pressed = mouse_buttons.pressed(MouseButton::Left);
140    let right_pressed = mouse_buttons.pressed(MouseButton::Right);
141    let middle_pressed = mouse_buttons.pressed(MouseButton::Middle);
142
143    let mut cursor_delta = Vec2::ZERO;
144    for event in mouse_motion_events.read() {
145        cursor_delta += event.delta;
146    }
147
148    let mut wheel_delta = 0.0;
149    for event in mouse_wheel_reader.read() {
150        wheel_delta += event.x + event.y;
151    }
152
153    let mut panning_dir = Vec2::ZERO;
154    let mut translation_dir = Vec2::ZERO; // y is forward/backward axis, x is rotation around Z
155
156    for key in keyboard.get_pressed() {
157        match key {
158            KeyCode::KeyE => {
159                panning_dir.y += 1.0;
160            }
161
162            KeyCode::KeyQ => {
163                panning_dir.y -= 1.0;
164            }
165
166            KeyCode::KeyA => {
167                panning_dir.x -= 1.0;
168            }
169
170            KeyCode::KeyD => {
171                panning_dir.x += 1.0;
172            }
173
174            KeyCode::KeyS => {
175                translation_dir.y -= 1.0;
176            }
177
178            KeyCode::KeyW => {
179                translation_dir.y += 1.0;
180            }
181
182            _ => {}
183        }
184    }
185
186    let mut panning = Vec2::ZERO;
187    let mut locomotion = Vec2::ZERO;
188
189    // If any of the mouse button are pressed; read additional signals from the keyboard for panning
190    // and locomotion along camera view axis
191    if left_pressed || middle_pressed || right_pressed {
192        panning += keyboard_mvmt_sensitivity * panning_dir;
193
194        if translation_dir.y != 0.0 {
195            locomotion.y += keyboard_mvmt_sensitivity * translation_dir.y;
196        }
197
198        keyboard_mvmt_sensitivity += keyboard_mvmt_wheel_sensitivity * wheel_delta;
199        controller.keyboard_mvmt_sensitivity = keyboard_mvmt_sensitivity.max(0.01);
200    }
201    // Otherwise, if any scrolling is happening, do locomotion along camera view axis
202    else if wheel_delta != 0.0 {
203        locomotion.y += wheel_translate_sensitivity * wheel_delta;
204    }
205
206    // You can also pan using the mouse only; add those signals to existing panning
207    if middle_pressed || (left_pressed && right_pressed) {
208        panning += mouse_translate_sensitivity * cursor_delta;
209    }
210
211    // When left only is pressed, mouse movements add up to the "unreal locomotion" scheme
212    if left_pressed && !middle_pressed && !right_pressed {
213        locomotion.x = mouse_rotate_sensitivity.x * cursor_delta.x;
214        locomotion.y -= mouse_translate_sensitivity.y * cursor_delta.y;
215    }
216
217    if !left_pressed && !middle_pressed && right_pressed {
218        events.write(ControlEvent::Rotate(
219            mouse_rotate_sensitivity * cursor_delta,
220        ));
221    }
222
223    if panning.length_squared() > 0.0 {
224        events.write(ControlEvent::TranslateEye(panning));
225    }
226
227    if locomotion.length_squared() > 0.0 {
228        events.write(ControlEvent::Locomotion(locomotion));
229    }
230}
231
232pub fn control_system(
233    time: Res<Time>,
234    mut events: EventReader<ControlEvent>,
235    mut cameras: Query<(&UnrealCameraController, &mut LookTransform)>,
236) {
237    // Can only control one camera at a time.
238    let mut transform = if let Some((_, transform)) = cameras.iter_mut().find(|c| c.0.enabled) {
239        transform
240    } else {
241        return;
242    };
243
244    let look_vector = match transform.look_direction() {
245        Some(safe_look_vector) => safe_look_vector,
246        None => return,
247    };
248    let mut look_angles = LookAngles::from_vector(look_vector);
249
250    let dt = time.delta_secs();
251    for event in events.read() {
252        match event {
253            ControlEvent::Locomotion(delta) => {
254                // Translates forward/backward and rotates about the Y axis.
255                look_angles.add_yaw(dt * -delta.x);
256                transform.eye += dt * delta.y * look_vector;
257            }
258            ControlEvent::Rotate(delta) => {
259                // Rotates with pitch and yaw.
260                look_angles.add_yaw(dt * -delta.x);
261                look_angles.add_pitch(dt * -delta.y);
262            }
263            ControlEvent::TranslateEye(delta) => {
264                let yaw_rot = Quat::from_axis_angle(Vec3::Y, look_angles.get_yaw());
265                let rot_x = yaw_rot * Vec3::X;
266
267                // Translates up/down and left/right (X).
268                let up = transform.up;
269                transform.eye -= dt * delta.x * rot_x - dt * delta.y * up;
270            }
271        }
272    }
273
274    look_angles.assert_not_looking_up();
275
276    transform.target = transform.eye + transform.radius() * look_angles.unit_vector();
277}