Skip to main content

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 `showcase/contributors.rs` or `ui/text.rs`.
7
8use bevy::{
9    color::palettes::css::*,
10    math::ops,
11    prelude::*,
12    sprite::{Anchor, Text2dShadow},
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().into(),
40        font_size: FontSize::Px(50.0),
41        ..default()
42    };
43    let text_justification = Justify::Center;
44    commands.spawn(Camera2d);
45    // Demonstrate changing translation
46    commands.spawn((
47        Text2d::new(" translation "),
48        text_font.clone(),
49        TextLayout::justify(text_justification),
50        TextBackgroundColor(Color::BLACK.with_alpha(0.5)),
51        Text2dShadow::default(),
52        AnimateTranslation,
53    ));
54    // Demonstrate changing rotation
55    commands.spawn((
56        Text2d::new(" rotation "),
57        text_font.clone(),
58        TextLayout::justify(text_justification),
59        TextBackgroundColor(Color::BLACK.with_alpha(0.5)),
60        Text2dShadow::default(),
61        AnimateRotation,
62    ));
63    // Demonstrate changing scale
64    commands.spawn((
65        Text2d::new(" scale "),
66        text_font,
67        TextLayout::justify(text_justification),
68        Transform::from_translation(Vec3::new(400.0, 0.0, 0.0)),
69        TextBackgroundColor(Color::BLACK.with_alpha(0.5)),
70        Text2dShadow::default(),
71        AnimateScale,
72    ));
73    // Demonstrate text wrapping
74    let slightly_smaller_text_font = TextFont {
75        font: font.into(),
76        font_size: FontSize::Px(35.0),
77        ..default()
78    };
79    let box_size = Vec2::new(300.0, 200.0);
80    let box_position = Vec2::new(0.0, -250.0);
81    let box_color = Color::srgb(0.25, 0.25, 0.55);
82    let text_shadow_color = box_color.darker(0.05);
83    commands.spawn((
84        Sprite::from_color(Color::srgb(0.25, 0.25, 0.55), box_size),
85        Transform::from_translation(box_position.extend(0.0)),
86        children![(
87            Text2d::new("this text wraps in the box\n(Unicode linebreaks)"),
88            slightly_smaller_text_font.clone(),
89            TextLayout::new(Justify::Left, LineBreak::WordBoundary),
90            // Wrap text in the rectangle
91            TextBounds::from(box_size),
92            // Ensure the text is drawn on top of the box
93            Transform::from_translation(Vec3::Z),
94            // Add a shadow to the text
95            Text2dShadow {
96                color: text_shadow_color,
97                ..default()
98            },
99            Underline,
100        )],
101    ));
102
103    let other_box_size = Vec2::new(300.0, 200.0);
104    let other_box_position = Vec2::new(320.0, -250.0);
105    commands.spawn((
106        Sprite::from_color(Color::srgb(0.25, 0.25, 0.55), other_box_size),
107        Transform::from_translation(other_box_position.extend(0.0)),
108        children![(
109            Text2d::new("this text wraps in the box\n(AnyCharacter linebreaks)"),
110            slightly_smaller_text_font.clone(),
111            TextLayout::new(Justify::Left, LineBreak::AnyCharacter),
112            // Wrap text in the rectangle
113            TextBounds::from(other_box_size),
114            // Ensure the text is drawn on top of the box
115            Transform::from_translation(Vec3::Z),
116            // Add a shadow to the text
117            Text2dShadow {
118                color: text_shadow_color,
119                ..default()
120            }
121        )],
122    ));
123
124    // Demonstrate font smoothing off
125    commands.spawn((
126        Text2d::new("This text has\nFontSmoothing::None\nAnd Justify::Center"),
127        slightly_smaller_text_font
128            .clone()
129            .with_font_smoothing(FontSmoothing::None),
130        TextLayout::justify(Justify::Center),
131        Transform::from_translation(Vec3::new(-400.0, -250.0, 0.0)),
132        // Add a black shadow to the text
133        Text2dShadow::default(),
134    ));
135
136    let make_child = move |(text_anchor, color): (Anchor, Color)| {
137        (
138            Text2d::new(" Anchor".to_string()),
139            slightly_smaller_text_font.clone(),
140            text_anchor,
141            TextBackgroundColor(Color::WHITE.darker(0.8)),
142            Transform::from_translation(-1. * Vec3::Z),
143            children![
144                (
145                    TextSpan("::".to_string()),
146                    slightly_smaller_text_font.clone(),
147                    TextColor(LIGHT_GREY.into()),
148                    TextBackgroundColor(DARK_BLUE.into()),
149                ),
150                (
151                    TextSpan(format!("{text_anchor:?} ")),
152                    slightly_smaller_text_font.clone(),
153                    TextColor(color),
154                    TextBackgroundColor(color.darker(0.3)),
155                )
156            ],
157        )
158    };
159
160    commands.spawn((
161        Sprite {
162            color: Color::Srgba(LIGHT_CYAN),
163            custom_size: Some(Vec2::new(10., 10.)),
164            ..Default::default()
165        },
166        Transform::from_translation(250. * Vec3::Y),
167        children![
168            make_child((Anchor::TOP_LEFT, Color::Srgba(LIGHT_SALMON))),
169            make_child((Anchor::TOP_RIGHT, Color::Srgba(LIGHT_GREEN))),
170            make_child((Anchor::BOTTOM_RIGHT, Color::Srgba(LIGHT_BLUE))),
171            make_child((Anchor::BOTTOM_LEFT, Color::Srgba(LIGHT_YELLOW))),
172        ],
173    ));
174}
175
176fn animate_translation(
177    time: Res<Time>,
178    mut query: Query<&mut Transform, (With<Text2d>, With<AnimateTranslation>)>,
179) {
180    for mut transform in &mut query {
181        transform.translation.x = 100.0 * ops::sin(time.elapsed_secs()) - 400.0;
182        transform.translation.y = 100.0 * ops::cos(time.elapsed_secs());
183    }
184}
185
186fn animate_rotation(
187    time: Res<Time>,
188    mut query: Query<&mut Transform, (With<Text2d>, With<AnimateRotation>)>,
189) {
190    for mut transform in &mut query {
191        transform.rotation = Quat::from_rotation_z(ops::cos(time.elapsed_secs()));
192    }
193}
194
195fn animate_scale(
196    time: Res<Time>,
197    mut query: Query<&mut Transform, (With<Text2d>, With<AnimateScale>)>,
198) {
199    // Consider changing font-size instead of scaling the transform. Scaling a Text2D will scale the
200    // rendered quad, resulting in a pixellated look.
201    for mut transform in &mut query {
202        let scale = (ops::sin(time.elapsed_secs()) + 1.1) * 2.0;
203        transform.scale.x = scale;
204        transform.scale.y = scale;
205    }
206}