借工作之便解決了先前想不到的 Drag and drop 的判斷.
- 利用 Event.Current 上的 EventType.DragUpdated, DragPerform 等抓取 Drag 狀態
- 使用 GUILayoutUtility.GetLastRect() 取得先前的 list element 的繪畫大小並覆蓋.
- 分拆出 DragAndDrop.objectReferences (選取的 Asset list) 並放入 nest 結構的 struct 裡面 (a.k.a: NamedPrefab)


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Test
{
[CreateAssetMenu]
public class PrefabLoader : ScriptableObject
{
[System.Serializable]
public struct NamedPrefab
{
public GameObject prefab;
public string name;
}
public List<NamedPrefab> m_PrefabList;
private Dictionary<string, GameObject> m_PrefabDic = null;
public Dictionary<string, GameObject> PrefabDic
{
get
{
if (m_PrefabDic == null)
{
m_PrefabDic = new Dictionary<string, GameObject>(m_PrefabList.Count);
for (int i = 0; i < m_PrefabList.Count; ++i)
{
// Noted: assume not require duplicate checking.
m_PrefabDic.Add(m_PrefabList[i].name, m_PrefabList[i].prefab);
}
}
return m_PrefabDic;
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(PrefabLoader), true)]
public class PrefabLoaderInspector : Editor
{
SerializedProperty scriptProp;
SerializedProperty prefabListProp;
private void OnEnable()
{
scriptProp = serializedObject.FindProperty("m_Script");
prefabListProp = serializedObject.FindProperty(nameof(PrefabLoader.m_PrefabList));
}
public override void OnInspectorGUI()
{
serializedObject.UpdateIfRequiredOrScript();
SerializedProperty iter = serializedObject.GetIterator();
iter.NextVisible(true);
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(scriptProp, includeChildren: true);
EditorGUI.EndDisabledGroup();
EditorGUI.BeginChangeCheck(); // ---- change check [optional]
do
{
if (scriptProp != null && iter.propertyPath == scriptProp.propertyPath)
{
// do nothing.
}
else
{
OnDrawProperty(iter);
}
}
while (iter.NextVisible(false));
if (EditorGUI.EndChangeCheck()) // ---- change check [optional]
serializedObject.ApplyModifiedProperties();
}
private void OnDrawProperty(SerializedProperty property)
{
if (property.propertyPath == prefabListProp.propertyPath)
{
EditorGUILayout.PropertyField(property);
DragArea(GUILayoutUtility.GetLastRect(), property, NamedPrefabEditorDrawer.CustomDraw);
}
else
{
EditorGUILayout.PropertyField(property);
}
}
public delegate void InsertProperty(SerializedProperty listProperty, UnityEngine.Object interactObj);
private void DragArea(Rect area, SerializedProperty property, InsertProperty drawCallback)
{
Event evt = Event.current;
if (evt.type == EventType.Repaint &&
DragAndDrop.visualMode == DragAndDropVisualMode.Copy)
GUI.Box(area, "Drag and drop all Skin.", GUI.skin.window);
switch (evt.type)
{
case EventType.DragUpdated:
case EventType.DragPerform:
if (!area.Contains(evt.mousePosition))
return;
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
if (property.isArray)
{
foreach (UnityEngine.Object obj in DragAndDrop.objectReferences)
{
if (!(obj is GameObject prefab))
continue;
if (!string.IsNullOrEmpty(prefab.scene.path))
{
Debug.LogError($"Scene object {prefab.name} not allow");
continue;
}
int last = property.arraySize;
property.InsertArrayElementAtIndex(last);
drawCallback?.Invoke(property.GetArrayElementAtIndex(last), obj);
property.serializedObject.ApplyModifiedProperties();
}
}
else
{
// Debug.LogError("Haven't test");
var obj = DragAndDrop.objectReferences.GetValue(0) as UnityEngine.Object;
drawCallback?.Invoke(property, obj);
}
}
break;
case EventType.MouseUp:
DragAndDrop.PrepareStartDrag();
break;
}
}
}
[CustomPropertyDrawer(typeof(PrefabLoader.NamedPrefab), true)]
public class NamedPrefabEditorDrawer : PropertyDrawer
{
public static void CustomDraw(SerializedProperty property, UnityEngine.Object interactObj)
{
// Debug.Log(listProperty.propertyType);
var nameProp = property.FindPropertyRelative(nameof(PrefabLoader.NamedPrefab.name));
var prefabProp = property.FindPropertyRelative(nameof(PrefabLoader.NamedPrefab.prefab));
nameProp.stringValue = interactObj.name;
prefabProp.objectReferenceValue = interactObj;
property.serializedObject.ApplyModifiedProperties();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
// return base.GetPropertyHeight(property, label);
return EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
// position = EditorGUI.PrefixLabel(position, label);
Rect[] rect = { position, position };
const float hSpace = 4f;
rect[0].width = EditorGUIUtility.labelWidth - hSpace;
rect[1].width = rect[1].width - rect[0].width;
rect[1].x += rect[0].width + hSpace;
SerializedProperty nameProp = property.FindPropertyRelative(nameof(PrefabLoader.NamedPrefab.name));
SerializedProperty prefabProp = property.FindPropertyRelative(nameof(PrefabLoader.NamedPrefab.prefab));
EditorGUI.BeginChangeCheck();
string tmp = EditorGUI.TextField(rect[0], nameProp.stringValue);
EditorGUI.PropertyField(rect[1], prefabProp, GUIContent.none, false);
if (EditorGUI.EndChangeCheck())
{
if (string.IsNullOrEmpty(tmp) && prefabProp.objectReferenceValue != null)
{
tmp = prefabProp.objectReferenceValue.name;
}
nameProp.stringValue = tmp;
property.serializedObject.ApplyModifiedProperties();
}
EditorGUI.EndProperty();
}
}
#endif
}
請教一下為什麼要Call UpdateIfRequiredOrScript,我一直搞不懂這個部分,但很多教學都直接寫要Call這個方法,想請問他實際上做了什麼?
官方文檔就有提到 UpdateIfRequiredOrScript
它就是把資料更新的 event 觸發一次,這樣確保 inspector收到event進行重繪.