Created
July 4, 2024 02:10
-
-
Save pyoneerC/be46ba4c28baf04abbafbf35f407eec1 to your computer and use it in GitHub Desktop.
This C# script for Unity, named UnityCleaner, is designed to enhance project management within the Unity Editor. It provides a graphical user interface accessible under the "Tools" menu as "Project Cleaner". It let us delete unused files in our code base and filter them, with multi scene support.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace PyoneerC.UnityCleaner | |
{ | |
using UnityEditor; | |
using UnityEngine; | |
using UnityEditor.SceneManagement; | |
using UnityEngine.SceneManagement; | |
using System.Collections.Generic; | |
using System.Linq; | |
using JetBrains.Annotations; | |
public class UnityCleaner : EditorWindow | |
{ | |
private static readonly List<string> PendingDeletions = new(); | |
private static AssetTypeFilter _selectedTypeFilter = AssetTypeFilter.All; | |
[MenuItem("Tools/Project Cleaner")] | |
public static void ShowWindow() | |
{ | |
GetWindow<UnityCleaner>("Project Cleaner"); | |
} | |
private static void DeleteAllUnusedAssets() | |
{ | |
var total = PendingDeletions.Count; | |
for (var index = 0; index < total; ++index) | |
{ | |
var progress = index / (float)total; | |
EditorUtility.DisplayProgressBar("Deleting Unused Assets", $"Deleting {PendingDeletions[index]}...", | |
progress); | |
AssetDatabase.DeleteAsset(PendingDeletions[index]); | |
// Delay to allow the progress bar to update | |
System.Threading.Thread.Sleep(10); | |
} | |
PendingDeletions.Clear(); | |
AssetDatabase.Refresh(); | |
EditorUtility.ClearProgressBar(); | |
} | |
private Vector2 _scrollPosition; | |
private void OnGUI() | |
{ | |
_scrollPosition = GUILayout.BeginScrollView(_scrollPosition); | |
_selectedTypeFilter = (AssetTypeFilter)EditorGUILayout.EnumPopup("Filter by Type:", _selectedTypeFilter); | |
if (GUILayout.Button("Find Unused Assets")) | |
{ | |
FindUnusedAssets(); | |
if (PendingDeletions.Count == 0) | |
{ | |
EditorUtility.DisplayDialog("No Unused Assets", "No unused assets found.", "OK"); | |
} | |
} | |
if (PendingDeletions.Count > 0) | |
{ | |
GUILayout.Label("Unused Assets:"); | |
var assetsToDelete = new List<string>(); | |
IEnumerable<string> filteredAssets = PendingDeletions; | |
if (_selectedTypeFilter != AssetTypeFilter.All) | |
{ | |
var filterPrefix = $"[{_selectedTypeFilter}]"; | |
filteredAssets = PendingDeletions.Where(asset => asset.StartsWith(filterPrefix)); | |
} | |
foreach (var asset in filteredAssets) | |
{ | |
GUILayout.BeginHorizontal(); | |
GUILayout.Label(asset, GUILayout.Width(250)); | |
if (GUILayout.Button("Delete")) | |
{ | |
assetsToDelete.Add(asset); | |
} | |
GUILayout.EndHorizontal(); | |
} | |
foreach (var asset in assetsToDelete) | |
{ | |
AssetDatabase.DeleteAsset(asset[(asset.IndexOf(']') + 2)..]); | |
PendingDeletions.Remove(asset); | |
} | |
if (GUILayout.Button("Delete All")) | |
{ | |
if (EditorUtility.DisplayDialog("Confirm Delete All", | |
"Are you sure you want to delete all unused assets? This action cannot be undone.", | |
"Yes", "No")) | |
{ | |
DeleteAllUnusedAssets(); | |
} | |
} | |
if (GUILayout.Button("Clear List")) | |
{ | |
PendingDeletions.Clear(); | |
Repaint(); | |
} | |
} | |
GUILayout.EndScrollView(); | |
} | |
private static void FindUnusedAssets() | |
{ | |
PendingDeletions.Clear(); | |
var allAssets = AssetDatabase.GetAllAssetPaths(); | |
var usedAssets = new HashSet<string>(); | |
foreach (var scenePath in AssetDatabase.FindAssets("t:Scene").Select(AssetDatabase.GUIDToAssetPath)) | |
{ | |
var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive); | |
foreach (var go in scene.GetRootGameObjects()) | |
{ | |
var dependencies = EditorUtility.CollectDependencies(new Object[] { go }); | |
foreach (var dep in dependencies) | |
{ | |
var path = AssetDatabase.GetAssetPath(dep); | |
if (!string.IsNullOrEmpty(path)) | |
{ | |
usedAssets.Add(path); | |
} | |
} | |
} | |
if (SceneManager.loadedSceneCount > 1) | |
{ | |
EditorSceneManager.CloseScene(scene, true); | |
} | |
} | |
var currentScene = SceneManager.GetActiveScene(); | |
if (!currentScene.path.Equals(SceneManager.GetActiveScene().path)) | |
{ | |
EditorSceneManager.OpenScene(currentScene.path, OpenSceneMode.Single); | |
} | |
foreach (var asset in allAssets) | |
{ | |
if (usedAssets.Contains(asset) || asset.EndsWith(".cs") || asset.StartsWith("Packages/com.") || | |
asset.StartsWith("ProjectSettings") || asset.StartsWith("Library") || | |
AssetDatabase.IsValidFolder(asset) || | |
asset.EndsWith(".unity")) continue; | |
var assetType = AssetDatabase.GetMainAssetTypeAtPath(asset); | |
var typeLabel = GetTypeLabel(assetType); | |
if (_selectedTypeFilter != AssetTypeFilter.All && | |
!typeLabel.Equals($"[{_selectedTypeFilter}]")) continue; | |
var assetName = System.IO.Path.GetFileName(asset); | |
var labeledAsset = $"{typeLabel} {assetName}"; | |
PendingDeletions.Add(labeledAsset); | |
} | |
} | |
private static string GetTypeLabel(System.Type assetType) | |
{ | |
if (assetType == typeof(Texture2D)) return "[Texture]"; | |
if (assetType == typeof(Mesh)) return "[Model]"; | |
if (assetType == typeof(AudioClip)) return "[Audio]"; | |
if (assetType == typeof(Material)) return "[Material]"; | |
if (assetType == typeof(Shader)) return "[Shader]"; | |
return assetType == typeof(AnimationClip) ? "[Animation]" : "[Other]"; | |
} | |
private enum AssetTypeFilter | |
{ | |
All, | |
[UsedImplicitly] Texture, | |
[UsedImplicitly] Model, | |
[UsedImplicitly] Audio, | |
[UsedImplicitly] Material, | |
[UsedImplicitly] Shader, | |
[UsedImplicitly] Animation, | |
[UsedImplicitly] Other | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment