Polymorphism for decisions in a dialogue editor

To give some context, I am creating a custom editor window to create and edit node-based dialogue files that can then be interpreted during run time, using Unity’s ScriptableObject class. It assigns each node an ID which is the same as its index in a list of nodes. Each node contains information on what will be said and what the possible responses are, as well as which nodes, defined by their IDs, each possible response links to.

The current issue I have is that, at certain points, there will be decisions that have to be made based on what choices the player has previously made in the game, how far through the game they are etc. as to where the conversation will go, and sometimes I will need to modify variable values based on how the player responds to certain things. However, I am having trouble implementing this sort of functionality. I thought about using a load of classes that derive from a base abstract class, but I ran into numerous problems with this - I didn’t know how I would provide some kind of ability to assign it within the custom editor, and I’m not entirely sure how to create the necessary asset files in that case.
Is there any way that I could implement some kind of decision-making functionality in this way? I’m not sure if I’ve included all of the relevant information, but I’ll provide any more as necessary to the best of my ability.

I’m interested in understanding what is going on and feel like I might be slightly out of my depth in what I’m trying to do, so if any solutions could be explained that would be great.

Thanks.

Well, it all highly depends on how your system is actually used at runtime. Pure dialog-trees are completely static data that is simply used at runtime. However you want now an information flow in the opposite direction. So your static dialog data should reference runtime state. This is in general a bit tricky.

The easiest way would probably be introducing “variables” / parameteres just like the Mecanim system does. It uses [this class][1] to represent a parameter which is created inside the mecanim system.

The more variable types you want to support the more complicated / complex things might get. In theory a float would be enough to represent fractional number, whole numbers and booleans.

The link between your dialog nodes and the variables should be made by a string. Indices are dangerous as when you remove an element from the beginning the indices after would be wrong. You also need a name for each variable to actually “set” the value from scripting.

For example something like that:

[System.Serializable]
public class DynVariable
{
    public string name;
    public float initialValue;
    private float currentValue;
    public float Value
    {
        get { return currentValue; }
        set { currentValue = value; }
    }
}

public class YourDialogMasterScript : ScriptableObject
{
    public List<DynVariable> variables;
    private Dictionary<string, DynVariable> varLookup = null;
    public void SetVariable(string aName, float aValue)
    {
        if (varLookup == null)
            InitVariables();
        DynVariable tmp;
        if (varLookup.TryGetValue(aName, out tmp))
        {
            tmp.Value = aValue;
            return;
        }
        Debug.LogWarning("SetVariable: variable with name '" + aName + "' doesn't exist");
    }
    public float GetVariable(string aName)
    {
        if (varLookup == null)
            InitVariables();
        DynVariable tmp;
        if (varLookup.TryGetValue(aName, out tmp))
        {
            return tmp.Value;
        }
        Debug.LogWarning("GetVariable: variable with name '"+aName+"' doesn't exist");
        return 0f;
    }
    void InitVariables()
    {
        varLookup = new Dictionary<string, DynVariable>();
        for (int i = 0; i < variables.Count; i++)
        {
            varLookup.Add(variables_.name, variables*);*_

variables_.Value = variables*.initialValue;
}*_

}
void OnEnable()
{
if (varLookup == null)
InitVariables();
}
}

public enum EComparison
{
Equal,
NotEqual,
Greater,
GreaterOrEqual,
Lower,
LowerOrEqual,
}

[System.Serializable]
public class DynVarCondition
{
public string variableName;
public EComparison comparison;
public float refValue;
public bool roundToInt = false;
public bool ConditionMet(YourDialogMasterScript aMaster)
{
float v = aMaster.GetVariable(variableName);
if (roundToInt)
v = Mathf.RoundToInt(v);
switch(comparison)
{
case EComparison.NotEqual: return v != refValue;
case EComparison.Greater: return v > refValue;
case EComparison.GreaterOrEqual: return v >= refValue;
case EComparison.Lower: return v < refValue;
case EComparison.LowerOrEqual: return v <= refValue;
case EComparison.Equal:
default: return v == refValue;
}
}
}

public class YourDialogNodeTransition
{
public List conditions;
public bool ConditionsMet(YourDialogMasterScript aMaster)
{
for (int i = 0; i < conditions.Count; i++)
if (!conditions*.ConditionMet(aMaster))*
return false;
return true;
}
}
DynVariable represents an actual “variable”. They are defined in your dialog master script or any other global script. From scripting you can use the GetVariable and SetVariable methods to read and write the variable at runtime.
Inside your dialog node transitions or other place where you need an external condition you simply add a list of “DynVarConditions”. There the user can specify a variable name, a reference value and how they should be compared. At runtime when you would use the “ConditionsMet” method to evaluate if the conditions are currently met or not.
Note that all conditions need to be met. So it’s an implicit “and” connection between them. If you want an optional “or” it gets way more complicated as you would need a true evaluation tree. The [WarCraft3 map editor][2] uses such a node based evaluation tree, but that’s far more than a dialog editor but a whole visual scripting solution. You have to decide how much you want to solve visually and hom much you want to solve via scripting. Some things are way easier to solve with a simple C# script.
As for the usability you could create some custom inspectors / editorwindows for editing those variables / conditions so the user can’t create duplicates (with the same name) and can only select existing variables from a list instead of typing in the variable name. But that’s not necessary.
How you actually implement that in your current system is up to you ^^.
*[1]: https://docs.unity3d.com/ScriptReference/AnimatorControllerParameter.html*_
*[2]: Theef's Warcraft 3 World Editor Tutorial #14 - Waves (and Timers) - YouTube