Improving Conditions: A Property Editor

Brian, you rock!! Many thanks!!! <3

Hmm… my predicates are having their negate drawn ontop them… looks like this

image

even if i use the original get property height without the changes and strip everything back to just have the negate and the predicate , i still get that view of it.?? i.e. even with the vanilla getpropertyheight value it throws that view. Here is the code without any fancy changes to get property height.

Blockquote 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”);

        EditorGUI.PropertyField(position, predicate);

        // float propHeight = EditorGUI.GetPropertyHeight(predicate);
        float propHeight = EditorGUI.GetPropertyHeight(predicate);
        
        position.height = propHeight;
        
        position.x += propHeight;
        EditorGUI.PropertyField(position, negate);

Why does your code use position.x+= prop height? I switched it to .y and it works now but now i can’t selected the negate box, it keeps targetting the enum predicate field…

My bad on the .x, it was on the code I was walking through step by step (typed by hand into the editor), but it was always supposed to be .y, which it was in my final code. Sorry about that.

Make sure that you are changing the height of position before you draw the predicate:

float propHeight = EditorGUI.GetPropertyHeight(predicate);
position.height = propHeight;
EditorGUI.PropertyField(position, predicate);
position.y+=propHeight;
EditorGUI.PropertyField(negate, predicate);
1 Like

Thank you brian. That last part was not particularly clear. Appreciate it. This fixed my issue! :slight_smile: <3

Part 4: Disjunction Junction, What’s your Function
We’ve got our Predicate property drawer in place, now it’s time to work with the Disjunction property drawer. At this point, you could actually skip the rest of this tutorial and have a fully operational inspector, relying on Unity’s built in array/list handling methods to manage the rest of the Condition. This is fine, but I’m sure some of you have noticed that sometimes Unity’s List and Array handling can be a bit buggy. This next lesson will give us a chance to implement a custom inspector for handling our predicates, and learn the best practices for crawling through a list or array of unknown size in a PropertyDrawer…

We’re going to start by creating, in the same folder as the PredicatePropertyDrawer, a new cs file, DisjuncitonPropertyDrawer.cs. Do the same sort of setup you did with the PredicatePropertyDrawer

using UnityEngine;
using UnityEditor;

namespace GameDevTV.Utils.Editor;
{
     [CustomPropertyDrawer(typeof(Condition.Disjunction))]
     public class DisjunctionPropertyDrawer : PropertyDrawer
     {
           public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
           {
                 
           }
     }
}

We’re going to start out by getting the size of our GetPropertyHeight. We’ll need this sooner than later, since predicates take several lines each to draw. For this, we’ll override GetPropertyHeight()

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
      float result=0;
      float propHeight = EditorGUIUtility.singleLineHeight;

      return result+propHeight;
}

Before I go much further, in the previous section, PropertyDrawer, I used EditorGUI.GetPropertyHeight() on the Epredicate property to get our property height. I confess to forgetting that this number can more easily be obtained with EditorGUIUtility.singleLineHeight. This will give you the standard size of one line. I had to recall this property for the disjuction editor because our editor only contains one property, an array of Predicates, and this value can be variable, depending on the predicate.
I’ve set the method up with my sandwitch of initializing a result and returning that result. The rest of what we’ll be doing is between the statements.

IEnumerator enumerator = property.FindPropertyRelative("or").GetEnumerator();

Since or is a Predicate[], we’re going to need to iterate over the array. One might be tempted to use a for loop, but I ran into difficulties when the array had zero elements. This actually affords us an opportunity to iterate over the array in another way. It happens that you can get an enumerator from a serialized property and iterate over the values. Bear in mind that the feature set of this enumerator is a bit restricted compared to an IEnumerable, array or a list.

while(enumerator.MoveNext())
{
    SerializedProperty p = enumerator.Current as SerializedProperty;
    result+=EditorGUI.GetPropertyHeight(p) +propHeight;
}

MoveNext() looks in the container to see if there is another item. If there is, it sets the Current pointer to that item. We take that enumerator.Current (which is an object) and convert it to a SerializedProperty.
Next, we add the GetPropertyHeight() of the property and add it to result, + one line for the buttons that will allow us to move the element up/down the array or remove it altogether.
You might be wondering, at this point, where does EditorGUI.GetPropertyHeight() get the height for the property? Quite simple, since the property is a Predicate, it asks the PredicatePropertyDrawer.GetPropertyHeight(). Since we’re passing in the specific property, the propertydrawer is able to calculate the correct height. Once there are no more elements in the array, MoveNext() will return false and the while loop will exit

So now GetPropertyHeight looks like this:

        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            float result = 0;
            float propHeight = EditorGUIUtility.singleLineHeight;
            IEnumerator enumerator = property.FindPropertyRelative("or").GetEnumerator();
            while (enumerator.MoveNext())
            {
                SerializedProperty p = enumerator.Current as SerializedProperty;
                result += EditorGUI.GetPropertyHeight(p)+propHeight;
            }
            return result+propHeight;
        }

That last propHeight will be for our Add Predicate button.

With our GetPropertyHeight set up, it’s time to start drawing the property:

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            SerializedProperty or = property.FindPropertyRelative("or");
            float propHeight = EditorGUIUtility.singleLineHeight;
           
            var enumerator = or.GetEnumerator();
            while (enumerator.MoveNext())
            {
                SerializedProperty p = enumerator.Current as SerializedProperty;
                position.height = EditorGUI.GetPropertyHeight(p);
                EditorGUI.PropertyField(position, p);
                position.y += position.height;
                position.height = propHeight;
            }
        }

At this point, we’re getting the standard line size, and we’re getting the enumerator we used in the GetPropertyHeight method. This time, we’re going to set the position’s height to the size of the property and draw each element using the EditorGUI.PropertyField() method. In this case, PropertyField will find the property drawer we wrote for the Predicate and draw it, just like as if it was a built in component. By now you should be starting to see just how powerful property drawers can be.
Here’s our new inspector:


Of course, we have issues… there’s a lot of extra space, since we added that space in when we did the calculations for GetPropertyHeight, and there is no way to add or remove elements. Plus, if you look at the last element, you’ll see it’s two Predicates smashed together. That’s not terribly readable.

Let’s start by adding functionality to add a record
at the end of the OnGUI method, add this:

            if (GUI.Button(position, "Add Predicate"))
            {
                or.InsertArrayElementAtIndex(idx);
            }

This draws a button at the current position, and if the button is pressed, then it inserts an array element. Of course, we haven’t defined idx. Before the while loop, add this line:

     int idx=0;

and at the end of the while loop add this line:

    int idx++;

Now you should have an Add Predicate button after each set of or
image
But we still want to be able to reorder and remove the items…
We’re going to need some extra rects and integer variables to make this happen…
Let’s start with the rects… all of this should come before var enumerator = or.GetEnumerator()

            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;
            int itemToRemove = -1;
            int itemToMoveUp = -1;
            int itemToMoveDown = -1;

There’s a lot going on here. In short, we’re creating three rects, one for the up arrow, one for the down arrow, and one for the delete button. We start by copying the position rect. We deduct the label spacing from the new rect, and divide the width by 1/3rd. We then set the height to be a single line height. Then we clone this rect and move it over next to the first rect, and clone it again moving it to the far right edge of the property. Finally, we create three ints as placeholders for if any of the buttons were pressed.
Now just before idx++; in our while loop, add these lines of code, which will give us our remove and up buttons:

                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;

The first line sets the y position of our buttons. We’re employing a C# trick to assign all three values to position.y.
Next, we draw a button, setting itemToRemove to idx if this button is clicked.
if idx is > 0 We draw another button, setting itemToMoveUp is clicked.
We can’t draw our down button yet, though, because we don’t know if there are more elements until we get into the next iteration in the while loop. We’ll get to that later.
Finally, we need two more if statements, outside of the while loop, before our button draw for the Add Predicate button.

            if (itemToRemove >= 0)
            {
                or.DeleteArrayElementAtIndex(itemToRemove);
            }

            if (itemToMoveUp >= 0)
            {
                or.MoveArrayElement(itemToMoveUp, itemToMoveUp - 1);
            }

Basically, these check to see if we’ve cached an index for deleting or moving. If we’re deleting, we just DeleteArrayElementAtIndex(itemToRemove). If we’re moving up, then we MoveArrayElement from the itemToMoveUp index to the itemToMoveUp index-1. Remember that if(idx>0 && statement before drawing the move up button? This ensures that the button is only drawn on the [1] or greater element, so we never have to worry about trying to move 0 to 0-1.
We now have remove and up (^) buttons on each predicate. The ^ button only exists where there is more than one or clause in the disjunction, like the bottom most element.


Still, we need two more tidbits to finish this thing off… we need a move down button on OR elements with more than one predicate, and we need something to visually remind us in these cases that these are OR conditions… We’ll manage this at the top of the While loop:

                if (idx > 0)
                {
                    if (GUI.Button(downPosition, "v")) itemToMoveDown = idx - 1;
                    EditorGUI.DropShadowLabel(position, "-------OR-------", style);
                    position.y += propHeight;
                }

There’s a reason this is at the beginning of the while loop. We don’t know if we need the OR separator or a Move down button until we move to the next element. If the idx is zero, then we don’t need to draw an OR or a down button. If we do, we draw the down button for the previous element and then an OR.
You’ll notice I’ve included a style on that label. We’ll define that at the beginning of the ONGui method:

            GUIStyle style = new GUIStyle(EditorStyles.label);
            style.alignment = TextAnchor.MiddleCenter;

This creates a new style, copying from the normal label style settings in EditorStyles. We then set the alignment to the center so the ------OR------- will be centered. To use the style, just insert it as the last parameter of a Label or DropShadowLabel.
One more thing to make the down button work, after our if statements for the remove and up buttons:

            if (itemToMoveDown >= 0)
            {
                or.MoveArrayElement(itemToMoveDown, itemToMoveDown + 1);
            }

And now our inspector should look like this:


You should be able to add predicates to an or, remove them, and move them up and down the chain with the ^ and v buttons.

In the next lesson, we’ll round this out by drawing the condition property drawer, which will give us an improved handling of the array of disjunctions,

Complete DisjunctionPropertyDr

Complete DisjunctionPropertyDrawer.cs

using System.Collections;
using UnityEngine;
using UnityEditor;

namespace GameDevTV.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;
        }
    }
}
8 Likes

I want you to know, I’ve been singing that song all day.

Also adding nonsense “hooking up predicates and supporting designers”

This section I’m definitely struggling to understand the individual lines more then previous, but my designer is significantly happier!

1 Like

Thank you so much for these! The heavy reliance on strings and the messy Inspector in the default system were stressing me out, they just seemed so designer-unfriendly. It’s so much better now!

I know it’s been a while, but I’d love to see how you handled the Condition/Conjunction property drawer, to see the whole thing finished off :slight_smile:

1 Like

@Brian_Trotter, you are AWESOME man!
I have been looking to improve on the condition and IPredicateEvaluator topic and had begun drafting code (rather hard to do in terms of editor code) untill I found this very post of yours… now I am in love with it.
Besides the great utility it provides, as well as the opportunity to learn about property drawers, dictionaries etc… it is a welcome alternative to video tutorials.

Keep up the good work and thanks again!

1 Like

First of all thank you for this awesome tutorial :slight_smile: :+1:
I was think about QuestCompletition.cs, maybe we could use the same approach to set the objective, or you think that would be excessive?

Also, I take this opportunity to ask about QuestCompletion in inspector it shows Quest as array
image

but if I click on the triangle I get this error:

ArgumentException: Getting control 1's position in a group with only 1 controls when doing repaint
Aborting

Thank you for your infinite patience again :sweat_smile:

Yes, a similar approach can be taken in QuestCompletion… in fact, here’s my Editor for this:

QuestCompletionEditor.cs
using System.Collections.Generic;
using UnityEditor;

namespace RPG.Quests.Editer
{
    [CustomEditor(typeof(QuestCompletion))]
    public class QuestCompletionEditor:Editor
    {
        private SerializedProperty quest;
        private SerializedProperty objective;

        private void OnEnable()
        {
            quest = serializedObject.FindProperty("quest");
            objective = serializedObject.FindProperty("objective");
        }

        public override void OnInspectorGUI()
        {
            EditorGUILayout.PropertyField(quest);
            if (quest.objectReferenceValue == null)
            {
                EditorGUILayout.HelpBox("Please select a Quest to continue.", MessageType.Info);
                return;
            }
            EditorGUILayout.PrefixLabel("Objective");
            List<string> references = new List<string>();
            List<string> descriptions= new List<string>();
            Quest selectedQuest = (Quest)quest.objectReferenceValue;
            {
                foreach (Quest.Objective questObjective in selectedQuest.GetObjectives())
                {
                    references.Add(questObjective.reference);
                    descriptions.Add(questObjective.description);
                }
            }
            int selectedIndex = references.IndexOf(objective.stringValue);
            int testIndex = EditorGUILayout.Popup(selectedIndex, descriptions.ToArray());
            if (testIndex != selectedIndex)
            {
                objective.stringValue = references[testIndex];
            }
            serializedObject.ApplyModifiedProperties();
        }
    }
}

I’m not sure why your inspector is showing that… hopefully the editor nixes that in the bud. If not, there is an issue in your QuestCompletion class.

2 Likes

Now that I’ve implemented the editor, the Quest object is working too :slight_smile:

Thanks again :sweat_smile:

I’m actually experiencing a bug with the Trait Predicate after making the transition from ETrait → EStat.

Here’s what is getting spat out now when attempting to pickup an Item with condition MinimumTrait

FormatException: Input string was not in a correct format.
System.Number.StringToNumber (System.String str, System.Globalization.NumberStyles options, System.Number+NumberBuffer& number, System.Globalization.NumberFormatInfo info, System.Boolean parseDecimal) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Number.ParseInt32 (System.String s, System.Globalization.NumberStyles style, System.Globalization.NumberFormatInfo info) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Int32.Parse (System.String s) (at <695d1cc93cca45069c528c15c9fdd749>:0)
Game.Attributes.StatStore.Evaluate (Game.Utils.EPredicate predicate, System.Collections.Generic.List`1[T] parameters) (at Assets/Scripts/Attributes/StatStore.cs:139)
Game.Utils.Condition+Predicate.Check (System.Collections.Generic.IEnumerable`1[T] evaluators) (at Assets/Scripts/Quests/Condition.cs:56)
Game.Utils.Condition+Disjunction.Check (System.Collections.Generic.IEnumerable`1[T] evaluators) (at Assets/Scripts/Quests/Condition.cs:35)
Game.Utils.Condition.Check (System.Collections.Generic.IEnumerable`1[T] evaluators) (at Assets/Scripts/Quests/Condition.cs:17)
Game.Inventories.EquipableItem.CanEquip (Game.Inventories.EquipLocation equipLocation, Game.Inventories.Equipment equipment) (at Assets/Scripts/Inventory/Items/EquipableItem.cs:39)
Game.Inventories.Inventory.AddToFirstEmptySlot (Game.Inventories.InventoryItem item, System.Int32 number) (at Assets/Scripts/Inventory/Equipment/Inventory.cs:164)
Game.Inventories.Pickup.PickupItem () (at Assets/Scripts/Inventory/Items/Pickup.cs:48)
Game.Control.PickupItem.OnTriggerEnter2D (UnityEngine.Collider2D other) (at Assets/Scripts/Inventory/Items/PickupItem.cs:13)

Double-clicking on the error inside Unity brings me to the TraitStore (I renamed to StatStore) evaluate code where it’s checking against the parameters[0]

        public bool? Evaluate(EPredicate predicate, List<string> parameters)
        {
            if (predicate == EPredicate.MinimumTrait)
            {
                if (Enum.TryParse<EStat>(parameters[0], out EStat traitStat))
                {
                    return GetPoints(traitStat) >= Int32.Parse(parameters[0]);
                }
            }
            return null;
        }

If Wisdom is set to ‘5’ and my Wisdom is 5 or greater it still throws this error. – end of original post

Found the bug but not sure how to fix it yet. – edited post

After some digging,

I enabled Debug inside my Inspector and noticed some Items have parameters[0] == "" – But then some items using the exact same predicate MinimumTrait will have parameters[0] == "Strength"

It appears the ones that are causing that string format error have EStat listed under the parameters[0] == "Strength", "Wisdom" etc.

And on the other hand the items that are blank can be equipped as if no condition was set.

But just to ensure that other Predicates were working, I tried the simplest one – HasLevel and that works fine. If I’m level 1, and the condition requires the player to be level 2, I can pick the item up no errors and it goes to my bag, attempt to equip, rejects it as intended.

Level up – can now slot it. So it just seems the MinimumTrait isn’t working as it once was

Oh I think I’m suppose to Int32.Parse from parameters[1] not from the parameters[0]. D’Oh!

Now it’s no longer producing the string formatted error inside Unity but the Predicate MinimumTrait negate isn’t functioning as intended.

If I set it to be false and don’t meet the required integer of Wisdom I cannot equip the Item, but can collect the item. However if my Wisdom is now equal or higher, it still won’t allow me to equip it. What a pain in the butt.

WORKED IT OUT!!

  1. Was attempting to Parse from a string. It was suppose to be Parsing from the DrawIntSlider.
  2. On this post I was using the ‘GetPoints(stat)’ but I should’ve been factoring in that baseStats needed to also be calculated. Therefore the fix was to change from GetPoints(stat) to GetTraitStat(stat) which returns the points invested as well as the baseStats integer.

The issue was that with GetPoints(stat) it wasn’t factoring in that I was starting off with 5 points into Wisdom. Therefore technically it was comparing 0 to 6 which was making it hard for me to debug. But once I did this Debug.Log

if (Enum.TryParse<EStat>(parameters[0], out EStat stat))
{
   Debug.Log($"Para[0]:{parameters[0]}/Pts:{GetTraitStat(stat)} | Para[1] Points:{parameters[1]}\nIsPara[0] >= Para[1]  ? {GetTraitStat(stat) >= Int32.Parse(parameters[1])}");
   return GetTraitStat(stat) >= Int32.Parse(parameters[1]);
}

It gave me all the information I needed. Case closed!

Good job working through all that. I’m impressed by how you reasoned through the issue, debugged, and found a solution!

1 Like

Hey! I’m trying to follow along with this, and it’s helpful. However, I keep getting this error and I’m not sure at all why. The relevant error lines are

NullReferenceException: Object reference not set to an instance of an object
…(file names and memory addresses)
Assets/Scripts/Core/Evaluator/Editor/PredicatePropertyDrawer.cs:24)

on line 24, I simply have
float propHeight = EditorGUI.GetPropertyHeight(predicate);

from what I can tell, I’m defining the class and the property correctly.

namespace RPG.Core.Evaluator.Editor
{
    [CustomPropertyDrawer(typeof(Condition.Predicate))]
    public class PredicatePropertyDrawer : PropertyDrawer
    {
        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");

            EditorGUI.PropertyField(position, predicate);

But for whatever reason, every single time I try to reference the predicate attribute it can’t “see” it at runtime. Like, the script will compile and won’t show any errors, but then I get errors at any lines like this where I’m trying to get/use the reference to the Predicate. It’s also not correctly drawing the Condition, and will either just draw the area below it as blank, or as “Null”

I’m probably missing something obvious, but does anyone have any advice?

Most likely, the declaration of the variable is different than the string in FindPropertyRelative
For example:

[SerializeField] EPredicate predication;

would cause the predicate in OnGUI to be null because property.FindPropertyRelative(“predicate”) would fail.

I double checked and I had the name right. Just to experiment, and kind of taking an educated guess, I tried changing the PropertyDrawer to just being typeof(Condition) and now suddenly everything is being drawn correctly. Maybe I just did something weird with how I made Predicate a subclass?

Anyways, as long as it’s all functioning, can you foresee this potentially causing any issue or do you think it’s fine?

Thank you again, as always, for all of your help. You’re seriously awesome Brian.

As long as its’ working, I’d call it a win!

This is awesome.

One recommendation if you intend on the Enum to be constantly changed is to use Scriptable Objects instead of Enums. I do not know how many times I have added an Element to my Enum or removed an Element from an Enum and spent hours trying to hunt down everything that was using that Enum and make sure that they are all set to the correct value.

I have actually done that with Stats, CharacterClasses, and Equiplocations in the past… While it solves one problem (easily adding enums), it created a few extra problems… Not terrible problems, but things like the Health component needing to know which ScriptableStat was the Health stat…

Similarly, you would need to attach the correct ScriptablePredicate(s) to each IPredicateEvaluator class so it would know what things it was testing for…

Something like

[SerializeField] ScriptablePredicate hasQuest;
[SerializeField] ScriptablePredicate completedQuest;
[SerializeField] ScriptablePredicate completedObjective;

public bool? Evaluate(ScriptablePredicate predicate, string[] parameters)
{
     switch (predicate)
            {
                case hasQuest: 
                    return HasQuest(Quest.GetByName(parameters[0]));
                case completedQuest:
                    QuestStatus status = GetQuestStatus(Quest.GetByName(parameters[0]));
                    if (status == null) return false;
                    return status.IsComplete();
                case completedObjective:
                    QuestStatus teststatus = GetQuestStatus(Quest.GetByName(parameters[0]));
                    if (teststatus==null) return false;
                    return teststatus.IsObjectiveComplete(parameters[1]);
            }
            return null;
}

Privacy & Terms