我正在使用SceneView.duringSceneGui在PropertyDrawer中制作一个编辑器。因此,当一个属性需要在SceneView中绘制内容时,需要订阅SceneView.DuringScene Gui,而当它不在时,则需要取消订阅。然而,我不知道如何知道编辑后的数组元素是否已从数组中删除。它仍然存在于内存中,SceneView.duringSceneGui订阅的方法仍然存在。我需要知道何时停止编辑和取消订阅。
我想我需要实现一些上下文对象,以存储属性值、编辑的对象、PropertyDrawer和该订阅方法,以便能够完全取消订阅该编辑器。。。尽管一次可能只有一个编辑器在运行。
有人发现了吗?找不到任何要删除或移除PropertyDrawers和数组元素的内容。
TL.DR.Unity是否有一个事件来告诉PropertyDrawer的数组元素已被删除,或者是否有一种简单或简洁的方法来解决这个问题?
因此,在制作示例时,我通过在每次SceneView绘制调用时获取属性值,并在捕获异常时或如果未编辑该值,则停止编辑器来解决问题。我在_IsInEditMode中添加了[NonSerialized],以修复我在最后一刻发现的一个新问题,因此这个问题至关重要。
不确定这是否是最好的方法。如果有人需要为某个类制作SceneView编辑器,下面的例子也适用于数组和列表。只需将其分成3个文件,并将它们放在各自的文件夹中,如Editor/forMyClassDrawer。
using InspectorSerializedUtility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MyScript : MonoBehaviour
{
public MyClass field;
public List<MyClass> list = new List<MyClass>();
}
[Serializable]
public class MyClass
{
#if UNITY_EDITOR
[NonSerialized]
public bool _IsInEditMode;
#endif
public Vector3 position;
public void Reset()
{
position = Vector3.zero;
#if UNITY_EDITOR
_IsInEditMode = false;
#endif
}
}
[CustomPropertyDrawer(typeof(MyClass))]
public class MyClassDrawer : PropertyDrawer
{
public MyClass value;
public Transform targetTransform;
private Tool internalTool;
bool editorStarted {
get => value?._IsInEditMode ?? false;
set {
if (this.value != null)
this.value._IsInEditMode = value;
}
}
private SerializedProperty currentProperty;
private SerializedProperty drawerProperty;
private static MyClassDrawer currentlyEditedDrawer;
string editorButtonText(bool isInEditMode) => isInEditMode ? "Stop Editing" : "Start Editing";
Color editorButtonColor(bool isInEditMode) => isInEditMode ? Color.red + Color.white / 2f : Color.white;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
//Debug.Log("OnGUI");
drawerProperty = property;
targetTransform = ((Component)property.serializedObject.targetObject).transform;
var val = property.GetValue<MyClass>();
GUI.color = editorButtonColor(val._IsInEditMode);
var toggle = GUI.Toggle(position, val._IsInEditMode, editorButtonText(val._IsInEditMode), "Button");
if (toggle != val._IsInEditMode)
{
if (toggle && currentlyEditedDrawer != null && currentlyEditedDrawer.editorStarted)
currentlyEditedDrawer.StopEditor();
value = val;
currentlyEditedDrawer = this;
if (toggle)
StartEditor();
else
StopEditor();
}
GUI.color = Color.white;
}
public void OnDrawScene(SceneView sv) => OnDrawScene();
public void OnDrawScene()
{
//Debug.Log("OnDrawScene");
MyClass value = null;
try
{
value = currentProperty.GetValue<MyClass>();
if (!value._IsInEditMode)
{
StopEditor();
return;
}
} catch
{
StopEditor();
return;
}
var m = Handles.matrix;
Handles.matrix = targetTransform.localToWorldMatrix;
if (Tools.current == Tool.Move)
{
internalTool = Tool.Move;
Tools.current = Tool.None;
}
if (internalTool == Tool.Move)
{
var pos = Handles.PositionHandle(value.position, Quaternion.identity);
if (value.position != pos)
{
Undo.RecordObject(targetTransform, "position changed");
value.position = pos;
}
}
Handles.matrix = m;
}
public void StartEditor()
{
currentProperty = drawerProperty;
editorStarted = true;
Debug.Log("StartEditor");
Subscribe();
CallAllSceneViewRepaint();
}
private void Subscribe()
{
Unsubscribe();
SceneView.duringSceneGui += OnDrawScene;
Selection.selectionChanged += StopEditor;
EditorSceneManager.sceneClosed += StopEditor;
AssemblyReloadEvents.beforeAssemblyReload += StopEditor;
}
public void StopEditor(Scene s) => StopEditor();
public void StopEditor()
{
Tools.current = internalTool;
editorStarted = false;
Unsubscribe();
currentProperty = null;
CallAllSceneViewRepaint();
}
private void Unsubscribe()
{
SceneView.duringSceneGui -= OnDrawScene;
Selection.selectionChanged -= StopEditor;
EditorSceneManager.sceneClosed -= StopEditor;
AssemblyReloadEvents.beforeAssemblyReload -= StopEditor;
}
private void CallAllSceneViewRepaint()
{
foreach (SceneView sv in SceneView.sceneViews)
sv.Repaint();
}
}
namespace InspectorSerializedUtility
{
/// <summary>
/// https://gist.github.com/douduck08/6d3e323b538a741466de00c30aa4b61f
/// </summary>
public static class InspectorSeriallizedUtils
{
public static T GetValue<T>(this SerializedProperty property) where T : class
{
try
{
if (property.serializedObject.targetObject == null) return null;
}
catch
{
return null;
}
object obj = property.serializedObject.targetObject;
string path = property.propertyPath.Replace(".Array.data", "");
string[] fieldStructure = path.Split('.');
Regex rgx = new Regex(@"[d+]");
for (int i = 0; i < fieldStructure.Length; i++)
{
if (fieldStructure[i].Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldStructure[i].Where(c => char.IsDigit(c)).ToArray()));
obj = GetFieldValueWithIndex(rgx.Replace(fieldStructure[i], ""), obj, index);
}
else
{
obj = GetFieldValue(fieldStructure[i], obj);
}
}
return (T)obj;
}
public static bool SetValue<T>(this SerializedProperty property, T value) where T : class
{
object obj = property.serializedObject.targetObject;
string path = property.propertyPath.Replace(".Array.data", "");
string[] fieldStructure = path.Split('.');
Regex rgx = new Regex(@"[d+]");
for (int i = 0; i < fieldStructure.Length - 1; i++)
{
if (fieldStructure[i].Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldStructure[i].Where(c => char.IsDigit(c)).ToArray()));
obj = GetFieldValueWithIndex(rgx.Replace(fieldStructure[i], ""), obj, index);
}
else
{
obj = GetFieldValue(fieldStructure[i], obj);
}
}
string fieldName = fieldStructure.Last();
if (fieldName.Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldName.Where(c => char.IsDigit(c)).ToArray()));
return SetFieldValueWithIndex(rgx.Replace(fieldName, ""), obj, index, value);
}
else
{
Debug.Log(value);
return SetFieldValue(fieldName, obj, value);
}
}
private static object GetFieldValue(string fieldName, object obj, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
return field.GetValue(obj);
}
return default(object);
}
private static object GetFieldValueWithIndex(string fieldName, object obj, int index, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
object list = field.GetValue(obj);
if (list.GetType().IsArray)
{
return ((object[])list)[index];
}
else if (list is IEnumerable)
{
return ((IList)list)[index];
}
}
return default(object);
}
public static bool SetFieldValue(string fieldName, object obj, object value, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
field.SetValue(obj, value);
return true;
}
return false;
}
public static bool SetFieldValueWithIndex(string fieldName, object obj, int index, object value, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
object list = field.GetValue(obj);
if (list.GetType().IsArray)
{
((object[])list)[index] = value;
return true;
}
else if (value is IEnumerable)
{
((IList)list)[index] = value;
return true;
}
}
return false;
}
}
}