AkiraPixelWind/Assets/Scripts/Editor/ProjectTools/NoReferenceWindow.cs
2022-12-29 18:20:40 +08:00

519 lines
15 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}