Basic Saliency
Four characters stand in a room, and what each of them says depends on the day and the time of day. Several pieces of content can often run at once, and Yarn Spinner picks one. Alice, Barry and Liz each have a node group, and George has a line group.
When you run it, the sign at the back of the room says “It is Monday morning.” A switch on the floor in front of the sign moves time on. Walk into it and the sign changes to Monday evening, then Tuesday morning, and so on. Talk to the characters before and after, and they say different things.
Running it
Open the BasicSaliencyDemo scene in the Basic Saliency sample’s folder and press Play.
| Key | What it does |
|---|---|
| W, A, S, D or the arrow keys | Move |
| Space | Talk to the character you’re next to |
| Space, or the button in the dialogue box | Go to the next line |
Walk into the orange switch at the back of the room to move time on. Each time, the time goes from morning to evening, or from evening to the next morning. After Wednesday evening, it goes back to Monday morning.
Capsley, the blue character with the coffee cup, explains the sample. Capsley’s node has when: once, so after you’ve talked to Capsley once, the speaking indicator doesn’t appear over Capsley again.
How it works
The day and the time are two Yarn variables, $day and $time. Every node in the characters’ node groups has when: headers that check them. Each time you talk to a character, the Dialogue Runner works out which of that character’s nodes can run, and its saliency strategy picks one. The sample leaves the Dialogue Runner’s Saliency Strategy at the default, Random Best Least Recently Viewed. See Saliency and Storylets for how the strategies pick.
The day and the time
The Yarn Script declares two enums, and a variable for each:
<<enum Day>>
<<case Monday>>
<<case Tuesday>>
<<case Wednesday>>
<<endenum>>
<<enum TimeOfDay>>
<<case Morning>>
<<case Evening>>
<<endenum>>
<<declare $day = Day.Monday>>
<<declare $time = TimeOfDay.Morning>>Nothing runs the Setup node. Declarations apply to the whole Yarn Project, so it never has to run.
In a condition, .Monday is short for Day.Monday. Yarn Spinner works out the enum from the variable it’s compared with.
Node groups
Alice has a node group with eight nodes. Some depend on the day, and some on the day and the time. On Wednesday evening, two of them can run:
title: Alice
when: $day == .Wednesday
when: $time == .Evening
when: once
---
Player: Hello
Alice: the week is half over, what a relief.
===
title: Alice
when: $day == .Wednesday
when: $time == .Evening
---
Player: Hello
Alice: Probably should go to bed, it is Wednesday evening after all.
===once, so this node’s complexity is 3. It can only run once.once, so this node’s complexity is 2.The default strategy first looks for the candidates seen the fewest times, then picks the one with the highest complexity. The first time you talk to Alice on a Wednesday evening, neither node has been seen, so the once node wins. After that it stops passing, and Alice says the other line.
The other characters show other ways of writing node groups:
- Barry has three nodes for the morning and three for the evening, each with one condition. The default strategy picks the one seen the fewest times, so Barry works through all three before he repeats one.
- Liz’s nodes don’t depend on the day or the time. Three of them have
when: always, and one haswhen: onceandwhen: always. Theoncenode has the highest complexity, so it’s the first thing Liz says. - Alice has two nodes for Tuesday evening and two for Wednesday morning, with the same conditions. When nodes tie like this, she takes turns between them.
Each character’s Dialogue Interactable starts the node group with the character’s name, such as Alice. Before showing the speaking indicator, it asks the Dialogue Runner’s saliency strategy whether anything in the group can run. See Saliency and Storylets.
A line group
George’s node is a single node with a line group in it. Each line starts with =>, and some have a condition:
title: George
---
=> George: oh hi
=> George: another Monday, I hate Mondays <<if $day == .Monday>>
=> George: I suppose I should make breakfast <<if $time == .Morning>>
=> George: Weird how tomorrow is Monday, right? <<if $day == .Wednesday && $time == .Evening>>
Player: did you want the devs to have to write out even more sample content?
George: it would be nice, yes
Player: well they aren'that gonna do that
George: boo
=> George: I wonder if I am in any other samples
===The Dialogue Runner picks one line from the group with the same strategy as the node groups. The lines with no condition can always be picked. The Wednesday evening line has two conditions joined with &&, so it has the highest complexity, and the first time you talk to George on a Wednesday evening, he says it. The indented lines under it only run when that line is picked.
Changing the time from C#
The Yarn Project has Generate Variables Source File turned on, with Variables Class Name set to BasicSaliencyVariableStorage and Variables Parent Class set to InMemoryVariableStorage. Yarn Spinner writes BasicSaliencyVariableStorage.cs from the variables the scripts declare, with a C# enum for each Yarn enum, and a property for each variable. See Generating a typed variables class.
public enum Day {
Monday = 0,
Tuesday = 1,
Wednesday = 2,
}
public partial class BasicSaliencyVariableStorage : Yarn.Unity.InMemoryVariableStorage, Yarn.Unity.IGeneratedVariableStorage {
public Day Day {
get => this.GetEnumValueOrDefault<Day>("$day");
set => this.SetValue("$day", value.GetBackingValue());
}$day as a Day. Reading it gets the variable, and setting it stores the new value.A Basic Saliency Variable Storage component is on the Dialogue System, and the Dialogue Runner’s Variable Storage is set to it. Game code can then read and change the day and the time without knowing the variable names.
The switch is a GameObject called Time Advancer, with a Box Collider set to Is Trigger, and a Time Advancer component. Its Variable Store is the Basic Saliency Variable Storage, and its Label is the sign’s text. When something walks into it, it moves time on through the Variable Storage, and updates the sign:
private void AdvanceTime()
{
if (variableStore == null)
{
Debug.Log($"Can't advance time: {nameof(variableStore)} is not set");
return;
}
if (variableStore.Time == TimeOfDay.Morning)
{
variableStore.Time = TimeOfDay.Evening;
return;
}
variableStore.Time = TimeOfDay.Morning;
switch (variableStore.Day)
{
case Day.Monday:
variableStore.Day = Day.Tuesday;
break;
case Day.Tuesday:
variableStore.Day = Day.Wednesday;
break;
case Day.Wednesday:
variableStore.Day = Day.Monday;
break;
}
}OnTriggerEnter() calls AdvanceTime(), then UpdateLabel(), which writes “It is Tuesday morning.” or similar on the sign.
The next time you talk to someone, the when: headers are checked against the new values. For more on custom Variable Storage, see Variables and Storage.
Things to try
- Select the Dialogue System, and on its Dialogue Runner, set Saliency Strategy to Best. Run the scene and talk to Barry a few times. The Best strategy doesn’t count how many times each node has been seen, so Barry says the same line every time.
- Add another node to Barry’s node group with two conditions, such as
when: $time == .Morningandwhen: $day == .Tuesday. It has a higher complexity than his other morning nodes, so it’s the first thing he says on a Tuesday morning. - Add a line to George’s line group with
<<if $time == .Evening>>at the end, and talk to him in the evening.