Unity Editor 随手笔记-创建系列

简介

最近搞了几天的编辑器的工具,发现不怎么用的API总是忘记。在此做一个记录。

ScriptableObject、SerializedObject、Editor、SerializedProperty

1.我们知道当我们使用 AssetDatabase.CreateAsset(UnityEngine.Object asset, string path)创建ScriptableObject的时候编写对应Editor类,会有一个对应的SerializedObject的关系映射。可是如果我们只需要ScriptableObject的界面表现,不需要对应的资源时的创建怎么处理呢?比如在A的编辑界面中,我需要展示B的界面,但是A类中不包含B的属性。所以我们要在A-Editor创建一个B的ScriptableObject然后遍历其属性进行显示。

	public class EditorWindows : EditorWindow
	{
	    private SerializedObject serializedObject;
	    private void OnEnable()
	    {
	      //SceneDynamicData 继承自ScriptableObject
	      //--通过这个我们可以获取到serializedObject 
	       serializedObject = new SerializedObject(ScriptableObject.CreateInstance<SceneDynamicData>());
	    }

	    private void OnGUI() 
	    {
	        this.serializedObject.Update();
           bool enterChildren = true;
			for (var prop = serializedObject.GetIterator(); prop.NextVisible(enterChildren); enterChildren = false) {
				EditorGUILayout.PropertyField(prop );
			}
	        this.serializedObject.ApplyModifiedProperties();
	    }
	}

1.1直接创建对应的Editor类,因为Editor类中是有SerializedObject 的引用。获取到Editor类,直接调用OnInspector方法即可。

	public class EditorWindows : EditorWindow
	{
	    private Editor editor;	
	     private void OnEnable()
	    {
	        editor= Editor.CreateEditor(ScriptableObject.CreateInstance<SceneDynamicData>());
	    }
	
	    private void OnGUI() 
	    {
	        // 直接调用Inspector的绘制显示
	        this.editor.OnInspectorGUI();
	    }
	}

# PropertyDrawer 扩展GetValue和SetValue
由于PropertyDrawer 在unity内部只有基本类型的数据获取。如果有一个复杂的数据结构时,我们需要在PropertyDrawer中获取当前绘制的数据结构的数值会很麻烦。比如

	   [Serializable]
	    public struct EffectProperty
	    {
	        public EffectPropertyEnum ValueType;
	        public string ParamName;
	        public string ParamValue;
	        public string ParamExtend;
	        public Gradient ColorValue;
	        public List<AnimationCurve> CurveList;
	        #region 此状态获取到控制权时,其他状态过渡到此状态时的变化
	        //--x轴表示完全控制的时间系数变化,y轴表示控制权重。y的取值范围在[0,1]
	        public AnimationCurve ChangeToCurve;
	        #endregion
	    }

有一个对应的SceneEffectPropertyDrawer.cs类,需要获取到当前的EffectProperty数值,以及获取到之后进行赋值的操作。直接上代码了,代码是从别的地方参考的(地址当时没记录,不好意思原创。。)

using System.Collections;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using UnityEditor;
using UnityEngine;

namespace QF.Art
{
    public static class SceneDynamicEitorUtils
    {
        public static string PropertyPath;
        public static SerializedProperty EffectPropertyCopy;
        #region 扩展程序
        /// (Extension) Get the value of the serialized property.
        public static object GetValue(this SerializedProperty property)
        {
            string propertyPath = property.propertyPath;
            object value = property.serializedObject.targetObject;
            int i = 0;
            while (NextPathComponent(propertyPath, ref i, out var token))
                value = GetPathComponentValue(value, token);
            return value;
        }

        /// (Extension) Set the value of the serialized property.
        public static void SetValue(this SerializedProperty property, object value)
        {
            Undo.RecordObject(property.serializedObject.targetObject, $"Set {property.name}");

            SetValueNoRecord(property, value);

            EditorUtility.SetDirty(property.serializedObject.targetObject);
            property.serializedObject.ApplyModifiedProperties();
        }

        /// (Extension) Set the value of the serialized property, but do not record the change.
        /// The change will not be persisted unless you call SetDirty and ApplyModifiedProperties.
        public static void SetValueNoRecord(this SerializedProperty property, object value)
        {
            string propertyPath = property.propertyPath;
            object container = property.serializedObject.targetObject;

            int i = 0;
            NextPathComponent(propertyPath, ref i, out var deferredToken);
            while (NextPathComponent(propertyPath, ref i, out var token))
            {
                container = GetPathComponentValue(container, deferredToken);
                deferredToken = token;
            }
            Debug.Assert(!container.GetType().IsValueType, $"Cannot use SerializedObject.SetValue on a struct object, as the result will be set on a temporary.  Either change {container.GetType().Name} to a class, or use SetValue with a parent member.");
            SetPathComponentValue(container, deferredToken, value);
        }

        // Union type representing either a property name or array element index.  The element
        // index is valid only if propertyName is null.
        struct PropertyPathComponent
        {
            public string propertyName;
            public int elementIndex;
        }

        static Regex arrayElementRegex = new Regex(@"\GArray\.data\[(\d+)\]", RegexOptions.Compiled);
        
        static bool NextPathComponent(string propertyPath, ref int index, out PropertyPathComponent component)
        {
            component = new PropertyPathComponent();

            if (index >= propertyPath.Length)
                return false;

            var arrayElementMatch = arrayElementRegex.Match(propertyPath, index);
            if (arrayElementMatch.Success)
            {
                index += arrayElementMatch.Length + 1; // Skip past next '.'
                component.elementIndex = int.Parse(arrayElementMatch.Groups[1].Value);
                return true;
            }

            int dot = propertyPath.IndexOf('.', index);
            if (dot == -1)
            {
                component.propertyName = propertyPath.Substring(index);
                index = propertyPath.Length;
            }
            else
            {
                component.propertyName = propertyPath.Substring(index, dot - index);
                index = dot + 1; // Skip past next '.'
            }

            return true;
        }

        static object GetPathComponentValue(object container, PropertyPathComponent component)
        {
            if (component.propertyName == null)
                return ((IList)container)[component.elementIndex];
            else
                return GetMemberValue(container, component.propertyName);
        }

        static void SetPathComponentValue(object container, PropertyPathComponent component, object value)
        {
            if (component.propertyName == null)
                ((IList)container)[component.elementIndex] = value;
            else
                SetMemberValue(container, component.propertyName, value);
        }

        static object GetMemberValue(object container, string name)
        {
            if (container == null)
                return null;
            var type = container.GetType();
            var members = type.GetMember(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
            for (int i = 0; i < members.Length; ++i)
            {
                if (members[i] is FieldInfo field)
                    return field.GetValue(container);
                else if (members[i] is PropertyInfo property)
                    return property.GetValue(container);
            }
            return null;
        }

        static void SetMemberValue(object container, string name, object value)
        {
            var type = container.GetType();

            var members = type.GetMember(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
            for (int i = 0; i < members.Length; ++i)
            {
                if (members[i] is FieldInfo field)
                {
                    //---这里应该深度拷贝
                    //if (field.GetType())
                    //{

                    //}
                    field.SetValue(container, value);
                    return;
                }
                else if (members[i] is PropertyInfo property)
                {
                    property.SetValue(container, value);
                    return;
                }
            }
            Debug.Assert(false, $"Failed to set member {container}.{name} via reflection");
        }
        #endregion
        #region 深度拷贝
        public static T DeepCopy<T>(T obj)
        {
            object retval;
            using (MemoryStream ms = new MemoryStream())
            {
                XmlSerializer xml = new XmlSerializer(typeof(T));
                xml.Serialize(ms, obj);
                ms.Seek(0, SeekOrigin.Begin);
                retval = xml.Deserialize(ms);
                ms.Close();
            }
            return (T)retval;
        }
        #endregion
    }
}

最后说下,关于SetValue和GetValue的额外处理,因为代码中获取到的GetValue是依然会存在引用关系,如果直接进行SetValue进行设置的话,会出现数据的错乱,所以在SetValue之前对获取到的Value进行了一次深度拷贝。关于深度拷贝最终选择了使用Xml的方式,反射的方式和二进制的方式有些情况不能满足,因为Struct本身就是值类型,而且Draw里的引用数据构造函数有私有化的情况,所以会各种失败,使用XMl好像我遇到的情况可以完美解决。。。如果可以在Drawer上进行这种操作那么对于数据复制粘贴等等操作都非常方便,而不用依赖于其父结构。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘培玉--大王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值