eulumdat_bevy/
camera.rs

1//! First-person camera controller
2
3use bevy::input::mouse::MouseMotion;
4use bevy::prelude::*;
5
6pub struct CameraPlugin;
7
8impl Plugin for CameraPlugin {
9    fn build(&self, app: &mut App) {
10        app.add_systems(Startup, spawn_camera)
11            .add_systems(Update, (camera_look, camera_move, camera_reset));
12    }
13}
14
15/// Default camera position and look target
16/// Position: standing on sidewalk looking at the lamp/scene center
17const DEFAULT_CAM_POS: Vec3 = Vec3::new(1.0, 1.7, 5.0); // Sidewalk, eye height ~1.7m
18const DEFAULT_LOOK_AT: Vec3 = Vec3::new(5.0, 4.0, 15.0); // Looking at lamp area
19
20#[derive(Component)]
21pub struct FirstPersonCamera {
22    pub speed: f32,
23    pub sensitivity: f32,
24    pub pitch: f32,
25    pub yaw: f32,
26}
27
28impl Default for FirstPersonCamera {
29    fn default() -> Self {
30        Self {
31            speed: 3.0,
32            sensitivity: 0.003,
33            pitch: 0.0,
34            yaw: 0.8, // ~45 degrees, looking into the room
35        }
36    }
37}
38
39/// Calculate yaw and pitch from a direction vector
40fn direction_to_yaw_pitch(dir: Vec3) -> (f32, f32) {
41    let yaw = dir.z.atan2(dir.x) - std::f32::consts::FRAC_PI_2;
42    let pitch = (-dir.y).asin();
43    (yaw, pitch)
44}
45
46/// Get default yaw/pitch for looking from DEFAULT_CAM_POS to DEFAULT_LOOK_AT
47fn default_yaw_pitch() -> (f32, f32) {
48    let dir = (DEFAULT_LOOK_AT - DEFAULT_CAM_POS).normalize();
49    direction_to_yaw_pitch(dir)
50}
51
52fn spawn_camera(mut commands: Commands) {
53    let (yaw, pitch) = default_yaw_pitch();
54    commands.spawn((
55        Camera3d::default(),
56        Transform::from_translation(DEFAULT_CAM_POS).with_rotation(Quat::from_euler(
57            EulerRot::YXZ,
58            yaw,
59            pitch,
60            0.0,
61        )),
62        FirstPersonCamera {
63            yaw,
64            pitch,
65            ..default()
66        },
67    ));
68}
69
70/// Reset camera with R or Home key
71fn camera_reset(
72    mut query: Query<(&mut Transform, &mut FirstPersonCamera)>,
73    keyboard: Res<ButtonInput<KeyCode>>,
74) {
75    if keyboard.just_pressed(KeyCode::KeyR) || keyboard.just_pressed(KeyCode::Home) {
76        let (yaw, pitch) = default_yaw_pitch();
77        for (mut transform, mut camera) in query.iter_mut() {
78            transform.translation = DEFAULT_CAM_POS;
79            camera.yaw = yaw;
80            camera.pitch = pitch;
81            transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, 0.0);
82        }
83    }
84}
85
86fn camera_look(
87    mut query: Query<(&mut Transform, &mut FirstPersonCamera)>,
88    mut mouse_motion: EventReader<MouseMotion>,
89    mouse_button: Res<ButtonInput<MouseButton>>,
90) {
91    // Only look when right mouse button is held
92    if !mouse_button.pressed(MouseButton::Right) {
93        mouse_motion.clear();
94        return;
95    }
96
97    let mut delta = Vec2::ZERO;
98    for event in mouse_motion.read() {
99        delta += event.delta;
100    }
101
102    if delta == Vec2::ZERO {
103        return;
104    }
105
106    for (mut transform, mut camera) in query.iter_mut() {
107        camera.yaw -= delta.x * camera.sensitivity;
108        camera.pitch -= delta.y * camera.sensitivity;
109        camera.pitch = camera.pitch.clamp(-1.5, 1.5);
110
111        transform.rotation = Quat::from_euler(EulerRot::YXZ, camera.yaw, camera.pitch, 0.0);
112    }
113}
114
115fn camera_move(
116    mut query: Query<(&mut Transform, &FirstPersonCamera)>,
117    keyboard: Res<ButtonInput<KeyCode>>,
118    time: Res<Time>,
119) {
120    for (mut transform, camera) in query.iter_mut() {
121        let mut direction = Vec3::ZERO;
122
123        // Get forward/right vectors (ignore Y for movement)
124        let forward = transform.forward();
125        let forward_flat = Vec3::new(forward.x, 0.0, forward.z).normalize_or_zero();
126        let right = transform.right();
127        let right_flat = Vec3::new(right.x, 0.0, right.z).normalize_or_zero();
128
129        // WASD movement
130        if keyboard.pressed(KeyCode::KeyW) || keyboard.pressed(KeyCode::ArrowUp) {
131            direction += forward_flat;
132        }
133        if keyboard.pressed(KeyCode::KeyS) || keyboard.pressed(KeyCode::ArrowDown) {
134            direction -= forward_flat;
135        }
136        if keyboard.pressed(KeyCode::KeyA) || keyboard.pressed(KeyCode::ArrowLeft) {
137            direction -= right_flat;
138        }
139        if keyboard.pressed(KeyCode::KeyD) || keyboard.pressed(KeyCode::ArrowRight) {
140            direction += right_flat;
141        }
142
143        // Q/E for up/down
144        if keyboard.pressed(KeyCode::KeyQ) {
145            direction += Vec3::Y;
146        }
147        if keyboard.pressed(KeyCode::KeyE) {
148            direction -= Vec3::Y;
149        }
150
151        // Apply movement
152        if direction != Vec3::ZERO {
153            direction = direction.normalize();
154            transform.translation += direction * camera.speed * time.delta_secs();
155        }
156    }
157}