Files
zeling_v2/Assets/_Game/Scripts/Editor/Tools/MissingScriptCleaner.cs
Joywayer 82ce9ff09a refactor(editor): reorganize Editor directory and unify menu hierarchy
File directory changes (mirror Scripts/ module structure):
- AbilityTypeDrawer.cs         → Equipment/
- CharacterWizardWindow.cs     → Character/
- FormEditorWindow.cs          → Player/
- GMToolWindow.cs              → Tools/
- SOManagerWindow.cs           → Tools/
- Map/MapRoomDataEditor.cs     → World/Map/
- Navigation/ (root)           → Enemies/Navigation/
- Achievements/                → Progression/

Menu hierarchy changes (BaseGames/ top-level):
- Data/: +Character Wizard (from Tools/), +Boss Skill Sequence (from Tools/)
- Addressables/: +Addressable Batch Tool, +Asset Reference Graph, +Validate Address Keys (from Tools/Verification/)
- Scene/Setup/: +Boot Flow Wizard, +Scaffold *, +Auto-Open Persistent (from Tools/)
- Scene/: +Camera Area Setup (from Camera/), +Bake All NavSurfaces (from Tools/)
- Events/: +Event Bus Monitor, +Event Chain Viewer, +Create/Reimport Event Channels (from Tools/)
- Tools/Validation/: +Validate All SOs, +Apply/Validate Script Order (from Tools/ flat)
- Tools/Maintenance/: +Missing Scripts/*, +Physics2D Layer Matrix/* (from Tools/ flat)

Result: BaseGames/Tools/ reduced from 16 flat items to 4 items + 2 submenus

Docs: update AssetFolderSpec §12 editor tool table with new menu paths

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-20 11:52:17 +08:00

231 lines
9.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace BaseGames.Editor
{
/// <summary>
/// 扫描当前场景及 Project 中所有 Prefab移除丢失Missing脚本引用。
///
/// 菜单BaseGames/Tools/Missing Scripts/
/// </summary>
public static class MissingScriptCleaner
{
// ──────────────────────────────────────────────
// 场景
// ──────────────────────────────────────────────
[MenuItem("BaseGames/Tools/Maintenance/Missing Scripts/Clear In Scene")]
public static void ClearMissingScriptsInScene()
{
int totalRemoved = 0;
var affected = new List<GameObject>();
foreach (var go in GetAllSceneObjects())
{
int removed = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(go);
if (removed > 0)
{
affected.Add(go);
totalRemoved += removed;
EditorUtility.SetDirty(go);
Debug.Log($"[MissingScriptCleaner] 场景已清理:{GetFullPath(go)}", go);
}
}
if (totalRemoved == 0)
Debug.Log("[MissingScriptCleaner] 场景中未发现丢失脚本。");
else
Debug.Log($"[MissingScriptCleaner] 场景完成。共移除 {totalRemoved} 个丢失脚本,影响 {affected.Count} 个 GameObject。");
}
[MenuItem("BaseGames/Tools/Maintenance/Missing Scripts/Find In Scene")]
public static void FindMissingScriptsInScene()
{
int totalFound = 0;
foreach (var go in GetAllSceneObjects())
{
foreach (var component in go.GetComponents<Component>())
{
if (component == null)
{
Debug.LogWarning($"[MissingScriptCleaner] 场景丢失脚本:{GetFullPath(go)}", go);
totalFound++;
break;
}
}
}
if (totalFound == 0)
Debug.Log("[MissingScriptCleaner] 场景中未发现丢失脚本。");
else
Debug.LogWarning($"[MissingScriptCleaner] 场景共发现 {totalFound} 个含丢失脚本的 GameObject。");
}
// ──────────────────────────────────────────────
// Prefab 资产
// ──────────────────────────────────────────────
[MenuItem("BaseGames/Tools/Maintenance/Missing Scripts/Clear In All Prefabs")]
public static void ClearMissingScriptsInPrefabs()
{
int totalRemoved = 0;
int affectedPrefabs = 0;
string[] guids = AssetDatabase.FindAssets("t:Prefab");
try
{
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
if (!path.StartsWith("Assets/")) continue; // 跳过 Packages 等只读路径
if (EditorUtility.DisplayCancelableProgressBar(
"清理 Prefab 丢失脚本",
path,
(float)i / guids.Length))
break;
int removed = CleanPrefabAtPath(path);
if (removed > 0)
{
totalRemoved += removed;
affectedPrefabs++;
}
}
}
finally
{
EditorUtility.ClearProgressBar();
AssetDatabase.SaveAssets();
}
if (totalRemoved == 0)
Debug.Log("[MissingScriptCleaner] 所有 Prefab 中未发现丢失脚本。");
else
Debug.Log($"[MissingScriptCleaner] Prefab 完成。共移除 {totalRemoved} 个丢失脚本,影响 {affectedPrefabs} 个 Prefab。");
}
[MenuItem("BaseGames/Tools/Maintenance/Missing Scripts/Find In All Prefabs")]
public static void FindMissingScriptsInPrefabs()
{
int totalFound = 0;
string[] guids = AssetDatabase.FindAssets("t:Prefab");
try
{
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
if (!path.StartsWith("Assets/")) continue; // 跳过 Packages 等只读路径
if (EditorUtility.DisplayCancelableProgressBar(
"查找 Prefab 丢失脚本",
path,
(float)i / guids.Length))
break;
var contentsRoot = PrefabUtility.LoadPrefabContents(path);
if (contentsRoot == null) continue;
try
{
var tempScene = contentsRoot.scene;
foreach (var go in Resources.FindObjectsOfTypeAll<GameObject>())
{
if (go.scene != tempScene) continue;
foreach (var component in go.GetComponents<Component>())
{
if (component == null)
{
Debug.LogWarning($"[MissingScriptCleaner] Prefab 丢失脚本:{path} → {go.name}");
totalFound++;
break;
}
}
}
}
finally
{
PrefabUtility.UnloadPrefabContents(contentsRoot);
}
}
}
finally
{
EditorUtility.ClearProgressBar();
}
if (totalFound == 0)
Debug.Log("[MissingScriptCleaner] 所有 Prefab 中未发现丢失脚本。");
else
Debug.LogWarning($"[MissingScriptCleaner] Prefab 共发现 {totalFound} 个含丢失脚本的 GameObject。");
}
// ──────────────────────────────────────────────
// 内部辅助
// ──────────────────────────────────────────────
static int CleanPrefabAtPath(string path)
{
// LoadPrefabContents 在临时预览场景中打开 Prefab
// 此时 Resources.FindObjectsOfTypeAll 可枚举到包括 HideInHierarchy 在内的全部对象。
// 修改完毕后必须用 SaveAsPrefabAsset 写回,否则隐藏对象的改动不会持久化。
var contentsRoot = PrefabUtility.LoadPrefabContents(path);
if (contentsRoot == null) return 0;
int totalRemoved = 0;
try
{
var tempScene = contentsRoot.scene;
foreach (var go in Resources.FindObjectsOfTypeAll<GameObject>())
{
if (go.scene != tempScene) continue;
int removed = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(go);
if (removed > 0)
{
totalRemoved += removed;
Debug.Log($"[MissingScriptCleaner] Prefab 已清理:{path} → {go.name}");
}
}
if (totalRemoved > 0)
PrefabUtility.SaveAsPrefabAsset(contentsRoot, path);
}
finally
{
PrefabUtility.UnloadPrefabContents(contentsRoot);
}
return totalRemoved;
}
static GameObject[] GetAllSceneObjects()
{
// FindObjectsByType 不返回 HideFlags.HideInHierarchy 的对象,
// 使用 Resources.FindObjectsOfTypeAll 获取全部对象,再过滤出已加载场景内的实例。
var all = Resources.FindObjectsOfTypeAll<GameObject>();
var result = new List<GameObject>();
foreach (var go in all)
{
if (go.scene.IsValid() && go.scene.isLoaded)
result.Add(go);
}
return result.ToArray();
}
static string GetFullPath(GameObject go)
{
var path = go.name;
var parent = go.transform.parent;
while (parent != null)
{
path = parent.name + "/" + path;
parent = parent.parent;
}
return path;
}
}
}