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>
231 lines
9.1 KiB
C#
231 lines
9.1 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|