text2d/
text2d.rs

1//! Shows text rendering with moving, rotating and scaling text.
2//!
3//! Note that this uses [`Text2d`] to display text alongside your other entities in a 2D scene.
4//!
5//! For an example on how to render text as part of a user interface, independent from the world
6//! viewport, you may want to look at `games/contributors.rs` or `ui/text.rs`.
7
8use bevy::{
9    color::palettes::css::*,
10    math::ops,
11    prelude::*,
12    sprite::Anchor,
13    text::{FontSmoothing, LineBreak, TextBounds},
14};
15
16fn main() {
17    App::new()
18        .add_plugins(DefaultPlugins)
19        .add_systems(Startup, setup)
20        .add_systems(
21            Update,
22            (animate_translation, animate_rotation, animate_scale),
23        )
24        .run();
25}
26
27#[derive(Component)]
28struct AnimateTranslation;
29
30#[derive(Component)]
31struct AnimateRotation;
32
33#[derive(Component)]
34struct AnimateScale;
35
36fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
37    let font = asset_server.load("fonts/FiraSans-Bold.ttf");
38    let text_font = TextFont {
39        font: font.clone(),
40        font_size: 50.0,
41        ..default()
42    };
43    let text_justification = JustifyText::Center;
44    commands.spawn(Camera2d);
45    // Demonstrate changing translation
46    commands.spawn((
47        Text2d::new("translation"),
48        text_font.clone(),
49        TextLayout::new_with_justify(text_justification),
50        AnimateTranslation,
51    ));
52    // Demonstrate changing rotation
53    commands.spawn((
54        Text2d::new("rotation"),
55        text_font.clone(),
56        TextLayout::new_with_justify(text_justification),
57        AnimateRotation,
58    ));
59    // Demonstrate changing scale
60    commands.spawn((
61        Text2d::new("scale"),
62        text_font,
63        TextLayout::new_with_justify(text_justification),
64        Transform::from_translation(Vec3::new(400.0, 0.0, 0.0)),
65        AnimateScale,
66    ));
67    // Demonstrate text wrapping
68    let slightly_smaller_text_font = TextFont {
69        font,
70        font_size: 35.0,
71        ..default()
72    };
73    let box_size = Vec2::new(300.0, 200.0);
74    let box_position = Vec2::new(0.0, -250.0);
75    commands
76        .spawn((
77            Sprite::from_color(Color::srgb(0.25, 0.25, 0.55), box_size),
78            Transform::from_translation(box_position.extend(0.0)),
79        ))
80        .with_children(|builder| {
81            builder.spawn((
82                Text2d::new("this text wraps in the box\n(Unicode linebreaks)"),
83                slightly_smaller_text_font.clone(),
84                TextLayout::new(JustifyText::Left, LineBreak::WordBoundary),
85                // Wrap text in the rectangle
86                TextBounds::from(box_size),
87                // Ensure the text is drawn on top of the box
88                Transform::from_translation(Vec3::Z),
89            ));
90        });
91
92    let other_box_size = Vec2::new(300.0, 200.0);
93    let other_box_position = Vec2::new(320.0, -250.0);
94    commands
95        .spawn((
96            Sprite::from_color(Color::srgb(0.25, 0.25, 0.55), other_box_size),
97            Transform::from_translation(other_box_position.extend(0.0)),
98        ))
99        .with_children(|builder| {
100            builder.spawn((
101                Text2d::new("this text wraps in the box\n(AnyCharacter linebreaks)"),
102                slightly_smaller_text_font.clone(),
103                TextLayout::new(JustifyText::Left, LineBreak::AnyCharacter),
104                // Wrap text in the rectangle
105                TextBounds::from(other_box_size),
106                // Ensure the text is drawn on top of the box
107                Transform::from_translation(Vec3::Z),
108            ));
109        });
110
111    // Demonstrate font smoothing off
112    commands.spawn((
113        Text2d::new("This text has\nFontSmoothing::None\nAnd JustifyText::Center"),
114        slightly_smaller_text_font
115            .clone()
116            .with_font_smoothing(FontSmoothing::None),
117        TextLayout::new_with_justify(JustifyText::Center),
118        Transform::from_translation(Vec3::new(-400.0, -250.0, 0.0)),
119    ));
120
121    commands
122        .spawn((
123            Sprite {
124                color: Color::Srgba(LIGHT_CYAN),
125                custom_size: Some(Vec2::new(10., 10.)),
126                ..Default::default()
127            },
128            Transform::from_translation(250. * Vec3::Y),
129        ))
130        .with_children(|commands| {
131            for (text_anchor, color) in [
132                (Anchor::TopLeft, Color::Srgba(LIGHT_SALMON)),
133                (Anchor::TopRight, Color::Srgba(LIGHT_GREEN)),
134                (Anchor::BottomRight, Color::Srgba(LIGHT_BLUE)),
135                (Anchor::BottomLeft, Color::Srgba(LIGHT_YELLOW)),
136            ] {
137                commands
138                    .spawn((
139                        Text2d::new(" Anchor".to_string()),
140                        slightly_smaller_text_font.clone(),
141                        text_anchor,
142                    ))
143                    .with_child((
144                        TextSpan("::".to_string()),
145                        slightly_smaller_text_font.clone(),
146                        TextColor(LIGHT_GREY.into()),
147                    ))
148                    .with_child((
149                        TextSpan(format!("{text_anchor:?} ")),
150                        slightly_smaller_text_font.clone(),
151                        TextColor(color),
152                    ));
153            }
154        });
155}
156
157fn animate_translation(
158    time: Res<Time>,
159    mut query: Query<&mut Transform, (With<Text2d>, With<AnimateTranslation>)>,
160) {
161    for mut transform in &mut query {
162        transform.translation.x = 100.0 * ops::sin(time.elapsed_secs()) - 400.0;
163        transform.translation.y = 100.0 * ops::cos(time.elapsed_secs());
164    }
165}
166
167fn animate_rotation(
168    time: Res<Time>,
169    mut query: Query<&mut Transform, (With<Text2d>, With<AnimateRotation>)>,
170) {
171    for mut transform in &mut query {
172        transform.rotation = Quat::from_rotation_z(ops::cos(time.elapsed_secs()));
173    }
174}
175
176fn animate_scale(
177    time: Res<Time>,
178    mut query: Query<&mut Transform, (With<Text2d>, With<AnimateScale>)>,
179) {
180    // Consider changing font-size instead of scaling the transform. Scaling a Text2D will scale the
181    // rendered quad, resulting in a pixellated look.
182    for mut transform in &mut query {
183        let scale = (ops::sin(time.elapsed_secs()) + 1.1) * 2.0;
184        transform.scale.x = scale;
185        transform.scale.y = scale;
186    }
187}