Create an Attribute that references another property

I want to make a variable with a custom editor that’s dependent on the value of another variable.

Simplified example:

public class IndexInStringBehaviour : MonoBehaviour
{
    [IndexInString("Content")] public int Index;
    public string Content;
}

Enumerate the letters in Content in a dropdown, let the user pick one from a list, and then be able to extract it at runtime (I don’t expect Content to change at runtime).

This pattern would be useful for a list of child transforms of a GameObject or other data within my data.

With the following code, you can extract the selected letter like so:

var c = IndexInStringAttribute.GetLetter(Content, Index);

Put this script in a file outside of Editor (it’s referenced by your runtime code via the attribute).

using UnityEngine;

#if UNITY_EDITOR
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Reflection;
using System;
using UnityEditor;
#endif

namespace Me.Inspector {

    public class IndexInStringAttribute : PropertyAttribute {
        // Name of the container to reference.
        public string ContentVar;
        public IndexInStringAttribute(string ContentVar) {
            this.ContentVar = ContentVar;
        }

        // Convert from the int decorated with IndexInString to the
        // selected child.
        public static char GetLetter(string container, int letter_index) {
            return container[letter_index];
        }
    }

#if UNITY_EDITOR
    [CustomPropertyDrawer(typeof(IndexInStringAttribute))]
    public class IndexInStringDrawer : PropertyDrawer {
        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
            var content_var = (attribute as IndexInStringAttribute).ContentVar;
            EditorGUI.BeginProperty(position, label, property);
            {
                // Try to find container as sibling variable.
                SerializedProperty container_prop = property.serializedObject.FindProperty(content_var);
                if (container_prop == null) {
                    // serializedObject might be an array. Swap our name with
                    // container and try again.
                    var container_path = property.propertyPath.Replace(property.name, content_var);
                    container_prop = property.serializedObject.FindProperty(container_path);
                }
                if (container_prop != null) {
                    // TODO: handle container_prop.hasMultipleDifferentValues
                    var container = container_prop.stringValue; // use objectReferenceValue for Serializable objects
                    // Here you'd do whatever you want with the container
                    // value. Let's make a popup of its contents.
                    var letters = new string[] {};
                    if (container != null) {
                        // Popup entries must be unique so include index
                        letters = container.ToCharArray().Select((c, i) => string.Format("{0} - {1}", i, c)).ToArray();
                    }
                    property.intValue = EditorGUI.Popup(position, property.displayName, property.intValue, letters);
                }
                else
                {
                    // Fallback when we can't find container property.
                    EditorGUI.PropertyField(position, property);
                }
            }
            EditorGUI.EndProperty();
        }
    }
#endif
}