Manual and Auto Advance
Who decides when the next line comes? Some games wait for a click on every line, others also offer an auto mode.
The key idea: a presented line is not instant, it plays for a while. A line with a sequence plays for as long as its effects take.
Three events are involved. Two are commands your game sends to the dialogue runner:
AdvanceConversationends the current line and moves on.SkipLinefast-forwards the current line without leaving it. FiresLineFinishedat the end.
The third goes the other way, from the runner to your game:
LineFinishedreports that the line has finished playing. It exists so your game knows the line has nothing left and can advance the conversation.
Manual
You can just ignore LineFinished entirely and advance on input:
// on click / key press, while the runner is presenting:
commands.trigger(AdvanceConversation { entity: runner });
This is the shop example.
Manual with fast-forward
What most dialogue-heavy games do: the first press finishes the line, the second moves on. Track whether the line has played out with a marker:
#[derive(Component)]
struct LineDone;
app.add_observer(|line: On<LineFinished>, mut commands: Commands| {
commands.entity(line.entity).insert(LineDone);
});
app.add_observer(|line: On<SubtitleStarted>, mut commands: Commands| {
commands.entity(line.entity).remove::<LineDone>();
});
Then input branches on the marker:
fn on_press(runner: Entity, done: bool, commands: &mut Commands) {
if done {
commands.trigger(AdvanceConversation { entity: runner });
} else {
commands.trigger(SkipLine { entity: runner });
}
}
Press once to jump to the end, press again to continue.
Auto-play
One observer, and the lines pace themselves:
app.add_observer(|line: On<LineFinished>, mut commands: Commands| {
commands.trigger(AdvanceConversation { entity: line.entity });
});
Every line stays up for its reading time, or for as long as its authored effects take, then flows on.
This is the cutscene example. Input still works: SkipLine fires LineFinished, which this observer turns into an advance, so pressing skip in auto mode jumps to the next line.
To give readers more time without touching every entry, stretch the default reading-time clock in SequencerSettings:
settings.default_sequence = "wait(line_end + 0.75)".to_owned();
The toggle
Both modes can be the same two observers with a switch in front:
#[derive(Resource, Default)]
struct AutoPlay(bool);
app.add_observer(
|line: On<LineFinished>, auto: Res<AutoPlay>, mut commands: Commands| {
if auto.0 {
commands.trigger(AdvanceConversation { entity: line.entity });
} else {
commands.entity(line.entity).insert(LineDone);
}
},
);
Input keeps the fast-forward branching from above. One catch when the player turns auto on: the current line may have finished long ago, sitting there waiting for a press that will never come. Catch it up when the flag flips:
fn enable_auto(
mut auto: ResMut<AutoPlay>,
waiting: Query<Entity, With<LineDone>>,
mut commands: Commands,
) {
auto.0 = true;
for runner in &waiting {
commands.trigger(AdvanceConversation { entity: runner });
}
}
Turning auto off needs nothing: the next LineFinished simply inserts the marker and waits.