docs/bevy/examples/ui/text/font_atlas_debug.rs 3.2 K · 111 lines · raw · history

1 //! This example illustrates how `FontAtlas`'s are populated.
2 //! Bevy uses `FontAtlas`'s under the hood to optimize text rendering.
3
4 use bevy::{color::palettes::basic::YELLOW, prelude::*, text::FontAtlasSet};
5
6 use chacha20::ChaCha8Rng;
7 use rand::{RngExt, SeedableRng};
8
9 fn 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)]
20 struct State {
21 atlas_count: u32,
22 handle: Handle<Font>,
23 timer: Timer,
24 }
25
26 impl 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)]
37 struct SeededRng(ChaCha8Rng);
38
39 fn 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
65 fn 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
84 fn 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 }