Skip to content

Instantly share code, notes, and snippets.

@pyoneerC
Created July 4, 2024 02:10
Show Gist options
  • Save pyoneerC/be46ba4c28baf04abbafbf35f407eec1 to your computer and use it in GitHub Desktop.
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.
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