Files
2026-03-18 16:39:56 +07:00

347 lines
10 KiB
C#

using System.IO;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace BrewMonster.Scripts.Utils.Editor
{
public static class ClearMissingScript
{
[MenuItem("Tools/Brew Monster/Clear Missing Script/Active Scene")]
public static void ClearMissingScriptsInActiveScene()
{
var scene = SceneManager.GetActiveScene();
if (!scene.IsValid())
{
EditorUtility.DisplayDialog("Clear Missing Script", "No valid active scene.", "OK");
return;
}
int removedCount = 0;
var roots = scene.GetRootGameObjects();
foreach (var root in roots)
{
Undo.RegisterFullObjectHierarchyUndo(root, "Clear Missing Scripts");
removedCount += RemoveMissingScriptsRecursive(root);
}
if (removedCount > 0)
{
EditorSceneManager.MarkSceneDirty(scene);
}
EditorUtility.DisplayDialog("Clear Missing Script", $"Removed {removedCount} missing script(s) from active scene.", "OK");
}
[MenuItem("Tools/Brew Monster/Clear Missing Script/Prefabs in Selected Folder", true)]
public static bool ValidateClearMissingScriptsInSelectedFolder()
{
Object selectedObject = Selection.activeObject;
if (selectedObject == null)
return false;
string folderPath = AssetDatabase.GetAssetPath(selectedObject);
return AssetDatabase.IsValidFolder(folderPath);
}
[MenuItem("Tools/Brew Monster/Clear Missing Script/Prefabs in Selected Folder")]
public static void ClearMissingScriptsInSelectedFolder()
{
Object selectedObject = Selection.activeObject;
if (selectedObject == null)
{
EditorUtility.DisplayDialog("Clear Missing Script",
"Please select a folder in the Project window first.", "OK");
return;
}
string folderPath = AssetDatabase.GetAssetPath(selectedObject);
if (!AssetDatabase.IsValidFolder(folderPath))
{
EditorUtility.DisplayDialog("Clear Missing Script",
"Selected object is not a valid folder. Please select a folder in the Project window.", "OK");
return;
}
if (!EditorUtility.DisplayDialog("Clear Missing Script in Folder",
$"This will process all prefabs in:\n{folderPath}\n\nContinue?",
"Yes", "Cancel"))
{
return;
}
ProcessPrefabsInFolder(folderPath);
}
[MenuItem("Tools/Brew Monster/Clear Missing Script/All Prefabs")]
public static void ClearMissingScriptsInPrefabs()
{
if (!EditorUtility.DisplayDialog("Clear Missing Script in Prefabs",
"This will process all prefabs in the project. This may take a while. Continue?",
"Yes", "Cancel"))
{
return;
}
ProcessPrefabsInFolder("Assets");
}
[MenuItem("Tools/Brew Monster/Set Addressable For Asset In Selected File (Group: models)")]
public static bool SetAddressableForAssetInSelectedFileGroupModels()
{
Object selectedObject = Selection.activeObject;
if (selectedObject == null)
return false;
AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings;
if (settings == null)
{
EditorUtility.DisplayDialog("Set Addressable", "Addressable settings not found.", "OK");
return false;
}
AddressableAssetGroup modelsGroup = GetOrCreateAddressableGroup(settings, "models");
if (modelsGroup == null)
{
EditorUtility.DisplayDialog("Set Addressable", "Could not find or create Addressable group: models", "OK");
return false;
}
string inputFilePath = AssetDatabase.GetAssetPath(selectedObject);
if (!string.IsNullOrEmpty(inputFilePath))
{
inputFilePath = inputFilePath.Replace("Assets/", "");
inputFilePath = Path.Combine(Application.dataPath, inputFilePath);
if (!File.Exists(inputFilePath))
{
EditorUtility.DisplayDialog("Set Addressable", $"Input file does not exist:\n{inputFilePath}", "OK");
return false;
}
string[] lines = File.ReadAllLines(inputFilePath);
int addedOrMovedCount = 0;
int skippedCount = 0;
foreach (string rawLine in lines)
{
if (string.IsNullOrWhiteSpace(rawLine))
continue;
string assetPath = rawLine.Trim().Trim('"').Replace("\\", "/");
if (assetPath.StartsWith(Application.dataPath.Replace("\\", "/")))
{
assetPath = "Assets" + assetPath.Substring(Application.dataPath.Replace("\\", "/").Length);
}
else if (!assetPath.StartsWith("Assets/"))
{
assetPath = assetPath.TrimStart('/');
assetPath = "Assets/" + assetPath;
}
string guid = AssetDatabase.AssetPathToGUID(assetPath);
if (string.IsNullOrEmpty(guid))
{
skippedCount++;
Debug.LogWarning($"[SetAddressableForAssetInSelectedFile] Invalid asset path in file: {assetPath}");
continue;
}
AddressableAssetEntry entry = settings.CreateOrMoveEntry(guid, modelsGroup);
if (entry == null)
{
skippedCount++;
Debug.LogWarning($"[SetAddressableForAssetInSelectedFile] Failed to create/move entry: {assetPath}");
continue;
}
entry.SetAddress(assetPath);
addedOrMovedCount++;
}
if (addedOrMovedCount > 0)
{
settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryModified, null, true);
AssetDatabase.SaveAssets();
}
EditorUtility.DisplayDialog(
"Set Addressable",
$"Processed {lines.Length} line(s).\nAdded/Moved: {addedOrMovedCount}\nSkipped: {skippedCount}\nGroup: models",
"OK");
}
return true;
}
private static AddressableAssetGroup GetOrCreateAddressableGroup(AddressableAssetSettings settings, string groupName)
{
if (settings == null || string.IsNullOrEmpty(groupName))
return null;
AddressableAssetGroup group = settings.FindGroup(groupName);
if (group != null)
return group;
group = settings.CreateGroup(groupName, false, false, true, null);
if (group != null)
{
EditorUtility.SetDirty(settings);
AssetDatabase.SaveAssets();
}
return group;
}
private static void ProcessPrefabsInFolder(string folderPath)
{
string[] prefabGUIDs = AssetDatabase.FindAssets("t:Prefab", new[] { folderPath });
int totalPrefabs = prefabGUIDs.Length;
if (totalPrefabs == 0)
{
EditorUtility.DisplayDialog("Clear Missing Script",
$"No prefabs found in folder:\n{folderPath}", "OK");
return;
}
int processedCount = 0;
int totalRemovedCount = 0;
int prefabsWithRemovals = 0;
EditorUtility.DisplayProgressBar("Clear Missing Scripts", "Processing prefabs...", 0f);
try
{
foreach (string guid in prefabGUIDs)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
processedCount++;
EditorUtility.DisplayProgressBar("Clear Missing Scripts",
$"Processing: {assetPath} ({processedCount}/{totalPrefabs})",
(float)processedCount / totalPrefabs);
GameObject prefabRoot = PrefabUtility.LoadPrefabContents(assetPath);
if (prefabRoot != null)
{
int removedCount = RemoveMissingScriptsRecursive(prefabRoot);
if (removedCount > 0)
{
PrefabUtility.SaveAsPrefabAsset(prefabRoot, assetPath);
totalRemovedCount += removedCount;
prefabsWithRemovals++;
Debug.Log($"[ClearMissingScript] Removed {removedCount} missing script(s) from prefab: {assetPath}");
}
PrefabUtility.UnloadPrefabContents(prefabRoot);
}
}
AssetDatabase.SaveAssets();
}
finally
{
EditorUtility.ClearProgressBar();
}
EditorUtility.DisplayDialog("Clear Missing Script",
$"Processed {processedCount} prefab(s) in folder:\n{folderPath}\n\nRemoved {totalRemovedCount} missing script(s) from {prefabsWithRemovals} prefab(s).",
"OK");
}
[MenuItem("Tools/Brew Monster/Clear Missing Script/Scene and Prefabs")]
public static void ClearMissingScriptsInSceneAndPrefabs()
{
if (!EditorUtility.DisplayDialog("Clear Missing Script",
"This will process the active scene and all prefabs in the project. This may take a while. Continue?",
"Yes", "Cancel"))
{
return;
}
int sceneRemovedCount = 0;
var scene = SceneManager.GetActiveScene();
if (scene.IsValid())
{
var roots = scene.GetRootGameObjects();
foreach (var root in roots)
{
Undo.RegisterFullObjectHierarchyUndo(root, "Clear Missing Scripts");
sceneRemovedCount += RemoveMissingScriptsRecursive(root);
}
if (sceneRemovedCount > 0)
{
EditorSceneManager.MarkSceneDirty(scene);
}
}
string[] prefabGUIDs = AssetDatabase.FindAssets("t:Prefab");
int totalPrefabs = prefabGUIDs.Length;
int processedCount = 0;
int prefabRemovedCount = 0;
int prefabsWithRemovals = 0;
EditorUtility.DisplayProgressBar("Clear Missing Scripts", "Processing prefabs...", 0f);
try
{
foreach (string guid in prefabGUIDs)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
processedCount++;
EditorUtility.DisplayProgressBar("Clear Missing Scripts",
$"Processing: {assetPath} ({processedCount}/{totalPrefabs})",
(float)processedCount / totalPrefabs);
GameObject prefabRoot = PrefabUtility.LoadPrefabContents(assetPath);
if (prefabRoot != null)
{
int removedCount = RemoveMissingScriptsRecursive(prefabRoot);
if (removedCount > 0)
{
PrefabUtility.SaveAsPrefabAsset(prefabRoot, assetPath);
prefabRemovedCount += removedCount;
prefabsWithRemovals++;
}
PrefabUtility.UnloadPrefabContents(prefabRoot);
}
}
AssetDatabase.SaveAssets();
}
finally
{
EditorUtility.ClearProgressBar();
}
int totalRemoved = sceneRemovedCount + prefabRemovedCount;
EditorUtility.DisplayDialog("Clear Missing Script",
$"Scene: Removed {sceneRemovedCount} missing script(s).\n" +
$"Prefabs: Processed {processedCount} prefab(s), removed {prefabRemovedCount} missing script(s) from {prefabsWithRemovals} prefab(s).\n" +
$"Total: {totalRemoved} missing script(s) removed.",
"OK");
}
private static int RemoveMissingScriptsRecursive(GameObject gameObject)
{
int removed = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(gameObject);
for (int i = 0; i < gameObject.transform.childCount; i++)
{
removed += RemoveMissingScriptsRecursive(gameObject.transform.GetChild(i).gameObject);
}
return removed;
}
}
}