1
\$\begingroup\$

I want to write custom inspector for one of my scripts. All I want to change is input method for one of the strings to popup (so instead of writing whole string everytime I choose it from premade list of strings, like enums, the list is different for every dialogue). But the thing is it's VERY long inspector with a lot of variables and rewriting everything just for this one input doesn't click for me. Other problem is that this string is nested deep in the serializable object arrays, it look like so

enter image description here

I'm very happy with how default inspector shows all the fields, expect this one string I want to change. I tried playing around with SerializedProperty but no luck.

If it comes to available options I populate public string array with the names in the inspector (every dialogue has different list of participants) so the options should vary between different dialogues. What's the best way to set options?

Is there a way to do it without rewriting whole inpector on your own?

\$\endgroup\$
  • \$\begingroup\$ The default inspector behaviour allows for enums to be displayed, have you tried taking advantage of that in your custom inspector? \$\endgroup\$ – TomTsagk Aug 8 at 11:04
  • 2
    \$\begingroup\$ Have you considered using a PropertyDrawer for an attribute you put only on these string fields? \$\endgroup\$ – DMGregory Aug 8 at 11:05
  • \$\begingroup\$ @TomTsagk the list of strings with names will vary depending on the dialogue, so it has to be dynamic list, I don' think you can edit enums in runtime. DMGregory Haven't heard of it, I might look into it \$\endgroup\$ – Bizio Aug 8 at 11:12
  • 1
    \$\begingroup\$ @Bizio I think this should be included information, as it vastly changes what the solution should be. \$\endgroup\$ – TomTsagk Aug 8 at 11:13
  • \$\begingroup\$ please have a look at the answer I posted on your original post \$\endgroup\$ – derHugo Aug 8 at 11:50
2
\$\begingroup\$

You can use a PropertyDrawer to control how a field is rendered, based on its type or attributes you've attached to it. This lets you target your changes to individual fields, wherever they might show up in a bigger nested set of types, without writing a custom inspector to handle drawing the whole hierarchy.

Here, we could define a [CharacterName] attribute that serves to mark fields we want to treat as character names. We'll also give it a static variable to keep track of which names should be options for the current batch of fields it's drawing.

using UnityEngine;

public class CharacterNameAttribute : PropertyAttribute
{
    public static string[] CurrentOptions;
}

Next, we define a custom property drawer (in an Editor folder) to draw fields marked with this attribute as a dropdown, using these options.

using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(CharacterNameAttribute))]
public class CharacterNameDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {

        if (property.propertyType != SerializedPropertyType.String) {
            EditorGUI.LabelField(position, 
                "[CharacterName] can only be used on strings.",
                (GUIStyle)"ErrorStyle");
            return;
        }

        var names = CharacterNameAttribute.CurrentOptions;

        if (names == null || names.Length == 0) {
            EditorGUI.LabelField(position, 
                "No [CharacterName] options available.", 
                (GUIStyle)"ErrorStyle");
            return;
        }

        int index = Mathf.Max(0, System.Array.IndexOf(names, property.stringValue));
        index = EditorGUI.Popup(position, property.displayName, index, names);

        property.stringValue = names[index];
    }
}

Lastly, we need to somehow populate the list of items that are valid for the current entry that we're drawing. Let's say you have a Dialogue script that looks something like this...

(Note that we've used our shiny new [CharacterName] attribute to mark our speaker name string)

using UnityEngine;

public class DialogueExample : MonoBehaviour
{
    [System.Serializable]
    public struct DialogueLine {
        [CharacterName]
        public string speaker;
        public Sprite expression;
        [TextArea(1, 4)]
        public string text;
    }

    [System.Serializable]
    public struct DialogueSection {
        public DialogueLine[] lines;
    }

    public string[] characterNames;

    public DialogueSection[] dialogues;
}

We can use a custom editor for this type (again in an Editor folder) to set the available character names, then let Unity handle drawing the standard inspector fields for all the contents of this instance:

using UnityEditor;

[CustomEditor(typeof(DialogueExample))]
public class DialogueExampleEditor : Editor
{
    public override void OnInspectorGUI() {

        // Set the names we're allowed to use in this dialogue.
        var dialogue = (DialogueExample)target;    
        CharacterNameAttribute.CurrentOptions = dialogue.characterNames;

        // Ask Unity to draw all our inspector fields, handle saving/undo/etc...
        DrawDefaultInspector();

        // Clean up after ourselves, so we don't leak into the next use of [CharacterName]
        CharacterNameAttribute.CurrentOptions = null;
    }
}

All told, we get an inspector that looks like this:

Example inspector showing drop-downs for character names, using unique name sets.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.