简介
最近搞了几天的编辑器的工具,发现不怎么用的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上进行这种操作那么对于数据复制粘贴等等操作都非常方便,而不用依赖于其父结构。