Bevy Text
Text recently went through a major overhaul in Bevy 0.15
.
Text can either be rendered through the UI or as part of your games world.
This functionality is provided through the bevy_text
crate. It adds a plugin that provides text resources, systems, and font assets to your game. It also adds a system to your render app which helps find sprites during your renders extract loop.
The core responsibility of bevy_text
is to take our Text
components and turn them into a series of positioned graphemes for the renderer. A grapheme refers to the smallest unit of a writing system that carries meaning.
When you add a Text
component Bevy will use a GlyphBrush
to position the letters of text and cache those positions so that on another render pass we don't update them unless we need to.
Graphemes are then rendered according to a font. A font is essentially a bunch of sprites contained inside a group of TextureAtlas
called a FontAtlas
which helps optimize text rendering.
These sprites are then scaled and transformed to be positioned on your screen with a proper size when they are rendered by your GPU.
When you add a font to your FontAtlas
it uses a DynamicTextureAtlasBuilder
to load at runtime only the exact glyph sprites it needs to display the text on your screen.
The default font provided by Bevy is Fira Mono and the supported font types are ttf
and otf
. You load fonts using a FontLoader
that works similarly to your other assets.
The text component
Text is either rendered through the UI with a Text
component, or inside your game world with a Text2d
component.
If we wanted our text to interact with or be part of our game world then we should choose Text
.
If instead we wanted our text positioned in relation to our window (like a HUD, or an inventory screen) then we should use Text2d
and make it part of our UI.
By placing one of these components onto an entity, other systems in bevy_text
and the core renderer will spring into action.
To style your text there are 3 main components we can add to Text
entities:
TextLayout
TextFont
TextColor
Each one has fields for changing the style of your text. By adding them to a component that has a Text
or Text2d
component you are overriding the defaults for that particular node.
Creating text
We can render the text as part of the scene by assembling the various components on a regular entity:
fn spawn_in_scene(mut commands: Commands, assets: Res<AssetServer>) {
let font = assets.load("fonts/FiraSans-Bold.ttf");
let text_color = TextColor(Color::WHITE);
let text_font = TextFont {
font: font.clone(),
font_size: 60.0,
..default()
};
let text_alignment = JustifyText::Center;
let text = Text2d::new("This is some text in the scene");
commands.spawn((
text,
text_color,
text_font
));
}
This text will be positioned just like your other components and rendered normally in the scene.
Lets say we have some text with two styles we want to keep together like:
Here is some text. But this part is bold
This is ultimately a single text span, but each part needs a separate style. We can represent this by creating two text components with the first as a parent to the second with a TextSpan
component on the child:
fn spawn_in_ui(mut commands: Commands, assets: Res<AssetServer>) {
let regular_font_handle: Handle<Font> = Default::default();
let bold_font_handle: Handle<Font> = assets.load("fonts/Roboto-Bold.ttf");
commands
.spawn((
Text::new("Here is some text. But "),
TextFont {
font: regular_font_handle.clone(),
..default()
},
Node {
position_type: PositionType::Absolute,
bottom: Val::Px(5.0),
right: Val::Px(5.0),
..default()
},
))
.with_child((
TextSpan::default(),
Text::new("this part is bold"),
TextFont {
font: bold_font_handle.clone(),
..default()
},
));
}
This will allow Bevy's text layout engine to place these in the proper position next to each other and according to the rules of our Node
.
Changing text
We can change the value of text rendered to the screen at anytime through the Text
component we spawned.
For example, if we wanted to create an inventory screen where players could hover over items and we show them the name:
fn handle_hovered_item(
mut text_query: Query<&mut Text, With<ItemName>>,
item_query: Query<&Item, Added<Selected>>,
) {
if let Ok(item_data) = item_query.get_single() {
for mut text in text_query.iter_mut() {
text.0 = item_data.name.clone();
}
}
}
It does not matter if your Text
was added to the UI through a node or just as part of your scene, we still manipulate the components on the entities that spawn the exact same way.
Clicking on text
To click text we can use Bevy's built in picking triggers using observers.
First we create a handler for the observed event:
fn handle_click(
trigger: Trigger<Pointer<Click>>,
mut query: Query<&mut Text2d>
) {
let mut text = query.get_mut(trigger.entity()).unwrap();
text.0 = "You clicked me!".to_string();
}
Then we can choose the entities we want to observe:
fn spawn_clickable_text(mut commands: Commands) {
commands
.spawn(Text2d::new("This is some text in the scene"))
.observe(handle_click);
}
Now when you click the text it changes to "You clicked me!".