Options That Timeout

This sample replaces the standard Options Presenter with a custom one, TimeoutOptionsView. When a group of options is set up for it, a bar appears under the options and shrinks. If the player hasn’t picked an option by the time the bar is empty, the Presenter picks one for them.

The sample shows three ways of deciding which option gets picked. When you run it and talk to Alice, she asks which one you’d like to see:

  • A hidden fallback option. The option tagged #fallback isn’t shown, and it’s picked if time runs out.
  • A default option. The option tagged #default is shown like the others, and it’s picked if time runs out.
  • The last highlighted option. The <<auto_opt>> Command turns this on, and whichever option was selected last is picked if time runs out.

Alice’s first set of options has no tags and no Command, so it has no countdown.

PLACEHOLDER — Screenshot needed
The Options That Timeout sample in the Default example: the player and Alice in the arena, with Option 1? and Option 2? at the bottom of the screen and the countdown bar under them, part way through shrinking.

Running it

In the Project window, open the Options That Timeout sample’s folder, open the Timeout scene, and press Play.

  • Move with WASD or the arrow keys.
  • Walk up to Alice and press Space to talk to her.
  • Press Space, or click the continue button, to show the whole line or go to the next one.
  • Click an option to pick it, or move between options with the arrow keys.

Alice’s second line has a link to the custom Presenter. Clicking it opens TimeoutOptionsView.cs, using the Link Markup Handler on the Markup Processors GameObject. Links like this only work in the Unity Editor.

How it works

Timeout
Dialogue System Dialogue RunnerIn Memory Variable Storage
Canvas CanvasCanvas ScalerGraphic Raycaster
Line View Content Size FitterVertical Layout GroupCanvas GroupLine Presenter
Background ImageLayout Element
Character Name TextMeshPro - Text (UI)
Text TextMeshPro - Text (UI)
Button Container Line Presenter Button HandlerLayout Element
Continue Button ImageButton
Options View Vertical Layout GroupContent Size FitterCanvas GroupTimeout Options View
Background ImageLayout Element
timer container Layout Element
timer ImageTimeout Bar
Markup Processors Palette Marker ProcessorStyle Marker ProcessorLink Markup Handler
Line Advancer Line Advancer
EventSystem Event SystemEnsure Input Module Available
Alice Character ControllerSimple CharacterCharacter AppearanceMouth ViewDialogue Interactable
The dialogue part of the Timeout scene. The Dialogue Runner’s Presenters are Line View, Options View and Line Advancer.

Line View uses the standard Line Presenter. Options View uses TimeoutOptionsView.cs, a DialoguePresenterBase that only handles options. Its Auto Select Duration is set to 5 seconds in the scene. To see how a Presenter like this fits together, read Building Your Own Presenter.

Alice’s Dialogue Interactable, from the samples’ shared scripts, runs the Alice node when you talk to her.

Tagging the options

In the Yarn Script, the options in FallbackExample and DefaultExample each have one option with a tag:

-> Option 1?
    Alice: option 1 was selected
-> Option 2?
    Alice: option 2 was selected
-> Option 3? #fallback
    Alice: The hidden fallback option was selected
Lines 1–4 These two options are shown, with the countdown bar under them.
Lines 5–6 This option isn’t shown. If the bar runs out, it’s picked, and Alice says this line.
The options in the FallbackExample node of Timeouts.yarn.
-> Option 1? #default
    Alice: option 1 was selected
    Alice: this is the default.
-> Option 2?
    Alice: option 2 was selected
Lines 1–3 This option is shown like any other. If the bar runs out, it’s picked.
The options in the DefaultExample node of Timeouts.yarn.

The LastHighlightExample node uses a Command instead of a tag:

<<auto_opt>>
-> Option 1?
    Alice: option 1 was selected
-> Option 2?
    Alice: option 2 was selected
===
Line 1 Turns on last highlighted mode for the next group of options.
Lines 2–5 When the bar runs out, whichever of these was selected last is picked.
The end of the LastHighlightExample node of Timeouts.yarn.

The Presenter registers <<auto_opt>> itself, when the scene starts:

protected void Start()
{
    if (canvasGroup != null)
    {
        canvasGroup.alpha = 0;
        canvasGroup.interactable = false;
        canvasGroup.blocksRaycasts = false;
    }

    var runner = FindAnyObjectByType<DialogueRunner>();
    runner.AddCommandHandler("auto_opt", SetSelectedOptionsToAutoComplete);
}
Lines 3–8 Hide the options until there are some to show.
Lines 10–11 Register the Command with the Dialogue Runner. SetSelectedOptionsToAutoComplete() sets a flag, and the Presenter clears it once the options are done.
Start() in TimeoutOptionsView.cs.

See Commands.

Choosing the mode

Each time the Presenter gets a group of options, it works out which mode to use from the options’ tags and the Command flag:

int hasDefault = 0;
TimeOutOptionType defaultOptionType = TimeOutOptionType.None;
DialogueOption? defaultOption = null;

foreach (var option in dialogueOptions)
{
    foreach (var metadata in option.Line.Metadata)
    {
        if (metadata == HiddenFallback)
        {
            if (!option.IsAvailable)
            {
                continue;
            }

            defaultOptionType = TimeOutOptionType.HiddenFallback;
            defaultOption = option;
            hasDefault += 1;

            break;
        }
        else if (metadata == VisibleDefault)
        {
            if (!option.IsAvailable)
            {
                continue;
            }

            defaultOptionType = TimeOutOptionType.VisibleDefault;
            defaultOption = option;
            hasDefault += 1;

            break;
        }
    }
}

if (LastHighlightedOptionIsSelectedAfterDuration)
{
    defaultOptionType = TimeOutOptionType.LastHighlighted;
}
Line 2 With no tags and no Command, the options work as normal, with no countdown.
Line 7 An option’s tags are in its line’s Metadata.
Lines 9–21 HiddenFallback is "fallback". An option that the player can’t pick, because its condition failed, doesn’t count.
Lines 22–34 VisibleDefault is "default".
Lines 38–41 <<auto_opt>> ran before these options.
The start of RunOptionsAsync() in TimeoutOptionsView.cs.

The Presenter then checks that the options make sense. A #default or #fallback group must have exactly one tagged option, and an <<auto_opt>> group must have none. If the check fails, it logs an error and returns without picking an option, so it doesn’t show those options.

It makes each option from its Option View Prefab, which is set to the Option Item prefab that comes with Yarn Spinner. When it sets them up, it leaves out the option tagged #fallback, and any option the player can’t pick. It selects the first option, so last highlighted mode always has an option to pick.

Running the countdown

After the options fade in, the Presenter starts the countdown. timer’s Timeout Bar shrinks the bar from its full width to nothing over Auto Select Duration seconds:

public async YarnTask Shrink(float duration, CancellationToken cancellationToken)
{
    if (bar == null)
    {
        return;
    }

    float accumulator = 0;
    var currentSize = bar.sizeDelta.x;

    while (accumulator < duration && !cancellationToken.IsCancellationRequested)
    {
        accumulator += Time.deltaTime;
        var newSize = Mathf.Lerp(currentSize, 0, accumulator / duration);
        bar.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, newSize);
        await YarnTask.Yield();
    }
    bar.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 0);
}
Shrink() in TimeoutBar.cs.

When the bar is empty, the Presenter picks an option:

internal async YarnTask BeginDefaultSelectTimeout(YarnTaskCompletionSource<DialogueOption?> selectedOptionCompletionSource, DialogueOption? option, CancellationToken cancellationToken)
{
    if (timedBar == null)
    {
        return;
    }

    await timedBar.Shrink(autoSelectDuration, cancellationToken);

    selectedOptionCompletionSource.TrySetResult(option);
}

internal async YarnTask BeginLastSelectedOptionTimeout(YarnTaskCompletionSource<DialogueOption?> selectedOptionCompletionSource, List<OptionItem> options, CancellationToken cancellationToken)
{
    if (timedBar == null)
    {
        return;
    }
    await timedBar.Shrink(autoSelectDuration, cancellationToken);

    bool foundIt = false;
    foreach (var option in options)
    {
        if (option.IsHighlighted)
        {
            foundIt = true;
            selectedOptionCompletionSource.TrySetResult(option.Option);
            break;
        }
    }

    if (!foundIt)
    {
        selectedOptionCompletionSource.TrySetResult(options[0].Option);
    }
}
Lines 1–11 In the default and fallback modes, wait for the bar to empty, then pick the tagged option.
Line 8 Shrink() also returns early when the token is cancelled, which happens when the player picks an option. TrySetResult() then does nothing, because the player’s choice is already set.
Lines 13–36 In last highlighted mode, pick the option that’s selected, or the first option if none is.
The two timeouts in TimeoutOptionsView.cs.

When the player clicks an option before the bar runs out, the Presenter stops the bar, fades the options out, and returns the option to the Dialogue Runner.

Things to try

  • Select Options View and change Auto Select Duration from 5 to a larger number. The bar shrinks more slowly.
  • Remove the <<auto_opt>> line from LastHighlightExample in Timeouts.yarn. Those options then have no countdown.
  • Add #default to Option 2? in DefaultExample, so two options have the tag. The Presenter logs an error and doesn’t show those options.
Next step Replacement Markup Markup that changes a line's text before it's shown.