Search code examples
c#unity-game-engineunity-editorunity3d-editorscriptable-object

How to get a value from an array of scriptable objects in unity custom editor?


I'm trying make a custom editor and I can't get a value from a scriptable object in an array. Here is the FightData script that contains CharacterData scriptable objects.

public class FightData : ScriptableObject {
    public CharacterData [] enemyDatas;
}

CharacterData contains stats.

public class CharacterData : ScriptableObject {

    public CharacterStats stats;
}

Here is the CharacterStats

[System.Serializable]
public class CharacterStats
{
    public float HP;
    public float damage;
    public float defense;
    public float attackInterval;

    public CharacterStats(){
        HP = 0f;
        damage = 0f;
        defense = 0f;
        attackInterval = 2f;
    }
}

I want to sum up all the stats and display them in the custom editor for FightData. But stats is always null. Here is my approach:

[CustomEditor(typeof(FightData))]
public class FightDataEditor : Editor
{
    SerializedProperty _enemyDatas;


    private void OnEnable() {  
        _enemyDatas = serializedObject.FindProperty("enemyDatas");     
    }

    public override void OnInspectorGUI(){
        serializedObject.Update();
        EditorGUILayout.PropertyField(_enemyDatas);

        var result = 0f;
        int length = _enemyDatas.arraySize;
        for (int i = 0; i < length; i++)
        {
            SerializedProperty stats = _enemyDatas.GetArrayElementAtIndex(i).serializedObject.FindProperty("stats");
            if(stats == null){
                Debug.Log(stats);
                continue;
            }
            result += stats.FindPropertyRelative("HP").floatValue;
            result += stats.FindPropertyRelative("damage").floatValue;
            result += stats.FindPropertyRelative("defense").floatValue;
        }

        EditorGUILayout.Space(); 
        EditorGUILayout.LabelField("Total Power", EditorStyles.boldLabel);
        EditorGUILayout.FloatField(result);

 
        serializedObject.ApplyModifiedProperties();
    }
}

Solution

  • FindPropertyRelative would work only if it is actuallly a nested "normal" serialized class which is serialized into YAML values.

    But for ScriptableObject (in general anything inheriting from UnityEngine.Object) what you store is not serialized values but rather only the UnityEngine.Object reference!

    You rather need to get the according SerializedObject instances like e.g.

    [CustomEditor(typeof(FightData))]
    public class FightDataEditor : Editor
    {
        SerializedProperty _enemyDatas;
    
    
        private void OnEnable() 
        {  
            _enemyDatas = serializedObject.FindProperty("enemyDatas");     
        }
    
        public override void OnInspectorGUI()
       {
            serializedObject.Update();
            EditorGUILayout.PropertyField(_enemyDatas);
    
            var result = 0f;
            int length = _enemyDatas.arraySize;
            for (int i = 0; i < length; i++)
            {
                // Get the element SerializedProperty
                var element = _enemyDatas.GetArrayElementAtIndex(i);
    
                // The field exists but no object is referenced
                if(!element.objectReferenceValue)
                {
                    continue;
                }
    
                // otherwise get a serialized object for this reference
                var sObj = new SerializedObject(element.objectReferenceValue);
    
                // stats is now a property of that serialized object
                var stats = sObj.FindProperty("stats");
    
                // The other values are now further nested properties of the serialized CharacterStats class
                result += stats.FindPropertyRelative("HP").floatValue;
                result += stats.FindPropertyRelative("damage").floatValue;
                result += stats.FindPropertyRelative("defense").floatValue;
            }
    
            EditorGUILayout.Space(); 
            EditorGUILayout.LabelField("Total Power", EditorStyles.boldLabel);
            EditorGUILayout.FloatField(result);
    
     
            serializedObject.ApplyModifiedProperties();
        }
    }
    

    Note: If you are going to expose fields of these SerializedObjects and also want to change them you would additionally also call accordingly

    sObj.Update();
    
    ...
    
    sObj.ApplyModifiedProperties();
    

    Typed on smartphone but I hope the idea gets clear