Bevy Text
Text recently went through a major overhaul in Bevy 0.15
.
Text can either be rendered in two ways:
- As part of the UI with a
Text
component - As part of your games world with a
Text2d
component
What should you choose? Depends on your use case:
Use case | Decision | Reason |
---|---|---|
Floating combat text | Text2d | This text would be relative to our entities |
Health meter | Text | We want this in a fixed position regardless of our viewport |
Title screen | Text OR Text2d | Would depend on the effect you are going for |
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.
Creating text
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 Text2d
.
If instead we wanted our text positioned in relation to our window (like a HUD, or an inventory screen) then we should use Text
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.
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");
commands.spawn((
Text2d::new("Here is some text in the scene"),
TextColor(Color::WHITE),
TextFont::from_font(font.clone()).with_font_size(60.),
TextLayout::new_with_justify(JustifyText::Center)
));
}
This text will be positioned just like your other components and rendered normally in the scene.
Normally Text2d
is providing defaults for the TextColor
, TextFont
and TextLayout
. By spawning our own modified versions of these components we are overriding the defaults.
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((
Node {
position_type: PositionType::Absolute,
bottom: Val::Px(5.0),
right: Val::Px(5.0),
..default()
},
Text::new("Here is some text. But "),
TextFont::from_font(regular_font_handle.clone()),
children![(
TextSpan::new("this part is bold"),
TextFont::from_font(bold_font_handle.clone()),
)],
));
}
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
.
Styling text
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.
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.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.target()).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!".
Font rendering
Bevy will 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 TextFont
. 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.