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;
}
}