font_atlas_debug/
font_atlas_debug.rs

1//! This example illustrates how `FontAtlas`'s are populated.
2//! Bevy uses `FontAtlas`'s under the hood to optimize text rendering.
3
4use bevy::{color::palettes::basic::YELLOW, prelude::*, text::FontAtlasSet};
5use rand::{Rng, SeedableRng};
6use rand_chacha::ChaCha8Rng;
7
8fn main() {
9    App::new()
10        .init_resource::<State>()
11        .insert_resource(ClearColor(Color::BLACK))
12        .add_plugins(DefaultPlugins)
13        .add_systems(Startup, setup)
14        .add_systems(Update, (text_update_system, atlas_render_system))
15        .run();
16}
17
18#[derive(Resource)]
19struct State {
20    atlas_count: u32,
21    handle: Handle<Font>,
22    timer: Timer,
23}
24
25impl Default for State {
26    fn default() -> Self {
27        Self {
28            atlas_count: 0,
29            handle: Handle::default(),
30            timer: Timer::from_seconds(0.05, TimerMode::Repeating),
31        }
32    }
33}
34
35#[derive(Resource, Deref, DerefMut)]
36struct SeededRng(ChaCha8Rng);
37
38fn atlas_render_system(
39    mut commands: Commands,
40    mut state: ResMut<State>,
41    font_atlas_set: Res<FontAtlasSet>,
42    images: Res<Assets<Image>>,
43) {
44    if let Some(font_atlases) = font_atlas_set.values().next() {
45        let x_offset = state.atlas_count as f32;
46        if state.atlas_count == font_atlases.len() as u32 {
47            return;
48        }
49        let font_atlas = &font_atlases[state.atlas_count as usize];
50        let image = images.get(&font_atlas.texture).unwrap();
51        state.atlas_count += 1;
52        commands.spawn((
53            ImageNode::new(font_atlas.texture.clone()),
54            Node {
55                position_type: PositionType::Absolute,
56                top: Val::ZERO,
57                left: px(image.width() as f32 * x_offset),
58                ..default()
59            },
60        ));
61    }
62}
63
64fn text_update_system(
65    mut state: ResMut<State>,
66    time: Res<Time>,
67    mut query: Query<&mut Text>,
68    mut seeded_rng: ResMut<SeededRng>,
69) {
70    if !state.timer.tick(time.delta()).just_finished() {
71        return;
72    }
73
74    for mut text in &mut query {
75        let c = seeded_rng.random::<u8>() as char;
76        let string = &mut **text;
77        if !string.contains(c) {
78            string.push(c);
79        }
80    }
81}
82
83fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut state: ResMut<State>) {
84    let font_handle = asset_server.load("fonts/FiraSans-Bold.ttf");
85    state.handle = font_handle.clone();
86    commands.spawn(Camera2d);
87    commands
88        .spawn((
89            Node {
90                position_type: PositionType::Absolute,
91                bottom: Val::ZERO,
92                ..default()
93            },
94            BackgroundColor(Color::NONE),
95        ))
96        .with_children(|parent| {
97            parent.spawn((
98                Text::new("a"),
99                TextFont {
100                    font: font_handle,
101                    font_size: 50.0,
102                    ..default()
103                },
104                TextColor(YELLOW.into()),
105            ));
106        });
107    // We're seeding the PRNG here to make this example deterministic for testing purposes.
108    // This isn't strictly required in practical use unless you need your app to be deterministic.
109    commands.insert_resource(SeededRng(ChaCha8Rng::seed_from_u64(19878367467713)));
110}