Custom Saliency Strategies
When the dialogue reaches a node group or a line group, the Dialogue Runner asks its saliency strategy to pick one candidate. Saliency and Storylets covers the strategies that come with Yarn Spinner. If none of them pick the way you want, you can write your own.
What a strategy does
A saliency strategy is a class that implements IContentSaliencyStrategy, from the Yarn.Saliency namespace. It has two methods:
QueryBestContent(content)is given every candidate, including the ones that don’t pass, and returns the one to run. It returnsnullif nothing should run.ContentWasSelected(content)is called after a candidate has been picked, before it runs.
sequenceDiagram participant Runner as Dialogue Runner participant Strategy as Your strategy Runner->>Strategy: QueryBestContent(content) Strategy-->>Runner: the candidate to run, or null Runner->>Strategy: ContentWasSelected(content) Note over Runner: The picked candidate runs
Each candidate is a ContentSaliencyOption, with the properties described in Looking at a node group from C#. QueryBestContent() must return one of the candidates it was given, or null. If it returns anything else, Yarn Spinner throws a DialogueException.
The strategy isn’t given anything else. If it needs more, such as the Variable Storage or the node headers, pass them in when you create it. dialogueRunner.Dialogue.GetHeaderValue(nodeName, headerName) reads a node’s header, and dialogueRunner.YarnProject.lineMetadata.GetMetadata(lineID) reads a line’s tags.
An example
This strategy picks at random, but never picks the same candidate twice in a row:
using System.Collections.Generic;
using System.Linq;
using Yarn.Saliency;
public class NoRepeatSaliencyStrategy : IContentSaliencyStrategy
{
string lastPicked;
public ContentSaliencyOption QueryBestContent(IEnumerable<ContentSaliencyOption> content)
{
var valid = content.Where(c => c.FailingConditionValueCount == 0).ToList();
if (valid.Count == 0)
{
return null;
}
var choices = valid.Where(c => c.ContentID != lastPicked).ToList();
if (choices.Count == 0)
{
choices = valid;
}
return choices[UnityEngine.Random.Range(0, choices.Count)];
}
public void ContentWasSelected(ContentSaliencyOption content)
{
lastPicked = content.ContentID;
}
}MonoBehaviour.null to run nothing.Code that only checks whether anything can run, such as has_any_content() and HasSalientContent(), calls QueryBestContent() too, and nothing runs afterwards. Don’t change any state in QueryBestContent(). Make changes in ContentWasSelected(), which is only called when a candidate is about to run.
If your strategy uses view counts, like the built-in Least Recently Viewed strategies, keep them in the Variable Storage so they’re saved with your other variables. Each candidate’s ViewCountKey is a variable name you can use for its count. Read it with TryGetValue<float>() in QueryBestContent(), and add 1 to it with SetValue() in ContentWasSelected().
The same strategy is used for node groups and line groups. Use ContentType if it needs to treat them differently.
Using your strategy
In the Inspector, set the Dialogue Runner’s Saliency Strategy to Custom.
In code, give the Dialogue Runner your strategy by setting Dialogue.ContentSaliencyStrategy:
void Start()
{
dialogueRunner.Dialogue.ContentSaliencyStrategy = new NoRepeatSaliencyStrategy();
}Set it before the dialogue reaches a node group or a line group. Setting it before StartDialogue() is called is the simplest way to be sure.
StartDialogue() is called, the Dialogue Runner replaces the strategy with the one chosen in Saliency Strategy, unless it’s set to Custom. If you set your own strategy while Saliency Strategy is still Random Best Least Recently Viewed, it’s thrown away as soon as the dialogue starts.If Saliency Strategy is Custom but you never set a strategy, the Dialogue Runner uses Random Best Least Recently Viewed.
Another example
The Custom Saliency sample has a strategy that picks at random, but makes some candidates more likely than others. Each node gets a weight header, and each line in a line group gets a #weight tag. The strategy is a MonoBehaviour that reads node headers with Dialogue.GetHeaderValue(), and line tags from the Yarn Project’s lineMetadata.