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::FontAtlasSets};
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_sets: Res<FontAtlasSets>,
42    images: Res<Assets<Image>>,
43) {
44    if let Some(set) = font_atlas_sets.get(&state.handle)
45        && let Some((_size, font_atlases)) = set.iter().next()
46    {
47        let x_offset = state.atlas_count as f32;
48        if state.atlas_count == font_atlases.len() as u32 {
49            return;
50        }
51        let font_atlas = &font_atlases[state.atlas_count as usize];
52        let image = images.get(&font_atlas.texture).unwrap();
53        state.atlas_count += 1;
54        commands.spawn((
55            ImageNode::new(font_atlas.texture.clone()),
56            Node {
57                position_type: PositionType::Absolute,
58                top: Val::ZERO,
59                left: px(image.width() as f32 * x_offset),
60                ..default()
61            },
62        ));
63    }
64}
65
66fn text_update_system(
67    mut state: ResMut<State>,
68    time: Res<Time>,
69    mut query: Query<&mut Text>,
70    mut seeded_rng: ResMut<SeededRng>,
71) {
72    if !state.timer.tick(time.delta()).just_finished() {
73        return;
74    }
75
76    for mut text in &mut query {
77        let c = seeded_rng.random::<u8>() as char;
78        let string = &mut **text;
79        if !string.contains(c) {
80            string.push(c);
81        }
82    }
83}
84
85fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut state: ResMut<State>) {
86    let font_handle = asset_server.load("fonts/FiraSans-Bold.ttf");
87    state.handle = font_handle.clone();
88    commands.spawn(Camera2d);
89    commands
90        .spawn((
91            Node {
92                position_type: PositionType::Absolute,
93                bottom: Val::ZERO,
94                ..default()
95            },
96            BackgroundColor(Color::NONE),
97        ))
98        .with_children(|parent| {
99            parent.spawn((
100                Text::new("a"),
101                TextFont {
102                    font: font_handle,
103                    font_size: 50.0,
104                    ..default()
105                },
106                TextColor(YELLOW.into()),
107            ));
108        });
109    // We're seeding the PRNG here to make this example deterministic for testing purposes.
110    // This isn't strictly required in practical use unless you need your app to be deterministic.
111    commands.insert_resource(SeededRng(ChaCha8Rng::seed_from_u64(19878367467713)));
112}