Skip to main content

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