特效处理
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
|
||||
public class MissingScriptRemover : EditorWindow
|
||||
{
|
||||
private readonly List<Object> targetObjects = new List<Object>();
|
||||
private readonly List<string> prefabPaths = new List<string>();
|
||||
private bool includeSubFolders = true;
|
||||
private bool includeScene = false;
|
||||
private bool dryRun = true;
|
||||
private bool lastRunWasScan = true;
|
||||
|
||||
private readonly Dictionary<string, int> scanResults = new Dictionary<string, int>();
|
||||
private int totalMissing;
|
||||
private int fileCount;
|
||||
private Vector2 scrollPos;
|
||||
private bool hasScanned = false;
|
||||
|
||||
[MenuItem("Tools/预制体丢失脚本清理工具")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
GetWindow<MissingScriptRemover>("丢失脚本清理");
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"拖入 Prefab 或文件夹 → 扫描 → 清理\n" +
|
||||
"扫描模式只统计不修改;确认无误后取消勾选「仅扫描」再点清理", MessageType.Info);
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
EditorGUILayout.LabelField("目标(Prefab / 文件夹,可多选拖入)", EditorStyles.boldLabel);
|
||||
Rect dropRect = GUILayoutUtility.GetRect(0, 40, GUILayout.ExpandWidth(true));
|
||||
GUI.Box(dropRect, targetObjects.Count == 0
|
||||
? "拖入 Prefab 或文件夹到此处"
|
||||
: string.Join(", ", targetObjects.ConvertAll(o => o.name).ToArray()),
|
||||
EditorStyles.helpBox);
|
||||
Event evt = Event.current;
|
||||
if ((evt.type == EventType.DragUpdated || evt.type == EventType.DragPerform) && dropRect.Contains(evt.mousePosition))
|
||||
{
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
if (evt.type == EventType.DragPerform)
|
||||
{
|
||||
DragAndDrop.AcceptDrag();
|
||||
if (DragAndDrop.objectReferences.Length > 0)
|
||||
{
|
||||
targetObjects.Clear();
|
||||
targetObjects.AddRange(DragAndDrop.objectReferences);
|
||||
hasScanned = false;
|
||||
}
|
||||
}
|
||||
evt.Use();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("使用 Project 当前选中", GUILayout.Height(22)))
|
||||
{
|
||||
Object[] sel = Selection.objects;
|
||||
if (sel != null && sel.Length > 0)
|
||||
{
|
||||
targetObjects.Clear();
|
||||
targetObjects.AddRange(sel);
|
||||
hasScanned = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "请先在 Project 窗口选中 Prefab 或文件夹", "确定");
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(6);
|
||||
includeSubFolders = EditorGUILayout.ToggleLeft("包含子文件夹", includeSubFolders);
|
||||
includeScene = EditorGUILayout.ToggleLeft("同时处理当前打开的场景", includeScene);
|
||||
dryRun = EditorGUILayout.ToggleLeft("仅扫描(不修改任何文件)", dryRun);
|
||||
|
||||
EditorGUILayout.Space(6);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUI.enabled = targetObjects.Count > 0 || includeScene;
|
||||
if (GUILayout.Button("扫描", GUILayout.Height(28)))
|
||||
{
|
||||
ScanOrClean(true);
|
||||
}
|
||||
GUI.enabled = true;
|
||||
|
||||
GUI.enabled = (targetObjects.Count > 0 || includeScene) && !dryRun;
|
||||
string cleanLabel = dryRun ? "清理(已禁用:勾选了仅扫描)" : "清理";
|
||||
if (GUILayout.Button(cleanLabel, GUILayout.Height(28)))
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("确认清理",
|
||||
$"将永久删除丢失的脚本组件(不可撤销,建议先提交或备份)。\n目标: {(targetObjects.Count > 0 ? string.Join(", ", targetObjects.ConvertAll(o => o.name).ToArray()) : "")}{(includeScene ? " + 当前场景" : "")}\n\n确认继续?",
|
||||
"确认清理", "取消"))
|
||||
{
|
||||
ScanOrClean(false);
|
||||
}
|
||||
}
|
||||
GUI.enabled = true;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
if (hasScanned)
|
||||
{
|
||||
string modeText = lastRunWasScan ? "[扫描结果]" : "[清理结果]";
|
||||
EditorGUILayout.LabelField($"{modeText} 共 {fileCount} 个文件,丢失脚本组件 {totalMissing} 处", EditorStyles.boldLabel);
|
||||
|
||||
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
|
||||
foreach (var kv in scanResults)
|
||||
{
|
||||
if (kv.Value > 0)
|
||||
{
|
||||
Color old = GUI.color;
|
||||
GUI.color = new Color(1f, 0.75f, 0.45f);
|
||||
EditorGUILayout.LabelField($"{kv.Key} ({kv.Value})");
|
||||
GUI.color = old;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndScrollView();
|
||||
|
||||
if (scanResults.Count > 0 && GUILayout.Button("定位选中第一个文件"))
|
||||
{
|
||||
foreach (var kv in scanResults)
|
||||
{
|
||||
if (kv.Value > 0 && kv.Key.EndsWith(".prefab"))
|
||||
{
|
||||
var obj = AssetDatabase.LoadMainAssetAtPath(kv.Key);
|
||||
if (obj != null)
|
||||
{
|
||||
Selection.activeObject = obj;
|
||||
EditorGUIUtility.PingObject(obj);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScanOrClean(bool scanOnly)
|
||||
{
|
||||
lastRunWasScan = scanOnly;
|
||||
prefabPaths.Clear();
|
||||
prefabPaths.AddRange(CollectPrefabPaths(targetObjects.ToArray()));
|
||||
|
||||
scanResults.Clear();
|
||||
totalMissing = 0;
|
||||
fileCount = 0;
|
||||
int sceneMissing = 0;
|
||||
|
||||
bool cancelled = false;
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < prefabPaths.Count; i++)
|
||||
{
|
||||
string path = prefabPaths[i];
|
||||
if (EditorUtility.DisplayCancelableProgressBar(
|
||||
scanOnly ? "扫描丢失脚本" : "清理丢失脚本",
|
||||
path, (float)i / prefabPaths.Count))
|
||||
{
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
int removed = ProcessPrefab(path, !scanOnly);
|
||||
scanResults[path] = removed;
|
||||
totalMissing += removed;
|
||||
fileCount++;
|
||||
}
|
||||
|
||||
if (includeScene)
|
||||
{
|
||||
sceneMissing = ProcessOpenScene(!scanOnly);
|
||||
if (sceneMissing > 0)
|
||||
{
|
||||
scanResults["[当前场景]"] = sceneMissing;
|
||||
totalMissing += sceneMissing;
|
||||
}
|
||||
fileCount++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
if (!scanOnly && totalMissing > 0)
|
||||
{
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
hasScanned = true;
|
||||
|
||||
string summary = cancelled
|
||||
? $"已取消。处理到第 {fileCount} 个,共发现 {totalMissing} 处丢失脚本"
|
||||
: (scanOnly
|
||||
? $"扫描完成: {fileCount} 个文件,共 {totalMissing} 处丢失脚本"
|
||||
: $"清理完成: 处理 {fileCount} 个文件,删除 {totalMissing} 个丢失脚本组件");
|
||||
|
||||
if (scanOnly && totalMissing > 0)
|
||||
summary += "\n取消勾选「仅扫描」后点击「清理」即可删除";
|
||||
Debug.Log($"[MissingScriptRemover] {summary}");
|
||||
EditorUtility.DisplayDialog(scanOnly ? "扫描完成" : "清理完成", summary, "确定");
|
||||
}
|
||||
|
||||
private IEnumerable<string> CollectPrefabPaths(Object[] objects)
|
||||
{
|
||||
var result = new List<string>();
|
||||
foreach (var obj in objects)
|
||||
{
|
||||
if (obj == null) continue;
|
||||
|
||||
string path = AssetDatabase.GetAssetPath(obj);
|
||||
if (string.IsNullOrEmpty(path)) continue;
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
if (includeSubFolders)
|
||||
{
|
||||
foreach (string guid in AssetDatabase.FindAssets("t:Prefab", new[] { path }))
|
||||
{
|
||||
string p = AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (!result.Contains(p)) result.Add(p);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(path, "*.prefab"))
|
||||
{
|
||||
string p = file.Replace('\\', '/');
|
||||
if (!result.Contains(p)) result.Add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (path.EndsWith(".prefab", System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!result.Contains(path)) result.Add(path);
|
||||
}
|
||||
}
|
||||
result.Sort();
|
||||
return result;
|
||||
}
|
||||
|
||||
private int ProcessPrefab(string path, bool clean)
|
||||
{
|
||||
GameObject root = PrefabUtility.LoadPrefabContents(path);
|
||||
int total = 0;
|
||||
try
|
||||
{
|
||||
total = ProcessGameObjectTree(root, clean);
|
||||
if (clean && total > 0)
|
||||
{
|
||||
PrefabUtility.SaveAsPrefabAsset(root, path);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrefabUtility.UnloadPrefabContents(root);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private int ProcessGameObjectTree(GameObject root, bool clean)
|
||||
{
|
||||
int total = 0;
|
||||
total += HandleGameObject(root, clean);
|
||||
Transform[] children = root.GetComponentsInChildren<Transform>(true);
|
||||
foreach (Transform t in children)
|
||||
{
|
||||
if (t.gameObject == root) continue;
|
||||
total += HandleGameObject(t.gameObject, clean);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private int HandleGameObject(GameObject go, bool clean)
|
||||
{
|
||||
if (clean)
|
||||
{
|
||||
return GameObjectUtility.RemoveMonoBehavioursWithMissingScript(go);
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
foreach (Component c in go.GetComponents<Component>())
|
||||
{
|
||||
if (c == null) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private int ProcessOpenScene(bool clean)
|
||||
{
|
||||
int total = 0;
|
||||
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
||||
foreach (GameObject rootObj in scene.GetRootGameObjects())
|
||||
{
|
||||
total += ProcessGameObjectTree(rootObj, clean);
|
||||
}
|
||||
if (clean && total > 0)
|
||||
{
|
||||
EditorSceneManager.MarkSceneDirty(scene);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user