Unity项目中未使用资源的自动检测与清理方案
背景说明
在长期迭代的Unity项目中,Assets目录会积累大量未被引用的资源文件,如纹理、材质、模型等。这些冗余资产不仅占用磁盘空间,还可能影响构建效率和版本控制。手动排查既耗时又容易出错,因此需要借助自动化工具进行识别和清理。
解决方案一:ResourceChecker - 资源引用分析器
该工具用于扫描当前场景中所有被引用的资源,并提供可视化界面展示其使用情况,帮助开发者定位无用资源。
核心功能包括:
- 扫描场景中所有渲染器、UI元素、动画系统及脚本中的资源引用
- 支持检查禁用对象和精灵动画序列中的资源依赖
- 显示每项资源的内存占用、尺寸、Mipmap层级等信息
- 可一键选中关联的GameObject或材质球进行快速定位
实现机制基于反射与依赖收集:
public class ResourceAnalyzer : EditorWindow
{
private Vector2 scrollPosition;
private List<AssetEntry> textures = new List<AssetEntry>();
private List<MaterialInfo> materials = new List<MaterialInfo>();
[MenuItem("Tools/资源分析器")]
public static void ShowWindow()
{
GetWindow<ResourceAnalyzer>().titleContent = new GUIContent("资源分析");
}
private void OnGUI()
{
EditorGUILayout.LabelField($"已加载纹理: {textures.Count}, 总显存: {GetFormattedMemory()}");
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
foreach (var entry in textures)
{
DrawTextureItem(entry);
}
EditorGUILayout.EndScrollView();
if (GUILayout.Button("刷新数据")) ScanSceneResources();
}
private void ScanSceneResources()
{
textures.Clear();
var renderers = Object.FindObjectsOfType<Renderer>(true);
foreach (var renderer in renderers)
{
ProcessRendererMaterials(renderer);
}
}
private void ProcessRendererMaterials(Renderer renderer)
{
foreach (var mat in renderer.sharedMaterials)
{
if (mat == null) continue;
var mainTex = mat.GetTexture("_MainTex");
if (mainTex != null && !textures.Exists(t => t.texture == mainTex))
{
textures.Add(new AssetEntry
{
texture = mainTex,
sizeKB = CalculateTextureSize(mainTex),
referencedBy = new List<Object> { renderer.gameObject }
});
}
}
}
private int CalculateTextureSize(Texture tex)
{
int bytes = 0;
if (tex is Texture2D t2d)
{
var format = GetBitsPerPixel(t2d.format);
bytes = t2d.width * t2d.height * format / 8;
}
return bytes / 1024;
}
private int GetBitsPerPixel(TextureFormat format)
{
return format switch
{
TextureFormat.RGBA32 or TextureFormat.ARGB32 => 32,
TextureFormat.RGB24 => 24,
TextureFormat.RGBA4444 => 16,
TextureFormat.DXT1 => 4,
TextureFormat.DXT5 => 8,
_ => 32
};
}
private void DrawTextureItem(AssetEntry entry)
{
GUILayout.BeginHorizontal("box");
if (entry.texture)
{
GUILayout.Box(AssetPreview.GetMiniThumbnail(entry.texture), GUILayout.Width(40), GUILayout.Height(40));
GUILayout.Label($"{entry.texture.name} ({entry.sizeKB}KB)", GUILayout.Width(150));
if (GUILayout.Button($"被{entry.referencedBy.Count}个对象使用", GUILayout.Width(120)))
{
Selection.objects = entry.referencedBy.ToArray();
}
}
GUILayout.EndHorizontal();
}
}
class AssetEntry
{
public Texture texture;
public int sizeKB;
public List<Object> referencedBy = new List<Object>();
}
class MaterialInfo
{
public Material material;
public List<Renderer> usedInRenderers = new List<Renderer>();
}
使用方式:菜单栏选择 Tools → 资源分析器 打开面板并点击"刷新数据"即可开始扫描。
解决方案二:SafeAssetCleaner - 安全资产清理工具
此插件专注于安全地移除未使用的资源,并具备自动备份机制,防止误删关键文件。
主要特性:
- 三种清理模式适应不同需求
- 删除前自动生成UnityPackage备份包
- 智能排除Resources、Plugins、Gizmos等特殊目录
- 支持保留编辑器扩展脚本(Editor文件夹下)
清理逻辑流程如下:
- 遍历Assets目录下所有非.meta文件
- 通过AssetDatabase.GetDependencies获取场景与Resources中的引用关系
- 过滤掉有引用的资源,剩余即为候选删除项
- 执行删除操作前导出为时间戳命名的package文件
- 调用AssetDatabase.DeleteAsset逐个移除文件
- 自动清理空文件夹
核心清理类简化实现:
public class SafeAssetCleaner : EditorWindow
{
private List<string> candidates = new List<string>();
private Vector2 scrollPos;
[MenuItem("Assets/清理未使用资源/仅游戏运行时")]
public static void CleanForBuild()
{
var window = CreateInstance<SafeAssetCleaner>();
window.ScanUnusedAssets(includeEditorCode: false);
window.Show();
}
private void ScanUnusedAssets(bool includeEditorCode = true)
{
var allGuids = AssetDatabase.FindAssets("", new[] { "Assets" })
.Where(g => IsTargetAsset(AssetDatabase.GUIDToAssetPath(g)));
var referenced = new HashSet<string>();
// 收集构建相关引用
var scenes = EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray();
foreach (var path in AssetDatabase.GetDependencies(scenes, true))
{
referenced.Add(AssetDatabase.AssetPathToGUID(path));
}
// Resources引用
var resourcePaths = GetResourceFiles();
foreach (var dep in AssetDatabase.GetDependencies(resourcePaths))
{
referenced.Add(AssetDatabase.AssetPathToGUID(dep));
}
candidates = allGuids.Where(guid => !referenced.Contains(guid)).ToList();
}
private bool IsTargetAsset(string path)
{
string ext = Path.GetExtension(path).ToLower();
if (new[] { ".meta", ".dll", ".js" }.Contains(ext)) return false;
if (Regex.IsMatch(path, @"[\\/](Gizmos|Plugins|Resources)[\\/]")) return false;
return true;
}
private string[] GetResourceFiles()
{
return Directory.GetFiles("Assets", "*.*", SearchOption.AllDirectories)
.Where(f => f.Contains("/Resources/") || f.Contains("\\Resources\\"))
.Where(f => Path.GetExtension(f) != ".meta").ToArray();
}
private void OnGUI()
{
EditorGUILayout.HelpBox($"发现 {candidates.Count} 个未使用资源", MessageType.Info);
if (GUILayout.Button("执行清理并备份"))
{
PerformDeletionWithBackup();
}
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
foreach (var guid in candidates.Take(100)) // 限制显示数量
{
var path = AssetDatabase.GUIDToAssetPath(guid);
GUILayout.Label(path.Length > 80 ? path.Substring(0, 77) + "..." : path);
}
EditorGUILayout.EndScrollView();
}
private void PerformDeletionWithBackup()
{
string backupDir = "BackupDeletedAssets";
Directory.CreateDirectory(backupDir);
string pkgName = $"{backupDir}/deleted_{DateTime.Now:yyyyMMdd_HHmmss}.unitypackage";
string[] paths = candidates.Select(AssetDatabase.GUIDToAssetPath).ToArray();
AssetDatabase.ExportPackage(paths, pkgName, ExportPackageOptions.Interactive);
foreach (string path in paths)
{
AssetDatabase.DeleteAsset(path);
}
CleanEmptyFolders("Assets");
AssetDatabase.Refresh();
EditorUtility.RevealInFinder(backupDir);
}
private void CleanEmptyFolders(string root)
{
foreach (string dir in Directory.GetDirectories(root))
{
CleanEmptyFolders(dir);
if (!Directory.EnumerateFiles(dir).Any(f => !f.EndsWith(".meta")) &&
!Directory.GetDirectories(dir).Any())
{
FileUtil.DeleteFileOrDirectory(dir);
FileUtil.DeleteFileOrDirectory(dir + ".meta");
}
}
}
}
使用方法:右键菜单选择 Assets → 清理未使用资源,根据项目阶段选择合适模式后点击确认。
最佳实践建议
- 定期执行资源审查,尤其是在版本发布前
- 优先使用SafeAssetCleaner的备份功能以规避风险
- 结合Git等版本控制系统,确保即使误删也可恢复
- 对Resources目录保持谨慎,因其内容默认包含在构建中