Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/helpers/camera_controller.rs
6592 views
1
//! A freecam-style camera controller plugin.
2
//! To use in your own application:
3
//! - Copy the code for the [`CameraControllerPlugin`] and add the plugin to your App.
4
//! - Attach the [`CameraController`] component to an entity with a [`Camera3d`].
5
//!
6
//! Unlike other examples, which demonstrate an application, this demonstrates a plugin library.
7
8
use bevy::{
9
input::mouse::{AccumulatedMouseMotion, AccumulatedMouseScroll, MouseScrollUnit},
10
prelude::*,
11
window::{CursorGrabMode, CursorOptions},
12
};
13
use std::{f32::consts::*, fmt};
14
15
/// A freecam-style camera controller plugin.
16
pub struct CameraControllerPlugin;
17
18
impl Plugin for CameraControllerPlugin {
19
fn build(&self, app: &mut App) {
20
app.add_systems(Update, run_camera_controller);
21
}
22
}
23
24
/// Based on Valorant's default sensitivity, not entirely sure why it is exactly 1.0 / 180.0,
25
/// but I'm guessing it is a misunderstanding between degrees/radians and then sticking with
26
/// it because it felt nice.
27
pub const RADIANS_PER_DOT: f32 = 1.0 / 180.0;
28
29
/// Camera controller [`Component`].
30
#[derive(Component)]
31
pub struct CameraController {
32
/// Enables this [`CameraController`] when `true`.
33
pub enabled: bool,
34
/// Indicates if this controller has been initialized by the [`CameraControllerPlugin`].
35
pub initialized: bool,
36
/// Multiplier for pitch and yaw rotation speed.
37
pub sensitivity: f32,
38
/// [`KeyCode`] for forward translation.
39
pub key_forward: KeyCode,
40
/// [`KeyCode`] for backward translation.
41
pub key_back: KeyCode,
42
/// [`KeyCode`] for left translation.
43
pub key_left: KeyCode,
44
/// [`KeyCode`] for right translation.
45
pub key_right: KeyCode,
46
/// [`KeyCode`] for up translation.
47
pub key_up: KeyCode,
48
/// [`KeyCode`] for down translation.
49
pub key_down: KeyCode,
50
/// [`KeyCode`] to use [`run_speed`](CameraController::run_speed) instead of
51
/// [`walk_speed`](CameraController::walk_speed) for translation.
52
pub key_run: KeyCode,
53
/// [`MouseButton`] for grabbing the mouse focus.
54
pub mouse_key_cursor_grab: MouseButton,
55
/// [`KeyCode`] for grabbing the keyboard focus.
56
pub keyboard_key_toggle_cursor_grab: KeyCode,
57
/// Multiplier for unmodified translation speed.
58
pub walk_speed: f32,
59
/// Multiplier for running translation speed.
60
pub run_speed: f32,
61
/// Multiplier for how the mouse scroll wheel modifies [`walk_speed`](CameraController::walk_speed)
62
/// and [`run_speed`](CameraController::run_speed).
63
pub scroll_factor: f32,
64
/// Friction factor used to exponentially decay [`velocity`](CameraController::velocity) over time.
65
pub friction: f32,
66
/// This [`CameraController`]'s pitch rotation.
67
pub pitch: f32,
68
/// This [`CameraController`]'s yaw rotation.
69
pub yaw: f32,
70
/// This [`CameraController`]'s translation velocity.
71
pub velocity: Vec3,
72
}
73
74
impl Default for CameraController {
75
fn default() -> Self {
76
Self {
77
enabled: true,
78
initialized: false,
79
sensitivity: 1.0,
80
key_forward: KeyCode::KeyW,
81
key_back: KeyCode::KeyS,
82
key_left: KeyCode::KeyA,
83
key_right: KeyCode::KeyD,
84
key_up: KeyCode::KeyE,
85
key_down: KeyCode::KeyQ,
86
key_run: KeyCode::ShiftLeft,
87
mouse_key_cursor_grab: MouseButton::Left,
88
keyboard_key_toggle_cursor_grab: KeyCode::KeyM,
89
walk_speed: 5.0,
90
run_speed: 15.0,
91
scroll_factor: 0.1,
92
friction: 0.5,
93
pitch: 0.0,
94
yaw: 0.0,
95
velocity: Vec3::ZERO,
96
}
97
}
98
}
99
100
impl fmt::Display for CameraController {
101
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102
write!(
103
f,
104
"
105
Freecam Controls:
106
Mouse\t- Move camera orientation
107
Scroll\t- Adjust movement speed
108
{:?}\t- Hold to grab cursor
109
{:?}\t- Toggle cursor grab
110
{:?} & {:?}\t- Fly forward & backwards
111
{:?} & {:?}\t- Fly sideways left & right
112
{:?} & {:?}\t- Fly up & down
113
{:?}\t- Fly faster while held",
114
self.mouse_key_cursor_grab,
115
self.keyboard_key_toggle_cursor_grab,
116
self.key_forward,
117
self.key_back,
118
self.key_left,
119
self.key_right,
120
self.key_up,
121
self.key_down,
122
self.key_run,
123
)
124
}
125
}
126
127
fn run_camera_controller(
128
time: Res<Time<Real>>,
129
mut windows: Query<(&Window, &mut CursorOptions)>,
130
accumulated_mouse_motion: Res<AccumulatedMouseMotion>,
131
accumulated_mouse_scroll: Res<AccumulatedMouseScroll>,
132
mouse_button_input: Res<ButtonInput<MouseButton>>,
133
key_input: Res<ButtonInput<KeyCode>>,
134
mut toggle_cursor_grab: Local<bool>,
135
mut mouse_cursor_grab: Local<bool>,
136
mut query: Query<(&mut Transform, &mut CameraController), With<Camera>>,
137
) {
138
let dt = time.delta_secs();
139
140
let Ok((mut transform, mut controller)) = query.single_mut() else {
141
return;
142
};
143
144
if !controller.initialized {
145
let (yaw, pitch, _roll) = transform.rotation.to_euler(EulerRot::YXZ);
146
controller.yaw = yaw;
147
controller.pitch = pitch;
148
controller.initialized = true;
149
info!("{}", *controller);
150
}
151
if !controller.enabled {
152
return;
153
}
154
155
let mut scroll = 0.0;
156
157
let amount = match accumulated_mouse_scroll.unit {
158
MouseScrollUnit::Line => accumulated_mouse_scroll.delta.y,
159
MouseScrollUnit::Pixel => accumulated_mouse_scroll.delta.y / 16.0,
160
};
161
scroll += amount;
162
controller.walk_speed += scroll * controller.scroll_factor * controller.walk_speed;
163
controller.run_speed = controller.walk_speed * 3.0;
164
165
// Handle key input
166
let mut axis_input = Vec3::ZERO;
167
if key_input.pressed(controller.key_forward) {
168
axis_input.z += 1.0;
169
}
170
if key_input.pressed(controller.key_back) {
171
axis_input.z -= 1.0;
172
}
173
if key_input.pressed(controller.key_right) {
174
axis_input.x += 1.0;
175
}
176
if key_input.pressed(controller.key_left) {
177
axis_input.x -= 1.0;
178
}
179
if key_input.pressed(controller.key_up) {
180
axis_input.y += 1.0;
181
}
182
if key_input.pressed(controller.key_down) {
183
axis_input.y -= 1.0;
184
}
185
186
let mut cursor_grab_change = false;
187
if key_input.just_pressed(controller.keyboard_key_toggle_cursor_grab) {
188
*toggle_cursor_grab = !*toggle_cursor_grab;
189
cursor_grab_change = true;
190
}
191
if mouse_button_input.just_pressed(controller.mouse_key_cursor_grab) {
192
*mouse_cursor_grab = true;
193
cursor_grab_change = true;
194
}
195
if mouse_button_input.just_released(controller.mouse_key_cursor_grab) {
196
*mouse_cursor_grab = false;
197
cursor_grab_change = true;
198
}
199
let cursor_grab = *mouse_cursor_grab || *toggle_cursor_grab;
200
201
// Update velocity
202
if axis_input != Vec3::ZERO {
203
let max_speed = if key_input.pressed(controller.key_run) {
204
controller.run_speed
205
} else {
206
controller.walk_speed
207
};
208
controller.velocity = axis_input.normalize() * max_speed;
209
} else {
210
let friction = controller.friction.clamp(0.0, 1.0);
211
controller.velocity *= 1.0 - friction;
212
if controller.velocity.length_squared() < 1e-6 {
213
controller.velocity = Vec3::ZERO;
214
}
215
}
216
217
// Apply movement update
218
if controller.velocity != Vec3::ZERO {
219
let forward = *transform.forward();
220
let right = *transform.right();
221
transform.translation += controller.velocity.x * dt * right
222
+ controller.velocity.y * dt * Vec3::Y
223
+ controller.velocity.z * dt * forward;
224
}
225
226
// Handle cursor grab
227
if cursor_grab_change {
228
if cursor_grab {
229
for (window, mut cursor_options) in &mut windows {
230
if !window.focused {
231
continue;
232
}
233
234
cursor_options.grab_mode = CursorGrabMode::Locked;
235
cursor_options.visible = false;
236
}
237
} else {
238
for (_, mut cursor_options) in &mut windows {
239
cursor_options.grab_mode = CursorGrabMode::None;
240
cursor_options.visible = true;
241
}
242
}
243
}
244
245
// Handle mouse input
246
if accumulated_mouse_motion.delta != Vec2::ZERO && cursor_grab {
247
// Apply look update
248
controller.pitch = (controller.pitch
249
- accumulated_mouse_motion.delta.y * RADIANS_PER_DOT * controller.sensitivity)
250
.clamp(-PI / 2., PI / 2.);
251
controller.yaw -=
252
accumulated_mouse_motion.delta.x * RADIANS_PER_DOT * controller.sensitivity;
253
transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, controller.yaw, controller.pitch);
254
}
255
}
256
257