bevy_prototype_parallax/
layer.rs

1use crate::window_size::WindowSize;
2use bevy::{prelude::*, render::camera::Camera};
3
4#[derive(Default, Debug)]
5pub struct Layer {
6    pub speed: f32,
7}
8#[derive(Bundle, Default)]
9pub struct LayerComponents {
10    pub layer: Layer,
11    pub transform: Transform,
12    pub global: GlobalTransform,
13    pub children: Children,
14    pub material: Handle<ColorMaterial>,
15    pub sprite: Sprite,
16}
17
18/// Gets the 'screen' width of the sprite.
19/// This takes into account the scaling
20fn sprite_scaled_width(sprite: &Sprite, transform: &Transform) -> f32 {
21    sprite.size[0] * transform.scale.x()
22}
23
24/// Calculate the amount of sprites we need for the effect
25fn desired_children_count(window: &WindowSize, sprite: &Sprite, transform: &Transform) -> u32 {
26    let tex_width = sprite_scaled_width(sprite, transform) as u32;
27    if tex_width > 0 {
28        window.width.div_euclid(tex_width) + 2
29    } else {
30        0
31    }
32}
33
34/// Caculates an offset to put the layer at the left edge of the 'container'
35/// This is because the camera seems to center on 0.0
36fn camera_left_edge_offset(window: &WindowSize) -> f32 {
37    let left_side = 0.0 - window.width as f32 / 2.0;
38    left_side
39}
40
41/// How far to offset the layer due to the camera position
42/// Will be clamped by the sprite offset
43fn camera_sprite_offset(
44    camera: &Vec3,
45    layer: &Layer,
46    sprite: &Sprite,
47    transform: &Transform,
48) -> f32 {
49    let sprite_width = sprite_scaled_width(sprite, transform);
50    -(camera.x() * layer.speed).rem_euclid(sprite_width)
51}
52
53/// Mutates the layer based on the camera position
54/// this allows us to have the parallax effect by having the layers move at different rates
55/// once we move past the width of the sprite, it resets to 0
56fn move_layer_position(
57    window: &WindowSize,
58    camera: &Vec3,
59    sprite: &Sprite,
60    layer: &Layer,
61    transform: &mut Transform,
62) -> () {
63    let offset = camera_left_edge_offset(&window);
64    let camera_x = camera_sprite_offset(camera, layer, sprite, transform);
65    *transform.translation.x_mut() = offset + camera_x;
66}
67
68/// Manages the amount of child sprites we need to repeat
69/// Based on the windows size
70pub fn children_count_system(
71    mut commands: Commands,
72    cameras_query: Query<(&Camera, &WindowSize, &Children)>,
73    mut layer_query: Query<(
74        With<Layer, Entity>,
75        &Parent,
76        &Children,
77        &Sprite,
78        &Handle<ColorMaterial>,
79        &Transform,
80    )>,
81) -> () {
82    for (entity, parent, children, sprite, material, transform) in layer_query.iter_mut() {
83        if let Ok(window) = cameras_query.get_component(parent.0) {
84            let desired_children = desired_children_count(&window, &sprite, &transform);
85            let current_children = children.len();
86            let to_add = desired_children as usize - current_children;
87
88            for _ in 0..to_add {
89                let child = SpriteComponents {
90                    material: material.clone(),
91                    sprite: Sprite::default(),
92                    ..Default::default()
93                };
94
95                commands.spawn(child).with(Parent(entity));
96            }
97
98            //TODO: remove sprites if they aren't needed
99        }
100    }
101}
102
103/// Responsible for setting the positioning of the sprites
104pub fn children_layout_system(
105    layers: Query<With<Layer, (&Sprite, &Children)>>,
106    mut sprites: Query<&mut Transform>,
107) {
108    for (sprite, children) in layers.iter() {
109        for (index, child) in children.iter().enumerate() {
110            if let Ok(mut transform) = sprites.get_component_mut::<Transform>(*child) {
111                *transform.translation.x_mut() =
112                    index as f32 * sprite_scaled_width(sprite, &transform);
113                *transform.translation.z_mut() = -999.0;
114            }
115        }
116    }
117}
118
119/// Matches the layer to the camera.
120/// Note the layer is offset to the left by half the window to make
121pub fn layer_movement_system(
122    cameras: Query<With<Camera, (&Transform, &WindowSize, &Children)>>,
123    mut layers: Query<(&Layer, &Sprite, &mut Transform)>,
124) -> () {
125    for (transform, window, children) in cameras.iter() {
126        let camera = transform.translation;
127        for child in children.iter() {
128            if let Ok((layer, sprite, mut trans)) = layers.get_mut(*child) {
129                move_layer_position(window, &camera, sprite, layer, &mut trans);
130            }
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use rstest::rstest;
139
140    #[rstest(
141        width,
142        expected,
143        case(1024, -512.0),
144        case(1000, -500.0)
145    )]
146    fn test_left_edge(width: u32, expected: f32) {
147        let window = WindowSize {
148            height: 576,
149            width: width,
150        };
151        let result = camera_left_edge_offset(&window);
152        assert_eq!(expected, result);
153    }
154
155    #[rstest(
156        camera,
157        speed,
158        sprite,
159        expected,
160        case(0.0, 1.0,100.0, 0.0),
161        case(1.0, 1.0,100.0, -1.0),
162        case(101.0, 1.0, 100.0, -1.0),
163        case(200.0, 1.0, 100.0, 0.0),
164        case(220.0, 1.0, 100.0, -20.0)
165        ::trace
166    )]
167    fn test_layer_offset(camera: f32, speed: f32, sprite: f32, expected: f32) {
168        let camera = Vec3::new(camera, 0.0, 0.0);
169        let sprite = Sprite::new(Vec2::splat(sprite));
170        let transform = Transform::default();
171        let layer = Layer { speed };
172        let result = camera_sprite_offset(&camera, &layer, &sprite, &transform);
173        assert_eq!(expected, result);
174    }
175
176    #[rstest(
177        sprite,
178        scale,
179        expected,
180        case(100.0, 1.0, 100.0),
181        case(100.0, 2.0, 200.0),
182        case(100.0, 0.0, 0.0),
183        case(512.0, 1.0, 512.0)
184        ::trace
185    )]
186    fn test_scaled_width(sprite: f32, scale: f32, expected: f32) {
187        let transform = Transform {
188            scale: Vec3::splat(scale),
189            ..Default::default()
190        };
191        let sprite = Sprite::new(Vec2::splat(sprite));
192        let result = sprite_scaled_width(&sprite, &transform);
193        assert_eq!(expected, result);
194    }
195
196    #[rstest(
197        screen,
198        texture,
199        scale,
200        expected,
201        case(1024, 100, 1.0,12),
202        case(1024, 1025,1.0, 2),
203        case(1024, 800, 1.0,3),
204        case(1024, 0, 1.0,0)
205        ::trace
206    )]
207    fn test_desired_children_count(screen: u32, texture: u32, scale: f32, expected: u32) {
208        let window = WindowSize {
209            height: 576,
210            width: screen,
211        };
212
213        let transform = Transform {
214            scale: Vec3::splat(scale),
215            ..Default::default()
216        };
217
218        let texture = Sprite::new(Vec2::new(texture as f32, window.height as f32));
219        let result = desired_children_count(&window, &texture, &transform);
220        assert_eq!(expected, result);
221    }
222
223    #[rstest(
224        screen,camera,sprite,speed,expected,
225        case(1024,0.0, 512.0,0.0,-512.0),
226        case(1024,1.0, 512.0,0.0,-512.0),
227        case(1024,512.0, 512.0,1.0,-512.0),
228        case(1024,513.0, 512.0,1.0,-513.0),
229        case(1024,1.0, 512.0,1.0,-513.0),
230        case(1024,2.0, 512.0,0.5,-513.0),
231        case(1024,1024.0, 512.0,1.0,-512.0)
232        ::trace
233    )]
234    fn test_layer_translation(screen: u32, camera: f32, sprite: f32, speed: f32, expected: f32) {
235        let window_size = WindowSize {
236            height: 576,
237            width: screen,
238        };
239
240        let camera = Vec3::new(camera, 0.0, 0.0);
241        let speed = Layer { speed };
242        let sprite = Sprite::new(Vec2::new(sprite, window_size.height as f32));
243        let mut transform = Transform::default();
244        move_layer_position(&window_size, &camera, &sprite, &speed, &mut transform);
245        assert_eq!(expected, transform.translation.x());
246    }
247}