184 lines
6.3 KiB
C#
184 lines
6.3 KiB
C#
using UnityEditor;
|
||
using UnityEngine;
|
||
using UnityEngine.Playables;
|
||
|
||
/// <summary>
|
||
/// 自动检测并清理 PlayableDirector 中 missing 的 Binding 引用。
|
||
/// 不使用 [CustomEditor],不影响原生 Inspector 显示。
|
||
/// 触发时机:选中带有 PlayableDirector 的物体时自动检测清理。
|
||
/// 手动触发:PlayableDirector 组件右上角菜单 → "检查并清理 Missing Binding 引用"。
|
||
/// </summary>
|
||
[InitializeOnLoad]
|
||
public class PlayableDirectorBindingValidator
|
||
{
|
||
static PlayableDirectorBindingValidator()
|
||
{
|
||
Selection.selectionChanged += OnSelectionChanged;
|
||
}
|
||
|
||
[MenuItem("CONTEXT/PlayableDirector/检查并清理 Missing Binding 引用")]
|
||
private static void ContextMenuClean(MenuCommand command)
|
||
{
|
||
var director = command.context as PlayableDirector;
|
||
if (director == null) return;
|
||
|
||
int cleaned = CleanMissingBindings(director);
|
||
if (cleaned > 0)
|
||
{
|
||
Debug.Log($"[PlayableDirector Cleaner] {director.gameObject.name}: 清理了 {cleaned} 个 missing 引用");
|
||
ActiveEditorTracker.sharedTracker.ForceRebuild();
|
||
}
|
||
else
|
||
{
|
||
Debug.Log($"[PlayableDirector Cleaner] {director.gameObject.name}: 未发现 missing 引用,一切正常 ✓");
|
||
}
|
||
}
|
||
|
||
private static void OnSelectionChanged()
|
||
{
|
||
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
|
||
if (Selection.activeGameObject == null) return;
|
||
|
||
var directors = Selection.activeGameObject.GetComponents<PlayableDirector>();
|
||
if (directors == null || directors.Length == 0) return;
|
||
|
||
// 先快速扫描是否有需要清理的,没有则跳过
|
||
bool hasMissing = false;
|
||
foreach (var director in directors)
|
||
{
|
||
if (director == null) continue;
|
||
if (HasMissingBindings(director))
|
||
{
|
||
hasMissing = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!hasMissing) return;
|
||
|
||
// 有 missing 引用时,延迟到下一帧清理,然后强制 Inspector 重建
|
||
EditorApplication.delayCall += () =>
|
||
{
|
||
bool anyCleaned = false;
|
||
foreach (var director in directors)
|
||
{
|
||
if (director == null) continue;
|
||
int cleaned = CleanMissingBindings(director);
|
||
if (cleaned > 0)
|
||
{
|
||
anyCleaned = true;
|
||
Debug.Log($"[PlayableDirector Cleaner] 自动清理 <b>{director.gameObject.name}</b>: {cleaned} 个 missing 引用");
|
||
}
|
||
}
|
||
if (anyCleaned)
|
||
{
|
||
// 强制 Inspector 重建,让原生 DirectorEditor 重新读取已修改的序列化数据
|
||
ActiveEditorTracker.sharedTracker.ForceRebuild();
|
||
}
|
||
};
|
||
}
|
||
|
||
private static bool HasMissingBindings(PlayableDirector director)
|
||
{
|
||
var so = new SerializedObject(director);
|
||
|
||
var sceneBindings = so.FindProperty("m_SceneBindings");
|
||
if (sceneBindings != null)
|
||
{
|
||
for (int i = 0; i < sceneBindings.arraySize; i++)
|
||
{
|
||
var element = sceneBindings.GetArrayElementAtIndex(i);
|
||
if (IsMissingReference(element.FindPropertyRelative("key"))
|
||
|| IsMissingReference(element.FindPropertyRelative("value")))
|
||
{
|
||
so.Dispose();
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
var exposedRefs = so.FindProperty("m_ExposedReferences.m_References");
|
||
if (exposedRefs != null)
|
||
{
|
||
for (int i = 0; i < exposedRefs.arraySize; i++)
|
||
{
|
||
if (IsMissingReference(exposedRefs.GetArrayElementAtIndex(i).FindPropertyRelative("second")))
|
||
{
|
||
so.Dispose();
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
so.Dispose();
|
||
return false;
|
||
}
|
||
|
||
private static int CleanMissingBindings(PlayableDirector director)
|
||
{
|
||
int cleaned = 0;
|
||
var so = new SerializedObject(director);
|
||
|
||
// 清理 m_SceneBindings 中 missing 的条目
|
||
var sceneBindings = so.FindProperty("m_SceneBindings");
|
||
if (sceneBindings != null)
|
||
{
|
||
for (int i = sceneBindings.arraySize - 1; i >= 0; i--)
|
||
{
|
||
var element = sceneBindings.GetArrayElementAtIndex(i);
|
||
|
||
var key = element.FindPropertyRelative("key");
|
||
if (IsMissingReference(key))
|
||
{
|
||
sceneBindings.DeleteArrayElementAtIndex(i);
|
||
cleaned++;
|
||
continue;
|
||
}
|
||
|
||
// key 有效但 value 是 missing 引用时,清空 value(只解绑,不删除整条记录)
|
||
var value = element.FindPropertyRelative("value");
|
||
if (IsMissingReference(value))
|
||
{
|
||
value.objectReferenceValue = null;
|
||
cleaned++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 清理 m_ExposedReferences 中 missing 的条目
|
||
var exposedRefs = so.FindProperty("m_ExposedReferences.m_References");
|
||
if (exposedRefs != null)
|
||
{
|
||
for (int i = exposedRefs.arraySize - 1; i >= 0; i--)
|
||
{
|
||
var second = exposedRefs.GetArrayElementAtIndex(i).FindPropertyRelative("second");
|
||
if (IsMissingReference(second))
|
||
{
|
||
exposedRefs.DeleteArrayElementAtIndex(i);
|
||
cleaned++;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (cleaned > 0)
|
||
{
|
||
so.ApplyModifiedProperties();
|
||
EditorUtility.SetDirty(director);
|
||
}
|
||
|
||
so.Dispose();
|
||
return cleaned;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断一个 ObjectReference 属性是否为 missing 引用:
|
||
/// 对象已被删除(无法解析),但序列化数据中仍残留非零的 instanceID。
|
||
/// </summary>
|
||
private static bool IsMissingReference(SerializedProperty prop)
|
||
{
|
||
if (prop == null) return false;
|
||
if (prop.propertyType != SerializedPropertyType.ObjectReference) return false;
|
||
return prop.objectReferenceValue == null
|
||
&& prop.objectReferenceInstanceIDValue != 0;
|
||
}
|
||
}
|