1use bevy::{
2 app::{PostStartup, Update},
3 asset::Assets,
4 ecs::{
5 bundle::Bundle,
6 component::Component,
7 system::{Commands, Res, ResMut},
8 },
9 gizmos::{gizmos::Gizmos, GizmoConfig},
10 math::{Vec2, Vec3},
11 prelude::{
12 default, App, Camera2dBundle, Entity, Plugin, PostUpdate, Query, Time, Timer, TimerMode,
13 Transform, With, Without,
14 },
15 render::{
16 color::Color,
17 mesh::{shape, Mesh},
18 },
19 sprite::{ColorMaterial, MaterialMesh2dBundle},
20};
21
22#[derive(Component)]
23pub struct Target;
24
25#[derive(Component)]
26pub struct Cameraman {
27 target: Entity, dead_zone: Vec2,
29 target_prev_translation: Vec3,
30 look_at: Vec3,
33 ahead_factor: Vec3,
34 traveling: bool,
35 center_after: Timer,
36}
37
38impl Cameraman {
39 pub fn new(target: Entity, dead_zone: Vec2, ahead_factor: Vec3) -> Self {
40 Self {
41 target,
42 dead_zone,
43 target_prev_translation: Vec3::ZERO,
44 look_at: Vec3::ZERO,
45 ahead_factor,
46 traveling: false,
47 center_after: Timer::from_seconds(0.4, TimerMode::Once),
48 }
49 }
50
51 pub fn new_default(target: Entity) -> Self {
52 Self {
53 target,
54 dead_zone: Vec2::new(30.0, 15.0),
55 target_prev_translation: Vec3::ZERO,
56 look_at: Vec3::ZERO,
57 ahead_factor: Vec3::ONE,
58 traveling: false,
59 center_after: Timer::from_seconds(0.4, TimerMode::Once),
60 }
61 }
62}
63
64#[derive(Bundle)]
65pub struct CameraBundle {
66 camera: Cameraman,
67 bundle: Camera2dBundle,
68}
69
70impl CameraBundle {
71 pub fn new(camera: Cameraman, bundle: Camera2dBundle) -> Self {
72 Self { camera, bundle }
73 }
74
75 pub fn new_with_default_bundle(target: Entity) -> Self {
76 Self {
77 camera: Cameraman::new_default(target),
78 bundle: Camera2dBundle::default(),
79 }
80 }
81}
82
83pub struct CameraPlugin;
84
85impl Plugin for CameraPlugin {
86 fn build(&self, app: &mut App) {
87 app.add_systems(PostStartup, center)
88 .add_systems(PostUpdate, cameraman);
89 }
90}
91
92fn center(
93 mut query_camera: Query<(&mut Transform, &mut Cameraman), Without<Target>>,
94 query_targets: Query<(&Transform, Entity), With<Target>>,
95) {
96 for (mut camera_transform, mut camera) in &mut query_camera {
97 for (target_transform, target_entity) in &query_targets {
98 if camera.target != target_entity {
99 continue;
100 }
101
102 if camera.target == target_entity {
104 camera_transform.translation.x = target_transform.translation.x;
105 camera_transform.translation.y = target_transform.translation.y;
106 camera.target_prev_translation = target_transform.translation;
107 camera.look_at = target_transform.translation;
108 break;
109 }
110 }
111 }
112}
113
114fn cameraman(
115 mut query_camera: Query<(&mut Transform, &mut Cameraman), Without<Target>>,
116 query_targets: Query<(&Transform, Entity), With<Target>>,
117 time: Res<Time>,
118) {
119 for (mut camera_transform, mut camera) in &mut query_camera {
120 for (target_transform, target_entity) in &query_targets {
121 if camera.target != target_entity {
122 continue;
123 }
124
125 let mut target_velocity = target_transform.translation - camera.target_prev_translation;
127 let target_moving = target_velocity != Vec3::ZERO;
128 if target_moving {
129 target_velocity /= time.delta_seconds();
130 }
131 camera.look_at = target_transform.translation + (target_velocity * camera.ahead_factor);
132 camera.target_prev_translation = target_transform.translation;
133
134 let diff_pos_abs = (target_transform.translation - camera_transform.translation)
136 .truncate()
137 .abs();
138 let dead_zone =
139 diff_pos_abs.x <= camera.dead_zone.x && diff_pos_abs.y <= camera.dead_zone.y;
140 let centered = diff_pos_abs.x < 3.0 && diff_pos_abs.y < 3.0;
141
142 if dead_zone && !centered && !target_moving && !camera.traveling {
144 camera.center_after.tick(time.delta());
145 } else {
146 camera.center_after.reset();
147 }
148
149 if !dead_zone {
151 camera.traveling = true;
152 }
153
154 if camera.traveling || camera.center_after.finished() {
156 let next_pos = camera.look_at - camera_transform.translation;
157 camera_transform.translation.x += next_pos.x * 0.02;
158 camera_transform.translation.y += next_pos.y * 0.02;
159
160 if centered && !target_moving {
162 camera.traveling = false;
163 }
164 }
165
166 }
168 }
169}
170
171pub struct CameraDebugPlugin;
172
173impl Plugin for CameraDebugPlugin {
174 fn build(&self, app: &mut App) {
175 app.add_systems(PostStartup, setup_debug)
176 .add_systems(Update, debug);
177 }
178}
179
180#[derive(Component)]
181pub struct CameraDebug(Entity);
182
183fn setup_debug(
184 mut commands: Commands,
185 mut meshes: ResMut<Assets<Mesh>>,
186 mut materials: ResMut<Assets<ColorMaterial>>,
187 query_cameras: Query<(&Transform, &Cameraman, Entity)>,
188) {
189 for (camera_transform, _camera, entity) in &query_cameras {
190 commands.spawn((
191 MaterialMesh2dBundle {
192 mesh: meshes.add(shape::Circle::new(2.).into()).into(),
193 material: materials.add(ColorMaterial::from(Color::GREEN)),
194 transform: Transform::from_translation(Vec3::new(
195 camera_transform.translation.x,
196 camera_transform.translation.y,
197 100.0,
198 )),
199 ..default()
200 },
201 CameraDebug(entity),
202 ));
203 }
204}
205
206#[allow(clippy::type_complexity)] fn debug(
208 mut gizmos: Gizmos,
209 mut config: ResMut<GizmoConfig>,
210 query_cameras: Query<(&Transform, &Cameraman, Entity)>,
211 query_targets: Query<(&Transform, Entity), (With<Target>, Without<CameraDebug>)>,
212 mut query_camera_debug: Query<(&mut Transform, &CameraDebug), Without<Cameraman>>,
213) {
214 config.line_width = 1.0;
215
216 for (camera_transform, camera, entity) in &query_cameras {
219 gizmos.rect_2d(
220 camera_transform.translation.truncate(),
221 0.,
222 camera.dead_zone * 2.0,
223 Color::RED,
224 );
225
226 for (target_transform, target_entity) in &query_targets {
227 if camera.target != target_entity {
228 continue;
229 }
230
231 gizmos.line_2d(
232 target_transform.translation.truncate(),
233 camera.look_at.truncate(),
234 Color::GREEN,
235 );
236 }
237
238 for (mut transform, camera_debug) in &mut query_camera_debug {
239 if camera_debug.0 != entity {
240 continue;
241 }
242 transform.translation.x = camera.look_at.x;
243 transform.translation.y = camera.look_at.y;
244 }
245 }
246}