bevy_parallax/
lib.rs

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 it is not my layer don't despawn
63                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
82/// Move camera and background layers
83fn 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
107/// Update layer positions to keep the effect going indefinitely
108fn 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                    // Do not move visible textures
134                    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                        // Move not visible right texture to left side of layer when camera is moving to left
143                        if event.has_left_translation() && texture_translation.x + half_width < -view_size.x {
144                            texture_transform.translation.x -= x_delta;
145                        }
146                        // Move not visible left texture to right side of layer when camera is moving to right
147                        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                        // Move not visible top texture to the bottom of the layer when the camera is moving to the bottom
155                        if event.has_down_translation() && texture_translation.y + half_height < -view_size.y {
156                            texture_transform.translation.y -= y_delta;
157                        }
158                        // Move not visible bottom texture to the top of the layer when the camera is moving to the top
159                        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}