Dropdown

Hey there,

I am kinda getting frustrated about those string actions in dialogues and quests. I always mistype them when using QuestCompletion or DialogueTrigger class.

If you have the same OCD as I do… I found a DropDown free public attribute. Just add some [ExecuteInEditMode] and additional methods for Dialogue and Quest to get all the actions: Dropdown Attribute | Utilities Tools | Unity Asset Store

I hope I helped somebody relieve their disorder levels :smiley:

4 Likes

Looks handy.
In a few weeks (depending on how fast I can get the Saving System Conversion to JSon tutorial written), I’ll be writing a tutorial to make a Custom Property Drawer for Conditions that

  • Uses Enums for the Predicates
  • Selects the correct type of ScriptableObject and builds a list depending on the Predicate
  • Sets the correct number of parameters in the inspector depending on the predicate… for example a Predicate might be HasTrait | Toughness | 3 so the first drop down will be the HasTrait, the second Drop Down the list of Traits, and the 3rd drop down a list of integers from 1-100
1 Like

Thats actually what Im trying to do :smiley:

So far:

using GameDevTV.Inventories;
using RPG.Core;
using RPG.Quests;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;

namespace RPG.Dialogue
{    
    [CustomPropertyDrawer(typeof(Condition))]    
    public class DialogeNodeInspector : PropertyDrawer
    {
        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            return 100;
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {            
            EditorGUI.BeginProperty(position, label, property);
            EditorGUIUtility.labelWidth = 50;
            position = EditorGUI.PrefixLabel(position, label);

            Debug.Log(position.height);

            // Setup Predicate Type
            Rect predicateRect = new Rect(position.x, position.y + 20, position.width, 20f);

            SerializedProperty predicateName = property.FindPropertyRelative("predicate");
            
            EditorGUI.PropertyField(predicateRect, predicateName, new GUIContent("Type"), false);

            PredicateTypes predicateValue = (PredicateTypes)predicateName.enumValueIndex;
            
            // Setup Predicate Paramaters
            Rect parametersPosition = new Rect(position.x, position.y + 40, position.width, 20f);

            // Setup NonePredicate
            SerializedProperty paramaters = property.FindPropertyRelative("paramaters");
            if (predicateValue == PredicateTypes.None)
            {
                paramaters.ClearArray();
            }

            // Setup HasQuestPredicate, CompletedQuestPredicate
            if (predicateValue == PredicateTypes.HasQuest || predicateValue == PredicateTypes.CompletedQuest)
            {
                var questNames = Resources.LoadAll<Quest>("").ToList();                

                int selectedIndex = 0;
                if (paramaters.arraySize != 0)
                {
                    var firstQuest = paramaters.GetArrayElementAtIndex(0).GetValue() as string;
                    selectedIndex = questNames.Select(quest => quest.name).ToList().IndexOf(firstQuest);
                    selectedIndex = selectedIndex == -1 ? 0 : selectedIndex;    
                }

                var selectedOption = (Quest)EditorGUI.ObjectField(parametersPosition, "Qst" ,questNames[selectedIndex], typeof(Quest), false);
                if (selectedOption == null)
                {
                    return;
                }
                paramaters.SetValue(new List<string>() { selectedOption.name });
            }

            // Set HasItemPredicate
            if (predicateValue == PredicateTypes.HasInventoryItem)
            {
                var items = Resources.LoadAll<InventoryItem>("").ToList();             

                int selectedIndex = 0;
                if (paramaters.arraySize != 0)
                {
                    var firstItem = paramaters.GetArrayElementAtIndex(0).GetValue() as string;
                    selectedIndex = items.Select(item => item.GetItemID()).ToList().IndexOf(firstItem);
                    selectedIndex = selectedIndex == -1 ? 0 : selectedIndex;
                }

                var selectedItem = (InventoryItem)EditorGUI.ObjectField(parametersPosition, "Item", items[selectedIndex], typeof(InventoryItem), false);
                if (selectedItem == null)
                {
                    return;
                }
                paramaters.SetValue(new List<string>() { selectedItem.GetItemID() });              
            }            
            EditorGUI.EndProperty();
        }
    }
}

It is a work in progress :smiley:
I have an error I cant seem to find solution for tho:
‘RPG.Dialogue.ConditionEditor’ is missing the class attribute ‘ExtensionOfNativeClass’!

If you have any suggestion please Im open for any :slight_smile:
PS: I have done the last lecture yet because my OCD with string problems manifested too soon so I dont have Predicates yet :laughing:

1 Like

Usually, this is because you’re trying to instantiate a class that isn’t derived from ScriptableObject or MonoBehavior… but I don’t see any of that behavior in the script you have here, so beyond that, I’m honestly not sure.

For your entertainment pleasure, this is my Condition.cs (which contains both the Condition code and classes AND the code to draw itself (as my DialogueNode has a Custom Editor as well to call the Condition.DrawInspector() using the same techniques I outline in my Inventory Item editor.
One small issue with this setup is that right now it has cross dependency between namespaces to get the names of the dialogues.

using GameDevTV.Inventories;
using RPG.Quests;
using System.Collections.Generic;
using System.Linq;
using RPG.Dialogue;

using UnityEditor;
using UnityEngine;

namespace GameDevTV.Utils
{
    [System.Serializable]
    public class Condition
    {
        [SerializeField] private List<Disjunction> and = new List<Disjunction>();

        public bool Check(IEnumerable<IPredicateEvaluator> evaluators)
        {
            List<IPredicateEvaluator> evalList = evaluators.ToList();
            foreach (Disjunction dis in and)
            {
                if (!dis.Check(evalList))
                {
                    return false;
                }
            }
            Debug.Log("Evaluation of condition is true.");
            return true;
        }

        [System.Serializable]
        public partial class Disjunction
        {
            [SerializeField] private List<Predicate> or = new List<Predicate>();

            public bool Check(IEnumerable<IPredicateEvaluator> evaluators)
            {
                var evalList = evaluators.ToList();
                foreach (Predicate pred in or)
                {
                    if (pred.Check(evalList))
                    {
                        return true;
                    }
                }
                return false;
            }

            #if UNITY_EDITOR
            public List<Condition.Predicate> GetPredicates() => or;
               
                           public void AddPredicate(DialogueNode owner)
                           {
                               Undo.RecordObject(owner, "Add Predicate");
                               or.Add(new Condition.Predicate());
                               EditorUtility.SetDirty(owner);
                           }
               
                           public void RemovePredicate(Predicate predicateToRemove, DialogueNode owner)
                           {
                               Undo.RecordObject(owner, "Remove Predicate");
                               or.Remove(predicateToRemove);
                               EditorUtility.SetDirty(owner);
                           }

#endif

        }

        [System.Serializable]
        public class Predicate
        {
            [SerializeField] private EPredicate predicate;
            [SerializeField] private List<string> parameters = new List<string>();
            [SerializeField] private bool negate = false;

            public bool Check(IEnumerable<IPredicateEvaluator> evaluators)
            {
                Debug.Log($"Testing {predicate} {parameters[0]}");
                foreach (var evaluator in evaluators)
                {
                    bool? result = evaluator.Evaluate(predicate, parameters.ToArray());
                    if (result == null)
                    {
                        continue;
                    }
                    if (result == negate) return false;
                }
                return true;
            }

            #if UNITY_EDITOR
            public void SetEPredicate(EPredicate value, DialogueNode owner)
            {
                if (predicate == value) return;
                Undo.RecordObject(owner, "Change Predicate");
                predicate = value;
                EditorUtility.SetDirty(owner);
            }

            public void SetNegate(bool value, DialogueNode owner)
            {
                if (negate == value) return;
                Undo.RecordObject(owner, "Change Negate");
                negate = value;
                EditorUtility.SetDirty(owner);
            }

            public void AddParameter(DialogueNode owner)
            {
                Undo.RecordObject(owner, "Add Parameter");
                parameters.Add("");
                EditorUtility.SetDirty(owner);
            }

            public void RemoveParameter(int stringToRemove, DialogueNode owner)
            {
                Undo.RecordObject(owner, "Remove Parameter");
                parameters.RemoveAt(stringToRemove);
                EditorUtility.SetDirty(owner);
            }

            public void SetParameter(int index, string value, DialogueNode owner)
            {
                if (parameters[index] == value) return;
                Undo.RecordObject(owner, "Change Parameter");
                parameters[index] = value;
                EditorUtility.SetDirty(owner);
            }
            

            public List<string> GetParameters() => parameters;
            public EPredicate GetEPredicate() => predicate;
            public bool GetNegate() => negate;
#endif
        }
        #if UNITY_EDITOR

        public void RemoveDisjunction(Disjunction disjunctionToRemove, DialogueNode owner)
        {
            Undo.RecordObject(owner, "Remove Disjunction");
            and.Remove(disjunctionToRemove);
            EditorUtility.SetDirty(owner);
        }

        public void AddDisjunction(DialogueNode owner)
        {
            Undo.RecordObject(owner, "Add Disjunction");
            and.Add(new Disjunction());
            EditorUtility.SetDirty(owner);
        }

        public void DrawInspector(DialogueNode selected)
        {
            Disjunction disJunctionToRemove = null;
            foreach (Disjunction disjunction in and)
            {
                EditorGUILayout.Separator();
                Predicate predicateToRemove = null;
                foreach (Predicate predicate in disjunction.GetPredicates())
                {
                    EditorGUILayout.BeginHorizontal();
                    predicate.SetNegate(EditorGUILayout.Toggle("Negate", predicate.GetNegate()), selected);
                    predicate.SetEPredicate((EPredicate)EditorGUILayout.EnumPopup(predicate.GetEPredicate()), selected);
                    if (GUILayout.Button("-")) predicateToRemove = predicate;
                    EditorGUILayout.EndHorizontal();
                    if (predicate.GetEPredicate() != EPredicate.None)
                    {
                        int stringToRemove = -1;
                        var stringList = GetNeededList(predicate.GetEPredicate(),0);
                        var secondParamList = GetNeededList(predicate.GetEPredicate(), 1);
                        for (int i = 0; i < predicate.GetParameters().Count; i++)
                        {
                            int positionInList = PositionInList(predicate.GetParameters()[i], i==0?stringList: secondParamList);
                            Debug.Log($"PositionInList = {positionInList}");
                            if (positionInList < 0) positionInList = 0;
                            EditorGUILayout.BeginHorizontal();
                            if (stringList != null && stringList.Count > 0)
                            {
                                Debug.Log($"{stringList.Count} - {positionInList} ");
                                predicate.SetParameter(i,
                                                       stringList
                                                           [EditorGUILayout.Popup(positionInList,i==0? stringList.ToArray():secondParamList.ToArray())],
                                                       selected);
                            }
                            else
                            {
                                predicate.SetParameter(i, EditorGUILayout.TextField(predicate.GetParameters()[i]),
                                                       selected);
                            }

                            if (GUILayout.Button("-")) stringToRemove = i;
                            EditorGUILayout.EndHorizontal();
                        }

                        if (stringToRemove > -1)
                        {
                            predicate.RemoveParameter(stringToRemove, selected);
                        }

                        if (GUILayout.Button("Add Parameter"))
                        {
                            predicate.AddParameter(selected);
                        }
                    }
                }

                if (predicateToRemove != null)
                {
                    disjunction.RemovePredicate(predicateToRemove, selected);
                }

                if (GUILayout.Button("Add Predicate"))
                {
                    disjunction.AddPredicate(selected);
                }

                if (GUILayout.Button("Remove Disjunction"))
                {
                    disJunctionToRemove = disjunction;
                }
            }

            if (disJunctionToRemove != null)
            {
                RemoveDisjunction(disJunctionToRemove, selected);
            }

            if (GUILayout.Button("Add Disjunction"))
            {
                AddDisjunction(selected);
            }
        }

        private List<string> GetNeededList(EPredicate predicate, int position)
        {
            switch (predicate)
            {
                case EPredicate.HasQuest:
                    return GetObjectNames<Quest>();
                case EPredicate.CompletedQuest:
                     return GetObjectNames<Quest>();
                case EPredicate.HasItem:
                    return GetObjectNames<InventoryItem>();
                case EPredicate.AboveLevel:
                {
                    return NumberList();
                }
                case EPredicate.MinimumTrait:
                {
                    return position==0?TraitList():NumberList();
                }
                    
                    
            }
            return new List<string>();
        }

        List<string> NumberList()
        {
            List<string> result = new List<string>();
            for (int i = 1; i < 100; i++)
            {
                result.Add($"{i}");
            }

            return result;
        }

        List<string> TraitList()
        {
            List<string> result = new List<string>()
                                  {
                                      "Strength",
                                      "Agility",
                                      "Constitution",
                                      "Intelligence",
                                      "Charisma",
                                  };

            return result;
        }
        
        private List<string> GetObjectNames<T>() where T:ScriptableObject
        {
            List<string> result = new List<string>();
            foreach (T obj in Resources.LoadAll<T>(""))
            {
                result.Add(obj.name);
            }
            return result;
        }


        private int PositionInList(string parameter, List<string> listToCheck)
        {
            return listToCheck.IndexOf(parameter);
        }

        #endif


    }
}
1 Like

Do you actually need a custom inspector for DialogueNode?

Wouldnt it be simpler to just keep the basic inspector logic from Unity, but at the same time have a custom PropertyDrawer for just Predicates?

That way you don’t have to mess around with CustomInspector but you keep drawing and setting the Array of Disjunctions on the Unity inspector.

I probably don’t. I think I wrote the DialogueNode inspector when I was working up the InventoryEditor, more of a learning experience… I tend to write a lot of custom inspectors, especially if there’s a List involved as I despise the way Unity’s built in inspector manages arrays and lists.

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.

Privacy & Terms