I feel like the system for the Conditions with the additions of Disjunction and Conjuction is …a bit over-engineered.
It is flexible but to actually use it in the Unity interface is just a chore. The Unity GUI is not built to handle such incapsulation.
And I feel like 95 % of the cases where I want to manipulate the dialogues by conditions, can be handle by one condition or two max. Beyond that, it is make more sens (for me) to give a new Dialogue, or another Quest to the NPC.
And to write the predicate type, and the name of the quests, or the items each time, it is not scallable for a full game. Every one make typos, and it will be very easy to make numerous one with so many copy pasting / writing.
So I make three changes.
- The “condition” field on DIalogueNode become a List of Condition instead of a single one.
- In Condition predicate become a Enum instead of a string. It gain its one file, so we can use it in others classes.
- In Condition parameters become … an array of ScriptableObject. In case of QuestList what are we trying to do with the parameters ? To obtain a Quest by its name. Same for Inventory with InventoryItem.
And both Quest and InventoryItem are ScriptableObject. So we don’t have to write the name, we directly search them !
namespace RPG.Core
{
public enum PredicateType
{
HasQuest,
CompletedQuest,
ObjectiveCompleted,
HasInventoryItem,
}
}
public class Condition
{
[SerializeField] PredicateType predicate;
[NonReorderable] [SerializeField] ScriptableObject[] parameters;
}
namespace RPG.Core
{
public interface IPredicateEvaluator
{
bool? Evaluate(PredicateType predicate, ScriptableObject[] parameters);
}
}
public class DialogueNode : ScriptableObject
{
[NonReorderable] [SerializeField] private List<Condition> conditions = new List<Condition>();
// Will return true if ALL the conditions are fulfilled.
public bool CheckCondition(IEnumerable<IPredicateEvaluator> evaluators)
{
foreach (Condition condition in conditions)
{
bool result = condition.Check(evaluators);
if (!result) return false;
}
return true;
}
}
PlayerConversant
private IEnumerable<DialogueNode> FilterOnCondition(IEnumerable<DialogueNode> inputNode)
{
foreach (var node in inputNode)
{
if (node.CheckCondition(GetEvaluators()))
{
yield return node;
}
}
}
Inventory
public bool? Evaluate(PredicateType predicate, ScriptableObject[] parameters)
{
InventoryItem item = parameters[0] as InventoryItem;
if (item == null) return null;
switch (predicate)
{
case PredicateType.HasInventoryItem:
return HasItem(item);
}
return null;
}
It is a less flexible system, but it feels much more conveniant to use.
I have lost the possibility (for now) to use the predicate “ObjectiveCompleted” but I think it is doable to get it back.
Probably by having three parameters in Evaluate. Something like this maybe
bool? Evaluate(PredicateType predicate, ScriptableObject object, string[] parameters);
public class Condition
{
[SerializeField] PredicateType predicate;
[NonReorderable] [SerializeField] ScriptableObject object;
[NonReorderable] [SerializeField] string[] parameters;
}