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