Typewriter Text Effect
A common effect in games is to have text progressively appear letter-by-letter as if it were being typed by a human. If all you need is a single line of text, you can achieve this with a simple system that adds letters to a Text
entity. However, this naive approach has an issue when the text occupies multiple lines. When the "typewriter" runs out of room on a line of text while typing a word, the layout gets adjusted. This causes the entire word to abruptly move to the next line.
We could get around this by using LineBreak::AnyCharacter
, but splitting words over multiple lines makes text awkward to read.
A better approach is to lay out the entire contents of the text immediately, but with Color::NONE
. We can then progressively make each character visible.
Bevy 0.15
#[derive(Component)]
struct Typewriter(Timer);
impl Typewriter {
fn new(delay: f32) -> Self {
Self(Timer::from_seconds(delay, TimerMode::Repeating))
}
}
fn update_typewriters(
time: Res<Time>,
mut query: Query<(Entity, &mut Typewriter), With<Typewriter>>,
mut writer: TextUiWriter,
) {
for (entity, mut typewriter) in query.iter_mut() {
if !typewriter.0.tick(time.delta()).just_finished() {
return;
}
while !writer.text(entity, 1).is_empty() {
let first_hidden = writer.text(entity, 1).remove(0);
writer.text(entity, 0).push(first_hidden);
if first_hidden != ' ' {
break;
}
}
}
}
fn setup(mut commands: Commands) {
let container = commands
.spawn(Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
})
.id();
let bg = commands
.spawn((
Node {
width: Val::Px(680.0),
height: Val::Px(300.0),
padding: UiRect::all(Val::Px(10.)),
..default()
},
BorderRadius::all(Val::Px(10.)),
BackgroundColor(Srgba::gray(0.2).into()),
))
.id();
let typewriter = commands
.spawn((
Typewriter::new(0.1),
Text::default(),
Node {
width: Val::Percent(100.),
..default()
},
))
.with_child((
TextSpan::new(TEXT),
TextColor(Color::NONE),
))
.id();
commands.entity(container).add_child(bg);
commands.entity(bg).add_child(typewriter);
}