Brian, you are a champ! I had trouble with the predicates that I wasn’t able to solve. Think it got messed up somewhere when mixing ANDs and ORs, but using the interface you created solved it all. Now everything is as it should be. Thank you so much for saving me without even trying!
Hi there ! how can i fix the code alert → typeof(Condition.Predicate) Predicate “member” is inaccessible due to its protection level
is starting in [CustomPropertyDrawer(typeof(Condition.Predicate))]
i made in condition.cs the [System.Serializable] class Predicate
into a public class Predicate so this alert disappears
Check to see if you made the above change. It is easy to miss (I did).
Hi Brian, i changed to enums and made all changes for EPredicates.
In condition.cs all is public, i have no complaint in my console
but the Disjunction no appears in editor.

Have you gotten to the Editor code portion of the tutorial?
Paste in your Condition, and if applicable your versions of the Editor scripts.
THX for your response Brian!
condition.cs , IPredicateEvaluator.cs are in Utils.
Editior folder is a subfolder of Utils.
So far so good but the condition appears like referenced screenshot!
IPredicateEvaluator.cs
namespace RPG.Utils
{
public enum EPredicate
{
Select,// - no handlers will be handling Select, it’s a placeholder to remind us to choose a predicate
HasItem, // Inventory needs the ID of the item
HasQuest, // QuestList needs the name of the quest
HasLevel, // BaseStats needs to know the level we’re shooting for
HasItems, // Inventory needs the ID of the item and the required quantity
MinimumTrait, // TraitStore needs the trait and the minimum level of the trait
CountedScalps, // enemy killings from thread
CompletedQuest, // QuestList needs the name of the quest
HasItemEquipped,
HasInventoryItems,
CompletedObjective // QuestList needs the name of the quest and the identifier of the objective
}
public interface IPredicateEvaluator
{
bool? Evaluate(EPredicate predicate, string[] parameters);
}
}
my condition.cs
using System.Collections.Generic;
namespace RPG.Utils
{
[System.Serializable]
public class Condition
{
public Disjunction[] and;
public bool Check(IEnumerable<IPredicateEvaluator> evaluators)
{
foreach (Disjunction dis in and)
{
if (!dis.Check(evaluators))
{
return false;
}
}
return true;
}
public class Disjunction
{
public Predicate[] or;
public bool Check(IEnumerable<IPredicateEvaluator> evaluators)
{
foreach (Predicate pred in or)
{
if (pred.Check(evaluators))
{
return true;
}
}
return false;
}
}
public class Predicate
{
public EPredicate predicate;
public string[] parameters;
public bool negate = false;
public bool Check(IEnumerable<IPredicateEvaluator> evaluators)
{
foreach (var evaluator in evaluators)
{
bool? result = evaluator.Evaluate(predicate, parameters);
if (result == null)
{
continue;
}
if (result == negate)
{
return false;
}
}
return true;
}
}
}
}
EDITOR folder : 2 files
PredicatePropertyDrawer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using RPG.Inventories;
using RPG.Quests;
using RPG.Stats;
using UnityEditor;
using UnityEngine;
namespace RPG.Utils.Editor
{
[CustomPropertyDrawer(typeof(Condition.Predicate))]
public class PredicatePropertyDrawer : PropertyDrawer
{
private static void DrawIntSlider(Rect position, string caption, SerializedProperty intParameter,
int minLevel = 1, int maxLevel = 100)
{
EditorGUI.BeginProperty(position, new GUIContent(caption), intParameter);
if (!int.TryParse(intParameter.stringValue, out int value))
{
value = 1;
}
EditorGUI.BeginChangeCheck();
int result = EditorGUI.IntSlider(position, caption, value, minLevel, maxLevel);
if (EditorGUI.EndChangeCheck())
{
intParameter.stringValue = $"{result}";
}
EditorGUI.EndProperty();
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
SerializedProperty predicate = property.FindPropertyRelative("predicate");
SerializedProperty parameters = property.FindPropertyRelative("parameters");
SerializedProperty negate = property.FindPropertyRelative("negate");
float propHeight = EditorGUI.GetPropertyHeight(predicate);
position.height = propHeight;
EditorGUI.PropertyField(position, predicate);
EPredicate selectedPredicate = (EPredicate)predicate.enumValueIndex;
if (selectedPredicate == EPredicate.Select) return; //Stop drawing if there's no predicate
while (parameters.arraySize < 2)
{
parameters.InsertArrayElementAtIndex(0);
}
SerializedProperty parameterZero = parameters.GetArrayElementAtIndex(0);
SerializedProperty parameterOne = parameters.GetArrayElementAtIndex(1);
if (selectedPredicate == EPredicate.HasQuest || selectedPredicate == EPredicate.CompletedQuest || selectedPredicate == EPredicate.CompletedObjective)
{
position.y += propHeight;
DrawQuest(position, parameterZero);
}
if (selectedPredicate == EPredicate.CompletedObjective)
{
position.y += propHeight;
DrawObjective(position, parameterOne, parameterZero);
}
if (selectedPredicate == EPredicate.HasItem || selectedPredicate == EPredicate.HasItems || selectedPredicate == EPredicate.CountedScalps || selectedPredicate == EPredicate.HasItemEquipped)
{
position.y += propHeight;
DrawInventoryItemList(position, parameterZero, selectedPredicate == EPredicate.HasItems, selectedPredicate == EPredicate.HasItemEquipped);
}
if (selectedPredicate == EPredicate.HasItems)
{
position.y += propHeight;
DrawIntSlider(position, "Quantity Needed", parameterOne);
}
if (selectedPredicate == EPredicate.CountedScalps)
{
position.y += propHeight;
DrawIntSlider(position, "Quantity Needed", parameterOne);
}
if (selectedPredicate == EPredicate.HasLevel)
{
position.y += propHeight;
DrawIntSlider(position, "Level Required", parameterZero);
}
if (selectedPredicate == EPredicate.MinimumTrait)
{
position.y += propHeight;
DrawTrait(position, parameterZero);
position.y += propHeight;
DrawIntSlider(position, "Minimum", parameterOne);
}
position.y += propHeight;
EditorGUI.PropertyField(position, negate);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
SerializedProperty predicate = property.FindPropertyRelative("predicate");
float propHeight = EditorGUIUtility.singleLineHeight;
EPredicate selectedPredicate = (EPredicate)predicate.enumValueIndex;
switch (selectedPredicate)
{
case EPredicate.Select: //No parameters, we only want the bare enum.
return propHeight;
case EPredicate.HasLevel: //All of these take 1 parameter
case EPredicate.CompletedQuest:
case EPredicate.HasQuest:
case EPredicate.HasItem:
case EPredicate.HasItemEquipped:
return propHeight * 3.0f; //Predicate + one parameter + negate
case EPredicate.CompletedObjective: //All of these take 2 parameters
case EPredicate.HasItems:
case EPredicate.MinimumTrait:
return propHeight * 4.0f; //Predicate + 2 parameters + negate;
}
return propHeight * 2.0f;
}
private Dictionary<string, Quest> quests;
void BuildQuestList()
{
if (quests != null) return;
quests = new Dictionary<string, Quest>();
foreach (Quest quest in Resources.LoadAll<Quest>(""))
{
quests[quest.name] = quest;
}
}
private void DrawQuest(Rect position, SerializedProperty element)
{
BuildQuestList();
var names = quests.Keys.ToList();
int index = names.IndexOf(element.stringValue);
EditorGUI.BeginProperty(position, new GUIContent("Quest:"), element);
int newIndex = EditorGUI.Popup(position, "Quest:", index, names.ToArray());
if (newIndex != index)
{
element.stringValue = names[newIndex];
}
EditorGUI.EndProperty();
}
private void DrawObjective(Rect position, SerializedProperty element, SerializedProperty selectedQuest)
{
string questName = selectedQuest.stringValue;
if (!quests.ContainsKey(questName))
{
EditorGUI.HelpBox(position, "Please Select A Quest", MessageType.Warning);
return;
}
List<string> references = new List<string>();
List<string> descriptions = new List<string>();
foreach (Quest.Objective objective in quests[questName].GetObjectives())
{
references.Add(objective.reference);
descriptions.Add(objective.description);
}
int index = references.IndexOf(element.stringValue);
EditorGUI.BeginProperty(position, new GUIContent("objective"), element);
int newIndex = EditorGUI.Popup(position, "Objective:", index, descriptions.ToArray());
if (newIndex != index)
{
element.stringValue = references[newIndex];
}
EditorGUI.EndProperty();
}
private Dictionary<string, InventoryItem> items;
private void BuildInventoryItemsList()
{
if (items != null) return;
items = new Dictionary<string, InventoryItem>();
foreach (InventoryItem item in Resources.LoadAll<InventoryItem>(""))
{
items[item.GetItemID()] = item;
}
}
private void DrawInventoryItemList(Rect position, SerializedProperty element, bool stackable = false, bool equipment = false)
{
BuildInventoryItemsList();
List<string> ids = items.Keys.ToList();
if (stackable) ids = ids.Where(s => items[s].IsStackable()).ToList();
if (equipment) ids = ids.Where(s => items[s] is EquipableItem e).ToList();
List<string> displayNames = new List<string>();
foreach (string id in ids)
{
displayNames.Add(items[id].GetDisplayName());
}
int index = ids.IndexOf(element.stringValue);
EditorGUI.BeginProperty(position, new GUIContent("items"), element);
int newIndex = EditorGUI.Popup(position, "Item", index, displayNames.ToArray());
if (newIndex != index)
{
element.stringValue = ids[newIndex];
}
}
private void DrawTrait(Rect position, SerializedProperty element)
{
if (!Enum.TryParse(element.stringValue, out Trait trait))
{
trait = Trait.Strength;
}
EditorGUI.BeginProperty(position, new GUIContent("Trait"), element);
Trait newTrait = (Trait)EditorGUI.EnumPopup(position, "Trait:", trait);
if (newTrait != trait)
{
element.stringValue = $"{newTrait}";
}
EditorGUI.EndProperty();
}
}
}
DisjunctionPropertyDrawer.cs
using System.Collections;
using UnityEditor;
using UnityEngine;
namespace RPG.Utils.Editor
{
[CustomPropertyDrawer(typeof(Condition.Disjunction))]
public class DisjunctionPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GUIStyle style = new GUIStyle(EditorStyles.label);
style.alignment = TextAnchor.MiddleCenter;
SerializedProperty or = property.FindPropertyRelative("or");
float propHeight = EditorGUIUtility.singleLineHeight;
Rect upPosition = position;
upPosition.width -= EditorGUIUtility.labelWidth;
upPosition.x = position.xMax - upPosition.width;
upPosition.width /= 3.0f;
upPosition.height = propHeight;
Rect downPosition = upPosition;
downPosition.x += upPosition.width;
Rect deletePosition = upPosition;
deletePosition.x = position.xMax - deletePosition.width;
var enumerator = or.GetEnumerator();
int idx = 0;
int itemToRemove = -1;
int itemToMoveUp = -1;
int itemToMoveDown = -1;
while (enumerator.MoveNext())
{
if (idx > 0)
{
if (GUI.Button(downPosition, "v")) itemToMoveDown = idx - 1;
EditorGUI.DropShadowLabel(position, "-------OR-------", style);
position.y += propHeight;
}
SerializedProperty p = enumerator.Current as SerializedProperty;
position.height = EditorGUI.GetPropertyHeight(p);
EditorGUI.PropertyField(position, p);
position.y += position.height;
position.height = propHeight;
upPosition.y = deletePosition.y = downPosition.y = position.y;
if (GUI.Button(deletePosition, "Remove")) itemToRemove = idx;
if (idx > 0 && GUI.Button(upPosition, "^")) itemToMoveUp = idx;
position.y+=propHeight;
idx++;
}
if (itemToRemove >= 0)
{
or.DeleteArrayElementAtIndex(itemToRemove);
}
if (itemToMoveUp >= 0)
{
or.MoveArrayElement(itemToMoveUp, itemToMoveUp - 1);
}
if (itemToMoveDown >= 0)
{
or.MoveArrayElement(itemToMoveDown, itemToMoveDown + 1);
}
if (GUI.Button(position, "Add Predicate"))
{
or.InsertArrayElementAtIndex(idx);
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
float result = 0;
float propHeight = EditorGUIUtility.singleLineHeight;
IEnumerator enumerator = property.FindPropertyRelative("or").GetEnumerator();
bool multiple = false;
while (enumerator.MoveNext())
{
SerializedProperty p = enumerator.Current as SerializedProperty;
result += EditorGUI.GetPropertyHeight(p) + propHeight;
if (multiple) result += propHeight;
multiple = true;
}
return result + propHeight * 1.5f;
}
}
}
Hi Brian !
It seems fixed !
My conditon.cs missed using System and all classes the [Serializable] setting.
and now it looks like this …
Hey there I know this post is a bit old, but I followed through and updated everything and it is all working fine. My only problem is when I’m using the Dialogue Editor and I select a node that I have added a predicate to, Unity will pause with a loading bar for several seconds, making it very hard to work with.
If I go into the DialogueNode.cs and add a [HideInInspector] tag on the Condition, it solves the problem, but then of course I can not set anything… but at least that narrows it down a bit that it is something to do with the Condition class.
Any help on this would be greatly appreciated
I tracked the slow down to when a Predicate was selected, not just added. So if I chose a predicate type other than ‘Select’ then Unity would reload all the resources to build the lists. Here is my refactored BuildQuestList and BuildInventoryItemsList methods, where I have removed 'Resources.LoadAll and it seems to have made the pause go away:
private void BuildQuestList()
{
if (quests != null) return;
quests = AssetDatabase.FindAssets("t:Quest")
.Select(guid => AssetDatabase.LoadAssetAtPath<Quest>(AssetDatabase.GUIDToAssetPath(guid)))
.ToDictionary(quest => quest.name, quest => quest);
}
private void BuildInventoryItemsList()
{
if (items != null) return;
items = AssetDatabase.FindAssets("t:InventoryItem")
.Select(guid => AssetDatabase.LoadAssetAtPath<InventoryItem>(AssetDatabase.GUIDToAssetPath(guid)))
.ToDictionary(item => item.GetItemID(), item => item);
}
The pause is in exactly where you found, loading the assets. The more assets in your project, the longer these searches will take. It’s not highly optimized. One optimization I made in the past was to restrict the search to the Game folder, which means it won’t consider every asset in the game, just the ones in the Game folder which is a lot quicker.

