testbed_2d/
2d.rs

1//! 2d testbed
2//!
3//! You can switch scene by pressing the spacebar
4
5mod helpers;
6
7use bevy::prelude::*;
8use helpers::Next;
9
10fn main() {
11    let mut app = App::new();
12    app.add_plugins((DefaultPlugins,))
13        .init_state::<Scene>()
14        .add_systems(OnEnter(Scene::Shapes), shapes::setup)
15        .add_systems(OnEnter(Scene::Bloom), bloom::setup)
16        .add_systems(OnEnter(Scene::Text), text::setup)
17        .add_systems(OnEnter(Scene::Sprite), sprite::setup)
18        .add_systems(OnEnter(Scene::Gizmos), gizmos::setup)
19        .add_systems(Update, switch_scene)
20        .add_systems(Update, gizmos::draw_gizmos.run_if(in_state(Scene::Gizmos)));
21
22    #[cfg(feature = "bevy_ci_testing")]
23    app.add_systems(Update, helpers::switch_scene_in_ci::<Scene>);
24
25    app.run();
26}
27
28#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
29enum Scene {
30    #[default]
31    Shapes,
32    Bloom,
33    Text,
34    Sprite,
35    Gizmos,
36}
37
38impl Next for Scene {
39    fn next(&self) -> Self {
40        match self {
41            Scene::Shapes => Scene::Bloom,
42            Scene::Bloom => Scene::Text,
43            Scene::Text => Scene::Sprite,
44            Scene::Sprite => Scene::Gizmos,
45            Scene::Gizmos => Scene::Shapes,
46        }
47    }
48}
49
50fn switch_scene(
51    keyboard: Res<ButtonInput<KeyCode>>,
52    scene: Res<State<Scene>>,
53    mut next_scene: ResMut<NextState<Scene>>,
54) {
55    if keyboard.just_pressed(KeyCode::Space) {
56        info!("Switching scene");
57        next_scene.set(scene.get().next());
58    }
59}
60
61mod shapes {
62    use bevy::prelude::*;
63
64    const X_EXTENT: f32 = 900.;
65
66    pub fn setup(
67        mut commands: Commands,
68        mut meshes: ResMut<Assets<Mesh>>,
69        mut materials: ResMut<Assets<ColorMaterial>>,
70    ) {
71        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Shapes)));
72
73        let shapes = [
74            meshes.add(Circle::new(50.0)),
75            meshes.add(CircularSector::new(50.0, 1.0)),
76            meshes.add(CircularSegment::new(50.0, 1.25)),
77            meshes.add(Ellipse::new(25.0, 50.0)),
78            meshes.add(Annulus::new(25.0, 50.0)),
79            meshes.add(Capsule2d::new(25.0, 50.0)),
80            meshes.add(Rhombus::new(75.0, 100.0)),
81            meshes.add(Rectangle::new(50.0, 100.0)),
82            meshes.add(RegularPolygon::new(50.0, 6)),
83            meshes.add(Triangle2d::new(
84                Vec2::Y * 50.0,
85                Vec2::new(-50.0, -50.0),
86                Vec2::new(50.0, -50.0),
87            )),
88        ];
89        let num_shapes = shapes.len();
90
91        for (i, shape) in shapes.into_iter().enumerate() {
92            // Distribute colors evenly across the rainbow.
93            let color = Color::hsl(360. * i as f32 / num_shapes as f32, 0.95, 0.7);
94
95            commands.spawn((
96                Mesh2d(shape),
97                MeshMaterial2d(materials.add(color)),
98                Transform::from_xyz(
99                    // Distribute shapes from -X_EXTENT/2 to +X_EXTENT/2.
100                    -X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * X_EXTENT,
101                    0.0,
102                    0.0,
103                ),
104                DespawnOnExit(super::Scene::Shapes),
105            ));
106        }
107    }
108}
109
110mod bloom {
111    use bevy::{core_pipeline::tonemapping::Tonemapping, post_process::bloom::Bloom, prelude::*};
112
113    pub fn setup(
114        mut commands: Commands,
115        mut meshes: ResMut<Assets<Mesh>>,
116        mut materials: ResMut<Assets<ColorMaterial>>,
117    ) {
118        commands.spawn((
119            Camera2d,
120            Tonemapping::TonyMcMapface,
121            Bloom::default(),
122            DespawnOnExit(super::Scene::Bloom),
123        ));
124
125        commands.spawn((
126            Mesh2d(meshes.add(Circle::new(100.))),
127            MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))),
128            Transform::from_translation(Vec3::new(-200., 0., 0.)),
129            DespawnOnExit(super::Scene::Bloom),
130        ));
131
132        commands.spawn((
133            Mesh2d(meshes.add(RegularPolygon::new(100., 6))),
134            MeshMaterial2d(materials.add(Color::srgb(6.25, 9.4, 9.1))),
135            Transform::from_translation(Vec3::new(200., 0., 0.)),
136            DespawnOnExit(super::Scene::Bloom),
137        ));
138    }
139}
140
141mod text {
142    use bevy::color::palettes;
143    use bevy::prelude::*;
144    use bevy::sprite::Anchor;
145    use bevy::text::TextBounds;
146
147    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
148        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Text)));
149
150        for (i, justify) in [
151            Justify::Left,
152            Justify::Right,
153            Justify::Center,
154            Justify::Justified,
155        ]
156        .into_iter()
157        .enumerate()
158        {
159            let y = 230. - 150. * i as f32;
160            spawn_anchored_text(&mut commands, -300. * Vec3::X + y * Vec3::Y, justify, None);
161            spawn_anchored_text(
162                &mut commands,
163                300. * Vec3::X + y * Vec3::Y,
164                justify,
165                Some(TextBounds::new(150., 60.)),
166            );
167        }
168
169        let sans_serif = TextFont::from(asset_server.load("fonts/FiraSans-Bold.ttf"));
170
171        const NUM_ITERATIONS: usize = 10;
172        for i in 0..NUM_ITERATIONS {
173            let fraction = i as f32 / (NUM_ITERATIONS - 1) as f32;
174
175            commands.spawn((
176                Text2d::new("Bevy"),
177                sans_serif.clone(),
178                Transform::from_xyz(0.0, fraction * 200.0, i as f32)
179                    .with_scale(1.0 + Vec2::splat(fraction).extend(1.))
180                    .with_rotation(Quat::from_rotation_z(fraction * core::f32::consts::PI)),
181                TextColor(Color::hsla(fraction * 360.0, 0.8, 0.8, 0.8)),
182                DespawnOnExit(super::Scene::Text),
183            ));
184        }
185
186        commands.spawn((
187            Text2d::new("This text is invisible."),
188            Visibility::Hidden,
189            DespawnOnExit(super::Scene::Text),
190        ));
191    }
192
193    fn spawn_anchored_text(
194        commands: &mut Commands,
195        dest: Vec3,
196        justify: Justify,
197        bounds: Option<TextBounds>,
198    ) {
199        commands.spawn((
200            Sprite {
201                color: palettes::css::YELLOW.into(),
202                custom_size: Some(5. * Vec2::ONE),
203                ..Default::default()
204            },
205            Transform::from_translation(dest),
206            DespawnOnExit(super::Scene::Text),
207        ));
208
209        for anchor in [
210            Anchor::TOP_LEFT,
211            Anchor::TOP_RIGHT,
212            Anchor::BOTTOM_RIGHT,
213            Anchor::BOTTOM_LEFT,
214        ] {
215            let mut text = commands.spawn((
216                Text2d::new("L R\n"),
217                TextLayout::new_with_justify(justify),
218                Transform::from_translation(dest + Vec3::Z),
219                anchor,
220                DespawnOnExit(super::Scene::Text),
221                ShowAabbGizmo {
222                    color: Some(palettes::tailwind::AMBER_400.into()),
223                },
224                children![
225                    (
226                        TextSpan::new(format!("{}, {}\n", anchor.x, anchor.y)),
227                        TextFont::from_font_size(14.0),
228                        TextColor(palettes::tailwind::BLUE_400.into()),
229                    ),
230                    (
231                        TextSpan::new(format!("{justify:?}")),
232                        TextFont::from_font_size(14.0),
233                        TextColor(palettes::tailwind::GREEN_400.into()),
234                    ),
235                ],
236            ));
237            if let Some(bounds) = bounds {
238                text.insert(bounds);
239
240                commands.spawn((
241                    Sprite {
242                        color: palettes::tailwind::GRAY_900.into(),
243                        custom_size: Some(Vec2::new(bounds.width.unwrap(), bounds.height.unwrap())),
244                        ..Default::default()
245                    },
246                    Transform::from_translation(dest - Vec3::Z),
247                    anchor,
248                    DespawnOnExit(super::Scene::Text),
249                ));
250            }
251        }
252    }
253}
254
255mod sprite {
256    use bevy::color::palettes::css::{BLUE, LIME, RED};
257    use bevy::prelude::*;
258    use bevy::sprite::Anchor;
259
260    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
261        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Sprite)));
262        for (anchor, flip_x, flip_y, color) in [
263            (Anchor::BOTTOM_LEFT, false, false, Color::WHITE),
264            (Anchor::BOTTOM_RIGHT, true, false, RED.into()),
265            (Anchor::TOP_LEFT, false, true, LIME.into()),
266            (Anchor::TOP_RIGHT, true, true, BLUE.into()),
267        ] {
268            commands.spawn((
269                Sprite {
270                    image: asset_server.load("branding/bevy_logo_dark.png"),
271                    flip_x,
272                    flip_y,
273                    color,
274                    ..default()
275                },
276                anchor,
277                DespawnOnExit(super::Scene::Sprite),
278            ));
279        }
280    }
281}
282
283mod gizmos {
284    use bevy::{color::palettes::css::*, prelude::*};
285
286    pub fn setup(mut commands: Commands) {
287        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Gizmos)));
288    }
289
290    pub fn draw_gizmos(mut gizmos: Gizmos) {
291        gizmos.rect_2d(
292            Isometry2d::from_translation(Vec2::new(-200.0, 0.0)),
293            Vec2::new(200.0, 200.0),
294            RED,
295        );
296        gizmos
297            .circle_2d(
298                Isometry2d::from_translation(Vec2::new(-200.0, 0.0)),
299                200.0,
300                GREEN,
301            )
302            .resolution(64);
303
304        // 2d grids with all variations of outer edges on or off
305        for i in 0..4 {
306            let x = 200.0 * (1.0 + (i % 2) as f32);
307            let y = 150.0 * (0.5 - (i / 2) as f32);
308            let mut grid = gizmos.grid(
309                Vec3::new(x, y, 0.0),
310                UVec2::new(5, 4),
311                Vec2::splat(30.),
312                Color::WHITE,
313            );
314            if i & 1 > 0 {
315                grid = grid.outer_edges_x();
316            }
317            if i & 2 > 0 {
318                grid.outer_edges_y();
319            }
320        }
321    }
322}