Unity Attribute 自定义属性绘制-绘制脚本属性

323 阅读2分钟

Unity Attribute 自定义属性绘制-绘制脚本属性scriptableObject的面板

为 scriptableObject 在 inspector 的脚本 Field 中 提供了一个自定义的属性检视面板 可以通过这个面板来设置属性的值,而不是在要跳到scriptableObject的面板中去设置

前置知识

  1. C# Attribute 特性
  2. Unity Editor 编辑器扩展
  3. PropertyDrawer 属性绘制器

完整代码

自定义属性代码

using System;
using UnityEngine;

namespace Vanko.Editor.CusProperty
{
    [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
    public class ExpandableAttribute : PropertyAttribute
    {
        public ExpandableAttribute()
        {
        }
    }
}

属性绘制代码

using System;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using Vanko.Editor.CusProperty;

namespace Vanko.Weapon.Components.Editor
{
    [CustomPropertyDrawer(typeof(ExpandableAttribute), true)]
    public class ExpandableAttributeDrawer : PropertyDrawer
    {
        private enum BackgroundStyle
        {
            None,
            HelpBox,
            Darken,
            Lighten
        }

        // 是否显示脚本字段
        private static bool _showScriptField = true;

        // 弹出框的内边距 
        private static float _innerSpacing = 6.0f;

        // 弹出框的外边距
        private static float _outerSpacing = 4.0f;
        private static BackgroundStyle _backgroundStyle = BackgroundStyle.HelpBox;
        private static Color _darkenColor = new(0.0f, 0.0f, 0.0f, 0.2f);
        private static Color _lightColor = new(1.0f, 1.0f, 1.0f, 0.2f);

        private UnityEditor.Editor editor = null;

        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            //在默认情况下(未展开),高度为一行.
            // singleLineHeight 为unity EditorGUI.TextField or EditorGUI.Popup 的默认一行的高度
            float height = EditorGUIUtility.singleLineHeight;

            if (property.objectReferenceValue == null) // 引用为空
                return height;
            if (!property.isExpanded) // 未展开
                return height;
            if (!editor) // 编辑器为空
                // 创建 弹出编辑器
                UnityEditor.Editor.CreateCachedEditor(property.objectReferenceValue, null, ref editor);

            if (!editor)
                return height;

            // 获取序列化属性迭代器
            SerializedProperty field = editor.serializedObject.GetIterator();
            field.NextVisible(true);

            if (_showScriptField)
            {
                height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
            }

            while (field.NextVisible(false))
            {
                height += EditorGUI.GetPropertyHeight(field, true) + EditorGUIUtility.standardVerticalSpacing;
            }

            height += _innerSpacing * 2;
            height += _outerSpacing * 2;

            return height;
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            Rect fieldRect = new Rect(position);
            fieldRect.height = EditorGUIUtility.singleLineHeight;

            UnityEditor.EditorGUI.PropertyField(fieldRect, property, label, true);

            if (property.objectReferenceValue == null)
                return;

            // EditorGUI.indentLevel代表缩进级别,我这里进行-1,是为了让弹出框的箭头前移一格
            EditorGUI.indentLevel--;
            property.isExpanded = EditorGUI.Foldout(fieldRect, property.isExpanded, GUIContent.none, true);
            EditorGUI.indentLevel++;
            
            if (!property.isExpanded)
                return;

            if (!editor)
                UnityEditor.Editor.CreateCachedEditor(property.objectReferenceValue, null, ref editor);

            if (!editor)
                return;

            List<Rect> propertyRects = new List<Rect>();
            Rect marginRect = new Rect(fieldRect);

            Rect bodyRect = new Rect(fieldRect);
            bodyRect.xMin += EditorGUI.indentLevel * 14f;
            bodyRect.yMin += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing
                                                               + _outerSpacing;

            SerializedProperty field = editor.serializedObject.GetIterator(); // 获取序列化属性
            field.NextVisible(true);

            marginRect.y += _innerSpacing + _outerSpacing;

            if (_showScriptField)
            {
                marginRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
                propertyRects.Add(marginRect);
            }

            while (field.NextVisible(false))
            {
                marginRect.y += marginRect.height + EditorGUIUtility.standardVerticalSpacing;
                marginRect.height = EditorGUI.GetPropertyHeight(field, true);
                propertyRects.Add(marginRect);
            }

            marginRect.y += _innerSpacing;

            bodyRect.yMax = marginRect.yMax;

            DrawBackGround(bodyRect);

            EditorGUI.indentLevel++;

            int index = 0;
            field = editor.serializedObject.GetIterator(); // 获取序列化属性迭代器
            field.NextVisible(true);

            if (_showScriptField)
            {
                EditorGUI.BeginDisabledGroup(true);
                EditorGUI.PropertyField(propertyRects[index], field, true);
                EditorGUI.EndDisabledGroup();
                index++;
            }

            while (field.NextVisible(false))
            {
                try
                {
                    EditorGUI.PropertyField(propertyRects[index], field, true);
                }
                catch (StackOverflowException)
                {
                    field.objectReferenceValue = null;
                }

                index++;
            }

            EditorGUI.indentLevel--;

            // 保存对 属性的修改
            using (var check = new EditorGUI.ChangeCheckScope())
            {
                editor.serializedObject.ApplyModifiedProperties();
                if (check.changed)
                {
                    property.serializedObject.ApplyModifiedProperties();
                }
            }
        }

        private void DrawBackGround(Rect bodyRect)
        {
            switch (_backgroundStyle)
            {
                case BackgroundStyle.HelpBox:
                    EditorGUI.HelpBox(bodyRect, "", MessageType.None);
                    break;
                case BackgroundStyle.Darken:
                    EditorGUI.DrawRect(bodyRect, _darkenColor);
                    break;
                case BackgroundStyle.Lighten:
                    EditorGUI.DrawRect(bodyRect, _lightColor);
                    break;
            }
        }
    }
}

参考教程:

【Unity C#】自定义特性及属性绘制