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
#fallbackisn’t shown, and it’s picked if time runs out. - A default option. The option tagged
#defaultis 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.
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
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-> Option 1? #default
Alice: option 1 was selected
Alice: this is the default.
-> Option 2?
Alice: option 2 was selectedThe 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
===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);
}SetSelectedOptionsToAutoComplete() sets a flag, and the Presenter clears it once the options are done.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;
}Metadata.HiddenFallback is "fallback". An option that the player can’t pick, because its condition failed, doesn’t count.VisibleDefault is "default".<<auto_opt>> ran before these options.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);
}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);
}
}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.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 Viewand change Auto Select Duration from 5 to a larger number. The bar shrinks more slowly. - Remove the
<<auto_opt>>line fromLastHighlightExampleinTimeouts.yarn. Those options then have no countdown. - Add
#defaulttoOption 2?inDefaultExample, so two options have the tag. The Presenter logs an error and doesn’t show those options.