1use bevy::{prelude::*, window::PrimaryWindow};
2
3pub mod camera;
4pub mod layer;
5pub mod parallax;
6pub mod sprite;
7
8pub use camera::*;
9pub use layer::*;
10pub use parallax::*;
11pub use sprite::*;
12
13pub struct ParallaxPlugin;
14
15impl ParallaxPlugin {
16 #[cfg(feature = "bevy-inspector-egui")]
17 fn add_features(&self, app: &mut App) {
18 app.register_type::<Limit>()
19 .register_type::<CameraFollow>()
20 .register_type::<LayerComponent>()
21 .register_type::<LayerTextureComponent>()
22 .register_type::<ParallaxCameraComponent>();
23 }
24
25 #[cfg(not(feature = "bevy-inspector-egui"))]
26 fn add_features(&self, _app: &mut App) {}
27}
28
29impl Plugin for ParallaxPlugin {
30 fn build(&self, app: &mut App) {
31 app.add_event::<ParallaxMoveEvent>()
32 .add_event::<CreateParallaxEvent>()
33 .add_systems(PreUpdate, create_parallax_system)
34 .add_systems(Update, sprite_frame_update_system)
35 .add_systems(
36 Update,
37 (camera_follow_system, move_layers_system, update_layer_textures_system)
38 .chain()
39 .in_set(ParallaxSystems),
40 );
41 self.add_features(app);
42 }
43}
44
45#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
46pub struct ParallaxSystems;
47
48fn create_parallax_system(
49 mut commands: Commands,
50 asset_server: Res<AssetServer>,
51 mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
52 window_query: Query<&Window, With<PrimaryWindow>>,
53 parallax_query: Query<(Entity, &ParallaxCameraComponent, &Camera)>,
54 layers_query: Query<(Entity, &LayerComponent)>,
55 mut create_parallax_events: EventReader<CreateParallaxEvent>,
56) {
57 let primary_window = window_query.get_single().unwrap();
58 let mut window_size = Vec2::new(primary_window.width(), primary_window.height());
59 for event in create_parallax_events.read() {
60 if let Ok((parallax_entity, parallax, camera)) = parallax_query.get(event.camera) {
61 for (entity, layer) in layers_query.iter() {
62 if layer.camera != parallax_entity {
64 continue;
65 }
66 commands.entity(entity).despawn_recursive();
67 }
68 if let Some(viewport) = &camera.viewport {
69 window_size = viewport.physical_size.as_vec2();
70 }
71 event.create_layers(
72 &mut commands,
73 window_size,
74 &asset_server,
75 &mut texture_atlases,
76 parallax.render_layer,
77 );
78 }
79 }
80}
81
82fn move_layers_system(
84 mut camera_query: Query<(&mut Transform, &ParallaxCameraComponent)>,
85 mut layer_query: Query<(&mut Transform, &LayerComponent), Without<ParallaxCameraComponent>>,
86 mut move_events: EventReader<ParallaxMoveEvent>,
87) {
88 for event in move_events.read() {
89 if let Ok((mut camera_transform, parallax)) = camera_query.get_mut(event.camera) {
90 let camera_translation = camera_transform.translation.clone();
91 camera_transform.translation = parallax
92 .inside_limits(camera_transform.translation.truncate() + event.translation)
93 .extend(camera_transform.translation.z);
94 let real_translation = camera_transform.translation - camera_translation;
95 camera_transform.rotate_z(event.rotation);
96 for (mut layer_transform, layer) in layer_query.iter_mut() {
97 if layer.camera != event.camera {
98 continue;
99 }
100 layer_transform.translation.x += real_translation.x * layer.speed.x;
101 layer_transform.translation.y += real_translation.y * layer.speed.y;
102 }
103 }
104 }
105}
106
107fn update_layer_textures_system(
109 layer_query: Query<(&LayerComponent, &Children)>,
110 mut texture_query: Query<(&GlobalTransform, &mut Transform, &LayerTextureComponent, &ViewVisibility), Without<ParallaxCameraComponent>>,
111 camera_query: Query<(Entity, &Transform, &Camera), With<ParallaxCameraComponent>>,
112 window_query: Query<&Window, With<PrimaryWindow>>,
113 mut move_events: EventReader<ParallaxMoveEvent>,
114) {
115 for event in move_events.read() {
116 if !event.has_translation() {
117 continue;
118 }
119 let primary_window = window_query.get_single().unwrap();
120 let window_size = Vec2::new(primary_window.width(), primary_window.height());
121 if let Ok((camera_entity, camera_transform, camera)) = camera_query.get(event.camera) {
122 let view_size = match &camera.viewport {
123 Some(viewport) => viewport.physical_size.as_vec2(),
124 _ => window_size,
125 };
126 for (layer, children) in layer_query.iter() {
127 if layer.camera != camera_entity {
128 continue;
129 }
130 for &child in children.iter() {
131 let (texture_gtransform, mut texture_transform, layer_texture, computed_visibility) =
132 texture_query.get_mut(child).unwrap();
133 if computed_visibility.get() {
135 continue;
136 }
137 let texture_gtransform = texture_gtransform.compute_transform();
138 let texture_translation = camera_transform.translation - texture_gtransform.translation;
139 if layer.repeat.has_horizontal() {
140 let x_delta = layer_texture.width * layer.texture_count.x;
141 let half_width = layer_texture.width * texture_gtransform.scale.x / 2.0;
142 if event.has_left_translation() && texture_translation.x + half_width < -view_size.x {
144 texture_transform.translation.x -= x_delta;
145 }
146 if event.has_right_translation() && texture_translation.x - half_width > view_size.x {
148 texture_transform.translation.x += x_delta;
149 }
150 }
151 if layer.repeat.has_vertical() {
152 let y_delta = layer_texture.height * layer.texture_count.y;
153 let half_height = layer_texture.height * texture_gtransform.scale.y / 2.0;
154 if event.has_down_translation() && texture_translation.y + half_height < -view_size.y {
156 texture_transform.translation.y -= y_delta;
157 }
158 if event.has_up_translation() && texture_translation.y - half_height > view_size.y {
160 texture_transform.translation.y += y_delta;
161 }
162 }
163 }
164 }
165 }
166 }
167}
168
169#[cfg(doctest)]
170mod test_readme {
171 macro_rules! external_doc_test {
172 ($x:expr) => {
173 #[doc = $x]
174 extern "C" {}
175 };
176 }
177 external_doc_test!(include_str!("../README.md"));
178}