bevy_topdown_camera/
lib.rs1use bevy::{input::mouse::MouseWheel, prelude::*};
3
4pub mod prelude {
5 pub use crate::*;
6}
7
8#[derive(Debug, Clone, Copy, SystemSet, PartialEq, Eq, Hash)]
9pub struct TopdownCameraSystemSet;
10
11
12pub struct TopdownCameraPlugin;
13
14impl Plugin for TopdownCameraPlugin {
15 fn build(&self, app: &mut App) {
16
17 app
18 .add_systems(
19 PostUpdate,
20 (
21 camera_follow_target,
22 camera_zoom,
23 ).in_set(TopdownCameraSystemSet)
24 );
25 }
26}
27
28
29#[derive(Component, Clone, Debug, PartialEq)]
30pub struct TopdownCamera {
31 pub follow_mode: FollowMode,
32 pub follow_speed: f32,
33 pub height: f32,
34 pub zoom: ZoomSettings,
35 pub zoom_keys: ZoomKeys,
36}
37
38#[derive(Clone, Debug, PartialEq)]
39pub enum FollowMode {
40 Smooth,
41 Fixed,
42}
43
44#[derive(Clone, Debug, PartialEq)]
45pub struct ZoomSettings {
46 pub can_zoom: bool,
47 pub allow_mouse_wheel: bool,
48 pub speed: f32,
49 pub max: f32,
50 pub min: f32,
51}
52
53impl Default for ZoomSettings {
54 fn default() -> Self {
55 Self {
56 can_zoom: true,
57 allow_mouse_wheel: true,
58 speed: 1.0,
59 max: 20.0,
60 min: 1.0,
61 }
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect)]
67pub struct ZoomKeys {
68 pub zoom_in: Vec<KeyCode>,
69 pub zoom_out: Vec<KeyCode>,
70}
71
72impl ZoomKeys {
73 pub const NONE: Self = Self {
75 zoom_in: vec![],
76 zoom_out: vec![],
77 };
78
79 pub fn plus_minus() -> Self {
80 Self {
81 zoom_in: vec![KeyCode::Equal],
82 zoom_out: vec![KeyCode::Minus],
83 }
84 }
85
86 fn direction(&self, keyboard_buttons: &Res<ButtonInput<KeyCode>>) -> f32 {
87 let mut factor: f32 = 0.0;
88
89 if self.zoom_in.iter().any(|key| keyboard_buttons.pressed(*key)) {
90 factor -= 1.;
91 }
92
93 if self.zoom_out.iter().any(|key| keyboard_buttons.pressed(*key)) {
94 factor += 1.;
95 }
96 factor
97 }
98}
99
100impl Default for TopdownCamera {
101 fn default() -> Self {
102 Self {
103 follow_mode: FollowMode::Smooth,
104 follow_speed: 5.0,
105 height: 5.0,
106 zoom: ZoomSettings::default(),
107 zoom_keys: ZoomKeys::plus_minus(),
108 }
109 }
110}
111
112
113#[derive(Component)]
114pub struct TopdownFollowTarget;
115
116
117fn camera_follow_target(
118 time: Res<Time>,
119 mut camera_query: Query<(&mut Transform, &TopdownCamera), Without<TopdownFollowTarget>>,
120 target_query: Query<&Transform, (With<TopdownFollowTarget>, Without<TopdownCamera>)>,
121) {
122 if let Ok((mut camera_transform, camera)) = camera_query.get_single_mut() {
123 if let Ok(target_transform) = target_query.get_single() {
124 let target_position = target_transform.translation;
125
126 let desired_position = target_position + Vec3::new(0.0, camera.height, camera.height);
128
129 match camera.follow_mode {
131 FollowMode::Smooth => {
132 camera_transform.translation = camera_transform.translation.lerp(
133 desired_position,
134 camera.follow_speed * time.delta_seconds()
135 );
136 }
137 FollowMode::Fixed => {
138 camera_transform.translation = desired_position;
139 }
140 }
141
142 let forward = (target_position - camera_transform.translation).normalize();
144 camera_transform.look_to(forward, Vec3::Y);
145 }
146 }
147}
148
149fn camera_zoom(
150 mut mouse_wheel_events: EventReader<MouseWheel>,
151 mut query: Query<(&mut Transform, &mut TopdownCamera)>,
152 keyboard_buttons: Res<ButtonInput<KeyCode>>,
153 time: Res<Time>,
154) {
155 if let Ok((_, camera)) = query.get_single() {
156 if !camera.zoom.can_zoom {
157 return;
158 }
159 }
160
161 let zoom_factor: f32 = mouse_wheel_events
162 .read()
163 .map(|event| event.y)
164 .sum();
165
166
167 for (mut transform, mut camera) in query.iter_mut() {
168 let mut zoom_amount = 0.0;
169
170 if camera.zoom.allow_mouse_wheel {
172 if zoom_factor != 0.0 {
173 zoom_amount += zoom_factor * camera.zoom.speed * time.delta_seconds();
174
175 }
176 }
177
178 let direction = camera.zoom_keys.direction(&keyboard_buttons);
180 if direction != 0.0 {
181 zoom_amount -= direction * camera.zoom.speed * time.delta_seconds();
182 }
183
184 if zoom_amount != 0.0 {
186 camera.height -= zoom_amount;
187 camera.height = camera.height.clamp(camera.zoom.min, camera.zoom.max);
188
189 let target_y = camera.height;
191 let target_z = camera.height;
192 transform.translation.y = transform.translation.y.lerp(target_y, camera.follow_speed * time.delta_seconds());
193 transform.translation.z = transform.translation.z.lerp(target_z, camera.follow_speed * time.delta_seconds());
194 }
195
196 }
197}