using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text.RegularExpressions; using UnityEditor; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; // using static com.fpnn.common.TaskThreadPool; public class PrefabPlayableBindingCleaner : EditorWindow { [MenuItem("Assets/批量清理Prefab中Playable Binding冗余元素")] static void MenuAssetsCheckFishLayer() { var path = AssetDatabase.GetAssetPath(Selection.activeObject); // 获取所有Prefab路径 string[] prefabPaths = Directory.GetFiles( path, "*.prefab", SearchOption.AllDirectories ); // 重置统计 int _processedPrefabCount = 0; int _cleanedBindingCount = 0; // 禁用自动刷新(提升批量处理速度) AssetDatabase.DisallowAutoRefresh(); try { foreach (string prefabPath in prefabPaths) { _processedPrefabCount++; // 清理单个Prefab的Binding _cleanedBindingCount += CleanPlayableBindingInPrefab(prefabPath); } } finally { // 恢复自动刷新+刷新资源库 AssetDatabase.AllowAutoRefresh(); AssetDatabase.Refresh(); } EditorUtility.DisplayDialog("清理完成", $"共处理 Prefab 数量:{_processedPrefabCount}\n清理冗余 Binding 数量:{_cleanedBindingCount}", "确定"); } public static string GetRelativePath(string path) { string srcPath = path.Replace("\\", "/"); var retPath = Regex.Replace(srcPath, @"^(.*?/)Assets/", "Assets/"); return retPath; } [MenuItem("Assets/批量清理Prefab中Playable Binding冗余元素", true)] private static bool MenuAssetsCheckFishLayerValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); return AssetDatabase.IsValidFolder(path); } /// /// 清理单个Prefab中PlayableDirector的冗余Binding /// /// Prefab完整路径 /// 当前Prefab清理的Binding数量 private static int CleanPlayableBindingInPrefab(string prefabPath) { int cleanedCount = 0; int cleanedSecondCount = 0; GameObject prefabRoot = null; try { prefabRoot = PrefabUtility.LoadPrefabContents(prefabPath); if (prefabRoot == null) { Debug.LogError($"加载Prefab失败,跳过:{prefabPath}"); return 0; } } catch (Exception e) { Debug.LogError($"加载Prefab失败,跳过:{prefabPath} {e.Message}"); return 0; } { // 获取Prefab中所有的PlayableDirector组件 PlayableDirector[] directors = prefabRoot.GetComponentsInChildren(true); foreach (PlayableDirector director in directors) { if (director == null || director.playableAsset == null) continue; GameObject prefab = director.gameObject; // 获取当前PlayableDirector绑定的TimelineAsset TimelineAsset currentTimeline = director.playableAsset as TimelineAsset; Dictionary bindings = new Dictionary(); foreach (var pb in director.playableAsset.outputs) { var key = pb.sourceObject; if (key != null) { var value = director.GetGenericBinding(key); if (value != null) { bindings.Add(key, value); } } } var directorSO = new SerializedObject(director); var sceneBindings = directorSO.FindProperty("m_SceneBindings"); var exposedRefsArray = directorSO.FindProperty("m_ExposedReferences.m_References"); int originalBindingCount = sceneBindings.arraySize; // 移除无用的绑定 while (sceneBindings.arraySize > 0) { sceneBindings.DeleteArrayElementAtIndex(0); } // 3. 反向遍历数组(核心:避免索引错位导致ObjectDisposedException) for (int i = exposedRefsArray.arraySize - 1; i >= 0; i--) { // 获取数组当前元素 SerializedProperty refElement = exposedRefsArray.GetArrayElementAtIndex(i); //m_ExposedReferences.m_References.Array.data[13].second // 找到元素中的"Second"字段(对应绑定的目标对象) SerializedProperty secondProp = refElement.FindPropertyRelative("second"); // 判断Second是否为空(None) if (secondProp == null || secondProp.objectReferenceValue == null) { // 删除该无效元素 cleanedSecondCount++; exposedRefsArray.DeleteArrayElementAtIndex(i); } } directorSO.ApplyModifiedProperties(); directorSO.Update(); // 恢复拷贝的Playable资源的bindings信息 // 设置有效绑定 foreach (var kv in bindings) { director.SetGenericBinding(kv.Key, kv.Value); } int newBindingCount = bindings.Count; cleanedCount += (originalBindingCount - newBindingCount); } if (cleanedCount > 0 || cleanedSecondCount > 0) { Debug.Log($"清理Prefab:{prefabRoot.name} 中PlayableDirector绑定 清理数:{cleanedCount} {cleanedSecondCount}"); EditorUtility.SetDirty(prefabRoot); // 保存Prefab修改(仅当有清理操作时) PrefabUtility.SaveAsPrefabAsset(prefabRoot, prefabPath); } } { // 释放Prefab资源(必须,避免内存泄漏) PrefabUtility.UnloadPrefabContents(prefabRoot); } return cleanedCount; } }