519 lines
15 KiB
C#
519 lines
15 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Text.RegularExpressions;
|
||
using UnityEditor;
|
||
using UnityEditor.SceneManagement;
|
||
using UnityEditor.UI;
|
||
using UnityEngine;
|
||
using Object = UnityEngine.Object;
|
||
|
||
|
||
public class NoReferenceWindow : EditorWindow
|
||
{
|
||
#if UNITY_EDITOR
|
||
private enum AssetType
|
||
{
|
||
None,
|
||
Prefab,
|
||
Material,
|
||
Scene,
|
||
Asset,
|
||
Lua,
|
||
Image,
|
||
FBX,
|
||
Controller,
|
||
Animation,
|
||
}
|
||
|
||
private class FileInfo
|
||
{
|
||
public string path;
|
||
public AssetType assetType;
|
||
public Object asset;
|
||
}
|
||
|
||
private static string[] ASSET_FILE_ROOT = { "Assets/Resources", "Assets/GameAssets", "Assets/Scene", "Assets/StreamingAssets" };
|
||
// 过滤文件夹
|
||
private static string[] FilterDirectory = { "Assets/Resources/UI/GenAtlas" };
|
||
|
||
private Dictionary<string, FileInfo> allFileDic = null;
|
||
// key:luaPath,value:luaContent
|
||
private Dictionary<string, string> luaFileContentDic = null;
|
||
private List<FileInfo> assetDependsList = null;
|
||
// 未被引用资源
|
||
private Dictionary<AssetType, List<FileInfo>> noReferenceAssetDic = null;
|
||
private Vector2 ve2;
|
||
private bool initComplete = false;
|
||
// 筛选文件夹
|
||
private List<string> siftPathList = new List<string>();
|
||
// 筛选文件夹中未使用的文件
|
||
private Dictionary<AssetType, List<FileInfo>> noReferenceSiftAssetDic = null;
|
||
|
||
private bool isAll = true;
|
||
|
||
private void Awake()
|
||
{
|
||
siftPathList.Add("Assets/GameAssets");
|
||
}
|
||
|
||
// 绘制编辑器窗口内容
|
||
private void OnGUI()
|
||
{
|
||
EditorGUILayout.Space();
|
||
|
||
if (initComplete == false)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (noReferenceAssetDic == null)
|
||
{
|
||
InitAllFileDic();
|
||
return;
|
||
}
|
||
|
||
|
||
DrawSift();
|
||
ve2 = EditorGUILayout.BeginScrollView(ve2);
|
||
|
||
var tmpNoRefernceDic = noReferenceAssetDic;
|
||
if (!isAll)
|
||
{
|
||
tmpNoRefernceDic = noReferenceSiftAssetDic;
|
||
}
|
||
|
||
foreach (var assetType in tmpNoRefernceDic.Keys)
|
||
{
|
||
List<FileInfo> infos = tmpNoRefernceDic[assetType];
|
||
if (infos.Count == 0) continue;
|
||
EditorGUILayout.LabelField(assetType.ToString(), EditorStyles.boldLabel);
|
||
for (int i = 0; i < infos.Count; i++)
|
||
{
|
||
CreateNpReferenceItem(infos[i]);
|
||
}
|
||
|
||
if (GUILayout.Button("一键删除", GUILayout.Height(30)))
|
||
{
|
||
DeleteAllAsset(assetType);
|
||
}
|
||
EditorGUILayout.Space();
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
EditorGUILayout.Space();
|
||
}
|
||
|
||
[MenuItem("项目工具/资源检查/检查未被引用的资源", false, 10)]
|
||
private static void OpenNoReferenceWindow()
|
||
{
|
||
NoReferenceWindow window = (NoReferenceWindow)EditorWindow.GetWindow(typeof(NoReferenceWindow), true, "未被引用资源列表");
|
||
window.InitAllFileDic();
|
||
}
|
||
|
||
public void RefreshInfo()
|
||
{
|
||
InitAssetDependDic();
|
||
InitLuaFileContentDic();
|
||
InitNoReferenceAssetDic();
|
||
|
||
initComplete = true;
|
||
}
|
||
|
||
private bool IsInFilterDirectory(string assetPath)
|
||
{
|
||
for (int i = 0; i < FilterDirectory.Length; i++)
|
||
{
|
||
if (assetPath.Contains(FilterDirectory[i]))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 找出目标路径下的所有资源
|
||
/// </summary>
|
||
private void InitAllFileDic()
|
||
{
|
||
Debug.Log("开始扫描");
|
||
long t = System.DateTime.Now.Ticks;
|
||
|
||
allFileDic = new Dictionary<string, FileInfo>();
|
||
string[] guids = AssetDatabase.FindAssets("", ASSET_FILE_ROOT);
|
||
|
||
for (int i = 0; i < guids.Length; i++)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
|
||
|
||
if (EditorUtility.DisplayCancelableProgressBar($"资源加载中:({i}/{guids.Length})", path,
|
||
(float)i / (float)guids.Length))
|
||
{
|
||
EditorUtility.ClearProgressBar();
|
||
Close();
|
||
return;
|
||
}
|
||
|
||
if (IsInFilterDirectory(path)) continue;
|
||
|
||
AssetType assetType = GetAssetTypeByPath(path);
|
||
if (assetType == AssetType.None) continue;
|
||
FileInfo info = new FileInfo();
|
||
info.path = path;
|
||
info.assetType = assetType;
|
||
info.asset = AssetDatabase.LoadAssetAtPath<Object>(path);
|
||
allFileDic.Add(path, info);
|
||
}
|
||
|
||
EditorUtility.ClearProgressBar();
|
||
Debug.Log("资源扫描完成");
|
||
RefreshInfo();
|
||
|
||
Debug.Log($"耗时:{(System.DateTime.Now.Ticks - t) / 10000} 毫秒");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 找出资源的依赖,并添加到依赖列表中
|
||
/// </summary>
|
||
private void InitAssetDependDic()
|
||
{
|
||
assetDependsList = new List<FileInfo>();
|
||
List<FileInfo> infos = new List<FileInfo>(allFileDic.Values);
|
||
if (infos.Count == 0) return;
|
||
for (int i = 0; i < infos.Count; i++)
|
||
{
|
||
FileInfo info = infos[i];
|
||
|
||
if (info.assetType == AssetType.Lua || info.assetType == AssetType.Image) continue;
|
||
|
||
if (EditorUtility.DisplayCancelableProgressBar("扫描资源依赖关系...", $"({i}/{infos.Count})\n path:{info.path}",
|
||
(float)i / (float)infos.Count))
|
||
{
|
||
EditorUtility.ClearProgressBar();
|
||
Close();
|
||
return;
|
||
}
|
||
string[] depends = AssetDatabase.GetDependencies(info.path, false);
|
||
|
||
for (int j = 0; j < depends.Length; j++)
|
||
{
|
||
string dependPath = depends[j];
|
||
|
||
FileInfo dependInfo = FindFileInfo(dependPath);
|
||
|
||
// 依赖项为空或者是自己不需要添加
|
||
if (dependInfo == null || dependInfo == info) continue;
|
||
if (!assetDependsList.Contains(dependInfo))
|
||
{
|
||
assetDependsList.Add(dependInfo);
|
||
}
|
||
}
|
||
}
|
||
|
||
Debug.Log("扫描资源依赖关系完成");
|
||
EditorUtility.ClearProgressBar();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取lua文件内容,并保存到列表中(由于项目是用lua热更新的,配置表最终也是生成lua文件,所以lua文件中会配置了引用资源的名称)
|
||
/// </summary>
|
||
private void InitLuaFileContentDic()
|
||
{
|
||
luaFileContentDic = new Dictionary<string, string>();
|
||
|
||
foreach (var info in allFileDic.Values)
|
||
{
|
||
if (info.assetType == AssetType.Lua)
|
||
{
|
||
luaFileContentDic.Add(info.path, File.ReadAllText(info.path));
|
||
}
|
||
}
|
||
}
|
||
|
||
// 初始化未被引用资源
|
||
private void InitNoReferenceAssetDic()
|
||
{
|
||
noReferenceAssetDic = new Dictionary<AssetType, List<FileInfo>>();
|
||
|
||
foreach (var info in allFileDic.Values)
|
||
{
|
||
if (info.assetType == AssetType.Lua) continue;
|
||
|
||
// 判断资源是否被引用(依赖列表中的引用,lua脚本内容列表中的引用)
|
||
// 被其他资源引用
|
||
if (CheckOtherAssetReference(info.path)) continue;
|
||
// 被lua脚本引用
|
||
if (CheckLuaReference(info.path)) continue;
|
||
|
||
AddNoReferenceAsset(info);
|
||
}
|
||
|
||
Debug.Log("未被引用资源添加完成");
|
||
}
|
||
|
||
private FileInfo FindFileInfo(string assetPath)
|
||
{
|
||
if (!allFileDic.ContainsKey(assetPath)) return null;
|
||
return allFileDic[assetPath];
|
||
}
|
||
|
||
private void AddNoReferenceAsset(FileInfo info)
|
||
{
|
||
List<FileInfo> list = null;
|
||
if (!noReferenceAssetDic.TryGetValue(info.assetType, out list))
|
||
{
|
||
list = new List<FileInfo>();
|
||
noReferenceAssetDic.Add(info.assetType, list);
|
||
}
|
||
list.Add(info);
|
||
}
|
||
|
||
private void RemoveNoReferenceAsset(Dictionary<AssetType, List<FileInfo>> noReferenceAsset, string path)
|
||
{
|
||
FileInfo info = FindFileInfo(path);
|
||
if (info == null) return;
|
||
|
||
List<FileInfo> list = null;
|
||
if (noReferenceAsset.TryGetValue(info.assetType, out list))
|
||
{
|
||
list.Remove(info);
|
||
}
|
||
}
|
||
|
||
private AssetType GetAssetTypeByPath(string assetPath)
|
||
{
|
||
string extension = Path.GetExtension(assetPath);
|
||
switch (extension.ToLower())
|
||
{
|
||
case ".prefab": return AssetType.Prefab;
|
||
case ".mat": return AssetType.Material;
|
||
case ".unity": return AssetType.Scene;
|
||
case ".asset": return AssetType.Asset;
|
||
case ".lua": return AssetType.Lua;
|
||
case ".png": return AssetType.Image;
|
||
case ".jpg": return AssetType.Image;
|
||
case ".fbx": return AssetType.FBX;
|
||
case ".controller": return AssetType.Controller;
|
||
case ".anim": return AssetType.Animation;
|
||
}
|
||
return AssetType.None;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查被其他资源引用
|
||
/// </summary>
|
||
/// <param name="assetPath"></param>
|
||
/// <returns></returns>
|
||
private bool CheckOtherAssetReference(string assetPath)
|
||
{
|
||
if (string.IsNullOrEmpty(assetPath))
|
||
{
|
||
Debug.LogError("CheckOtherAssetReference error: assetPath is null");
|
||
return false;
|
||
}
|
||
|
||
FileInfo info = FindFileInfo(assetPath);
|
||
if (info == null) return false;
|
||
|
||
if (info.assetType == AssetType.Scene)
|
||
{
|
||
return EditorSceneManager.GetActiveScene().name.Equals(Path.GetFileNameWithoutExtension(assetPath));
|
||
}
|
||
|
||
foreach (var dependInfo in assetDependsList)
|
||
{
|
||
if (dependInfo == info)
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 检查被lua脚本引用
|
||
private bool CheckLuaReference(string assetPath)
|
||
{
|
||
string fileName = Path.GetFileNameWithoutExtension(assetPath);
|
||
|
||
if (string.IsNullOrEmpty(fileName))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
foreach (var content in luaFileContentDic.Values)
|
||
{
|
||
if (Regex.IsMatch(content, fileName + "\"") || Regex.IsMatch(content, fileName + "\'"))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private void DeleteAsset(FileInfo info, bool refresh = false)
|
||
{
|
||
RemoveDepends(info);
|
||
RemoveNoReferenceAsset(noReferenceAssetDic, info.path);
|
||
if (!isAll && noReferenceSiftAssetDic != null) RemoveNoReferenceAsset(noReferenceSiftAssetDic, info.path);
|
||
allFileDic.Remove(info.path);
|
||
AssetDatabase.DeleteAsset(info.path);
|
||
if (refresh)
|
||
AssetDatabase.Refresh();
|
||
}
|
||
|
||
// 删除资源
|
||
private void DeleteAllAsset(AssetType assetType)
|
||
{
|
||
List<FileInfo> infos = noReferenceAssetDic[assetType];
|
||
for (int i = infos.Count - 1; i >= 0; i--)
|
||
{
|
||
DeleteAsset(infos[i]);
|
||
}
|
||
AssetDatabase.Refresh();
|
||
}
|
||
|
||
// 移除依赖数据
|
||
private void RemoveDepends(FileInfo info)
|
||
{
|
||
if (assetDependsList.Contains(info))
|
||
{
|
||
assetDependsList.Remove(info);
|
||
}
|
||
}
|
||
|
||
// 初始化筛选文件中未被引用资源
|
||
private void RefreshNoReferenceSiltAssetDic()
|
||
{
|
||
noReferenceSiftAssetDic = new Dictionary<AssetType, List<FileInfo>>();
|
||
|
||
foreach (var info in allFileDic.Values)
|
||
{
|
||
if (info.assetType == AssetType.Lua) continue;
|
||
|
||
if (!CheckInSiltDirectory(info.path)) continue;
|
||
|
||
// 判断资源是否被引用(依赖列表中的引用,lua脚本内容列表中的引用)
|
||
// 被其他资源引用
|
||
if (CheckOtherAssetReference(info.path)) continue;
|
||
// 被lua脚本引用
|
||
if (CheckLuaReference(info.path)) continue;
|
||
|
||
AddNoReferenceSiltAsset(info);
|
||
}
|
||
}
|
||
|
||
private bool CheckInSiltDirectory(string assetPath)
|
||
{
|
||
for (int i = 0; i < siftPathList.Count; i++)
|
||
{
|
||
string path = siftPathList[i];
|
||
if (string.IsNullOrEmpty(path)) continue;
|
||
if (!Directory.Exists(path)) continue;
|
||
if (Regex.IsMatch(assetPath, path))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private void AddNoReferenceSiltAsset(FileInfo info)
|
||
{
|
||
List<FileInfo> list = null;
|
||
if (!noReferenceSiftAssetDic.TryGetValue(info.assetType, out list))
|
||
{
|
||
list = new List<FileInfo>();
|
||
noReferenceSiftAssetDic.Add(info.assetType, list);
|
||
}
|
||
list.Add(info);
|
||
}
|
||
|
||
|
||
#region // ---------------------- 绘制相关 ------------------------- //
|
||
private void CreateNpReferenceItem(FileInfo info)
|
||
{
|
||
GUILayout.BeginHorizontal();
|
||
// var obj = AssetDatabase.LoadAssetAtPath<Object>(info.path);
|
||
EditorGUILayout.ObjectField(info.asset, typeof(UnityEngine.Object));
|
||
// string assetName = Path.GetFileNameWithoutExtension(info.path);
|
||
// EditorGUILayout.DelayedTextField(assetName);
|
||
// if (GUILayout.Button("定位",GUILayout.Width(70)))
|
||
// {
|
||
// var obj = AssetDatabase.LoadAssetAtPath<Object>(info.path);
|
||
// if(obj != null)
|
||
// EditorGUIUtility.PingObject(obj);
|
||
// }
|
||
|
||
if (GUILayout.Button("删除资源", GUILayout.Width(70)))
|
||
{
|
||
DeleteAsset(info, true);
|
||
}
|
||
GUILayout.EndHorizontal();
|
||
}
|
||
|
||
private void DrawSift()
|
||
{
|
||
EditorGUILayout.LabelField("*注意:有些资源仅靠代码加载,注意区分。", EditorStyles.boldLabel, GUILayout.Height(30));
|
||
EditorGUILayout.LabelField("筛选列表", EditorStyles.boldLabel, GUILayout.Height(30));
|
||
for (int i = 0; i < siftPathList.Count; i++)
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("Path:", GUILayout.Width(50));
|
||
siftPathList[i] = EditorGUILayout.TextField(siftPathList[i]);
|
||
if (GUILayout.Button("x", GUILayout.Width(20)))
|
||
{
|
||
siftPathList.RemoveAt(i);
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
if (GUILayout.Button("+", GUILayout.Height(20)))
|
||
{
|
||
siftPathList.Add(string.Empty);
|
||
}
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
if (GUILayout.Button("筛选资源", GUILayout.Height(40)))
|
||
{
|
||
string noExistDirectoryStr = "";
|
||
|
||
for (int i = 0; i < siftPathList.Count; i++)
|
||
{
|
||
string path = siftPathList[i];
|
||
if (string.IsNullOrEmpty(path)) continue;
|
||
if (!Directory.Exists(path))
|
||
{
|
||
noExistDirectoryStr = noExistDirectoryStr + path + "\r\n";
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (noExistDirectoryStr != "")
|
||
{
|
||
EditorUtility.DisplayDialog("提示", "文件夹不存在:\r\n" + noExistDirectoryStr, "ok");
|
||
}
|
||
|
||
RefreshNoReferenceSiltAssetDic();
|
||
isAll = false;
|
||
}
|
||
|
||
if (GUILayout.Button("全部资源", GUILayout.Height(40)))
|
||
{
|
||
isAll = true;
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space();
|
||
EditorGUILayout.Space();
|
||
}
|
||
|
||
#endregion // ---------------------- 绘制相关 ------------------------- //
|
||
|
||
|
||
|
||
#endif
|
||
} |