うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】シリアライズされていないオブジェクトを Inspector や EditorWindow で表示する

シリアライズされていないオブジェクトの中身は Debug モードの Inspector でも表示できないため

表示させるためにはそれ専用のなにかが必要になる




しかしながら、必要なオブジェクトごとにそれを作るのが面倒になるので、リフレクションを用いて

オブジェクトを解析してパラメータを表示できるクラスを作成した

※長くなるので、コードは最後に記述する

サンプル

先程のクラスを利用して TestClass の中身を表示させてみる

   private Test TestClass = new Test();

    public class Test
    {
        private int _intValue;
        private string _stringValue = "";
        private bool _boolValue;
        private float _floatValue;
        private int? _nullableIntValue = 10;
        private TestSub _sub = new TestSub();
        private Generic<int> _genericInt = new Generic<int>();
        private Generic<string> _genericString = new Generic<string>();
        private int[] _intArray = {0, 1, 2};
        private List<int> _intList = new List<int>{0, 1, 2};
        private Dictionary<string, int> _dictionary = new Dictionary<string, int>
        {
            {"a", 1},
            {"b", 2},
            {"c", 3},
        };
        private Dictionary<string, TestSub> _dictionaryClass = new Dictionary<string, TestSub>
        {
            {"a", new TestSub()},
            {"b", new TestSub()},
        };
    }

    public class TestSub
    {
        private int _intValue;
        private string _stringValue = "";
        private bool _boolValue;
        private float _floatValue;
        private Inner _inner = new Inner();

        private class Inner
        {
            private int _intValue;
        }
    }

    private class Generic<T>
    {
        private T Value;
    }

このように Inspector に表示される

見てわかるように、全部のフィールドが表示されるようになり、値の変更も可能になっている

f:id:hacchi_man:20210414234458p:plain:w400

コード

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;

public class ObjectDrawer
{
    protected virtual BindingFlags Flags => BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;

    /// <summary>
    /// オブジェクトから生成
    /// </summary>
    public void Draw(object obj, string label = "")
    {
        if (obj == null)
        {
            EditorGUILayout.LabelField("Object is null");
            return;
        }

        var type = obj.GetType();
        if (!type.IsClass)
        {
            Debug.Log("require class");
            return;
        }

        if (string.IsNullOrEmpty(label))
        {
            label = type.Name;
        }

        DrawClass(label, type, obj);
    }

    protected virtual object DrawClass(string fieldName, Type type, object value)
    {
        var foldout = DrawFoldOut(fieldName, fieldName);
        if (foldout)
        {
            using (new EditorGUI.IndentLevelScope())
            {
                if (value == null)
                {
                    EditorGUILayout.LabelField("Null");
                }
                else
                {
                    var fields = type.GetFields(Flags);
                    using (new EditorGUILayout.VerticalScope())
                    {
                        foreach (var field in fields)
                        {
                            DrawField(field, value);
                        }
                    }
                }
            }
        }

        return value;
    }

    protected virtual void DrawField(FieldInfo field, object obj)
    {
        if (IsSkipDrawField(field.FieldType))
        {
            return;
        }
        var value = field.GetValue(obj);
        var v2 = DrawValue(field.Name, field.FieldType, value);
        if (v2 != value)
        {
            field.SetValue(obj, v2);
        }
    }

    /// <summary>
    /// Fieldの描画を無視するかどうか
    /// </summary>
    protected virtual bool IsSkipDrawField(Type type)
    {
        return false;
    }

    private object DrawValue(string fieldName, Type type, object value)
    {
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            value = DrawNullable(fieldName, type, value);
        }
        else if (value == null)
        {
            EditorGUILayout.LabelField(fieldName, "null");
        }
        else if (IsDrawOverride(type, value))
        {
            value = DrawOverride(fieldName, type, value);
        }
        else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
        {
            value = DrawList(fieldName, type.GetGenericArguments()[0], (IList) value);
        }
        else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
        {
            value = DrawDictionary(fieldName, type, (IDictionary) value);
        }
        else if (type.IsArray)
        {
            value = DrawArray(fieldName, type.GetElementType(), (Array) value);
        }
        else if (type.IsEnum)
        {
            value = DrawEnum(fieldName, type, (Enum) value);
        }
        else if (type == typeof(bool))
        {
            value = DrawBool(fieldName, (bool) value);
        }
        else if (type == typeof(int))
        {
            value = DrawInt(fieldName, (int) value);
        }
        else if (type == typeof(uint))
        {
            value = DrawUint(fieldName, (uint) value);
        }
        else if (type == typeof(float))
        {
            value = DrawFloat(fieldName, (float)value);
        }
        else if (type == typeof(string))
        {
            value = DrawString(fieldName, (string) value);
        }
        else if (type == typeof(Vector2))
        {
            value = DrawVector2(fieldName, (Vector2) value);
        }
        else if (type == typeof(Vector2Int))
        {
            value = DrawVector2Int(fieldName, (Vector2Int) value);
        }
        else if (type == typeof(Vector3))
        {
            value = DrawVector3(fieldName, (Vector3) value);
        }
        else if (type == typeof(Vector3Int))
        {
            value = DrawVector3Int(fieldName, (Vector3Int) value);
        }
        else if (type == typeof(Vector4))
        {
            value = DrawVector4(fieldName, (Vector4) value);
        }
        else if (type == typeof(Color))
        {
            value = DrawColor(fieldName, (Color) value);
        }
        else if (type == typeof(AnimationCurve))
        {
            value = DrawAnimationCurve(fieldName, (AnimationCurve) value);
        }
        else if (type == typeof(Gradient))
        {
            value = DrawGradient(fieldName, (Gradient) value);
        }
        // stringとかクラスが色々あるので最後にクラスチェック
        else if (type.IsClass)
        {
            value = DrawClass(fieldName, type, value);
        }
        else
        {
            EditorGUILayout.LabelField(fieldName, "invalid Type: " + type.Name);
        }

        return value;
    }

    protected virtual object DrawNullable(string fieldName, Type type, object value)
    {
        var baseType = Nullable.GetUnderlyingType(type);
        var hasValue = value != null;
        if (hasValue)
        {
            return DrawValue(fieldName + "?", baseType, value);
        }

        EditorGUILayout.LabelField(fieldName, "null");
        return value;
    }

    /// <summary>
    /// 描画処理の上書きするかどうか
    /// </summary>
    protected virtual bool IsDrawOverride(Type type, object value)
    {
        return false;
    }

    /// <summary>
    /// 描画処理の上書きするかどうか
    /// </summary>
    protected virtual object DrawOverride(string fieldName, Type type, object value)
    {
        return value;
    }

    protected virtual Array DrawArray(string fieldName, Type type, Array value)
    {
        var foldout = DrawFoldOut(fieldName, fieldName + "[" + value.Length + "]");
        if (foldout)
        {
            using (new EditorGUI.IndentLevelScope())
            {
                using (new EditorGUILayout.VerticalScope())
                {
                    for (var i = 0; i < value.Length; i++)
                    {
                        var v = value.GetValue(i);
                        var v2 = DrawValue(i.ToString(), type, v);
                        if (v2 != v)
                        {
                            value.SetValue(v2, i);
                        }
                    }
                }
            }
        }

        return value;
    }

    protected virtual IList DrawList(string fieldName, Type type, IList value)
    {
        var foldout = DrawFoldOut(fieldName, fieldName + "[" + value.Count + "]");
        if (foldout)
        {
            using (new EditorGUI.IndentLevelScope())
            {
                using (new EditorGUILayout.VerticalScope())
                {
                    for (var i = 0; i < value.Count; i++)
                    {
                        var v = value[i];
                        value[i] = DrawValue(i.ToString(), type, v);
                    }
                }
            }
        }

        return value;
    }

    protected virtual IDictionary DrawDictionary(string fieldName, Type type, IDictionary value)
    {
        var foldout = DrawFoldOut(fieldName, fieldName + "[" + value.Count + "]");
        if (foldout)
        {
            var valueType = type.GetGenericArguments()[1];
            using (new EditorGUI.IndentLevelScope())
            {
                var keys = new object[value.Keys.Count];
                value.Keys.CopyTo(keys, 0);
                for (var i = 0; i < keys.Length; i++)
                {
                    var key = keys[i];
                    using (new EditorGUILayout.HorizontalScope())
                    {
                        var v = DrawValue(key.ToString(), valueType, value[key]);
                        if (!v.Equals(value[key]))
                        {
                            value[key] = v;
                        }
                    }
                }
            }
        }

        return value;
    }

    protected virtual Enum DrawEnum(string fieldName, Type type, Enum value)
    {
        return EditorGUILayout.EnumPopup(fieldName, value);
    }

    protected virtual bool DrawBool(string fieldName, bool value)
    {
        return EditorGUILayout.Toggle(fieldName, value);
    }

    protected virtual int DrawInt(string fieldName, int value)
    {
        return EditorGUILayout.IntField(fieldName, value);
    }

    protected virtual object DrawUint(string fieldName, uint value)
    {
        return EditorGUILayout.FloatField(fieldName, value);
    }

    protected virtual float DrawFloat(string fieldName, float value)
    {
        return EditorGUILayout.FloatField(fieldName, value);
    }

    protected virtual string DrawString(string fieldName, string value)
    {
        return EditorGUILayout.TextField(fieldName, value);
    }

    protected virtual Vector2 DrawVector2(string fieldName, Vector2 value)
    {
        return EditorGUILayout.Vector2Field(fieldName, value);
    }

    protected virtual Vector2Int DrawVector2Int(string fieldName, Vector2Int value)
    {
        return EditorGUILayout.Vector2IntField(fieldName, value);
    }

    protected virtual Vector3 DrawVector3(string fieldName, Vector3 value)
    {
        return EditorGUILayout.Vector3Field(fieldName, value);
    }

    protected virtual Vector3Int DrawVector3Int(string fieldName, Vector3Int value)
    {
        return EditorGUILayout.Vector3IntField(fieldName, value);
    }

    protected virtual Vector3 DrawVector4(string fieldName, Vector4 value)
    {
        return EditorGUILayout.Vector4Field(fieldName, value);
    }

    protected virtual Color DrawColor(string fieldName, Color value)
    {
        return EditorGUILayout.ColorField(fieldName, value);
    }

    protected virtual AnimationCurve DrawAnimationCurve(string fieldName, AnimationCurve value)
    {
        return EditorGUILayout.CurveField(fieldName, value);
    }

    protected virtual Gradient DrawGradient(string fieldName, Gradient value)
    {
        return EditorGUILayout.GradientField(fieldName, value);
    }

    private bool DrawFoldOut(string key, string label)
    {
        var foldout = EditorPrefs.GetBool(key, false);
        var f = EditorGUILayout.Foldout(foldout, label);
        if (f != foldout)
        {
            EditorPrefs.SetBool(key, f);
        }

        return f;
    }
}