├── Editor ├── AssetCheckerSettings.cs.meta ├── AssetCheckerWindow.GUI.cs.meta ├── ResultTypeIconStyle.cs ├── BuiltinProviders.meta ├── GBG.AssetChecker.Editor.asmdef.meta ├── AssetChecker.cs.meta ├── AssetProvider.cs.meta ├── AssetCheckResult.cs.meta ├── CheckResultStats.cs.meta ├── CheckResultType.cs.meta ├── CustomViewProvider.cs.meta ├── ResultTypeIconStyle.cs.meta ├── AssetCheckerLocalCache.cs.meta ├── CheckResultDetailsView.cs.meta ├── CheckResultEntryView.cs.meta ├── BuiltinProviders │ ├── ExplicitAssetProvider.cs.meta │ ├── SelectionAssetProvider.cs.meta │ ├── TextSearchAssetProvider.cs.meta │ ├── ExplicitAssetProvider.cs │ ├── SelectionAssetProvider.cs │ └── TextSearchAssetProvider.cs ├── AssetProvider.cs ├── AssetCheckerWindow.cs.meta ├── GBG.AssetChecker.Editor.asmdef ├── CustomViewProvider.cs ├── AssetCheckResult.cs ├── AssetCheckerSettings.cs ├── CheckResultStats.cs ├── CheckResultEntryView.cs ├── AssetCheckerLocalCache.cs ├── CheckResultType.cs ├── AssetChecker.cs ├── AssetCheckerWindow.cs ├── AssetCheckerWindow.GUI.cs └── CheckResultDetailsView.cs ├── Documents~ └── imgs │ ├── img_sample_asset_checker_window.png │ └── img_sample_asset_checker_new_settings.png ├── LICENSE.meta ├── README.md.meta ├── README_CN.md.meta ├── package.json.meta ├── Editor.meta ├── package.json ├── LICENSE ├── .gitignore ├── README_CN.md └── README.md /Editor/AssetCheckerSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e75c0a47d71a4625a6c7a43a57b3f80f 3 | timeCreated: 1714195280 -------------------------------------------------------------------------------- /Editor/AssetCheckerWindow.GUI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fedf1df82f184a37bd48c5946ebf0b9c 3 | timeCreated: 1714199142 -------------------------------------------------------------------------------- /Documents~/imgs/img_sample_asset_checker_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolarianZ/UnityAssetChecker/HEAD/Documents~/imgs/img_sample_asset_checker_window.png -------------------------------------------------------------------------------- /Documents~/imgs/img_sample_asset_checker_new_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolarianZ/UnityAssetChecker/HEAD/Documents~/imgs/img_sample_asset_checker_new_settings.png -------------------------------------------------------------------------------- /Editor/ResultTypeIconStyle.cs: -------------------------------------------------------------------------------- 1 | namespace GBG.AssetChecking.Editor 2 | { 3 | public enum ResultTypeIconStyle 4 | { 5 | Style1, 6 | Style2, 7 | Style3 8 | } 9 | } -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dc8b8e95ae4f924449aad800698edda2 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 13763f616ab3acc4b8d1a6496e0a2d34 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README_CN.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2fa4e2c17401a6045bd905ff1122df70 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 275df7006fb1a2b4ca147b04cb7556f3 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7c95e7e38146dd44dbaf8e1f4bb095fd 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/BuiltinProviders.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a76a689caa4f29247bc5b15545a853f9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/GBG.AssetChecker.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4e6095db9a4acdf4f855f84f0d925ab5 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/AssetChecker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3830f4453dd96d74a93b69143c2287ae 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AssetProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4454c208aec4b124e9117668f3446ea2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AssetCheckResult.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4013ed18f34da434495674693d9856cc 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/CheckResultStats.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b2ccdef89613ef74f9d6f2111a86f400 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/CheckResultType.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f2d0b826323f02d4c816c938d1691238 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/CustomViewProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eed1fb3b3ff7bb44e8ee5d3e93c407b4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ResultTypeIconStyle.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9e9f28f4dcbc1af4d8f9efd5f15809cf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AssetCheckerLocalCache.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 43a4fed22883b6448bec5536f6241c73 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/CheckResultDetailsView.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6edeea117a5417f4389bc56d0b737033 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/CheckResultEntryView.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0f9ab8f9c26d7b74b97385064b60e2f3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/BuiltinProviders/ExplicitAssetProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0e77bbfbcbef8bb498da62d6019930aa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/BuiltinProviders/SelectionAssetProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9d8f8eca555cd0c4ead45354cba1f534 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/BuiltinProviders/TextSearchAssetProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 925338c10dbb53e42bf73eeb7c1e14cb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AssetProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using UObject = UnityEngine.Object; 4 | 5 | namespace GBG.AssetChecking.Editor 6 | { 7 | public abstract class AssetProvider : ScriptableObject 8 | { 9 | /// 10 | /// Obtain the assets to be checked. 11 | /// 12 | /// 13 | public abstract IReadOnlyList GetAssets(); 14 | } 15 | } -------------------------------------------------------------------------------- /Editor/AssetCheckerWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3ecd11746ce735042a0dbf5cb30a651a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: 7 | - m_ViewDataDictionary: {instanceID: 0} 8 | - _mainVisualTreeAsset: {fileID: 9197481963319205126, guid: 27e4c26865ba0624b968ef6e13d478df, 9 | type: 3} 10 | executionOrder: 0 11 | icon: {instanceID: 0} 12 | userData: 13 | assetBundleName: 14 | assetBundleVariant: 15 | -------------------------------------------------------------------------------- /Editor/GBG.AssetChecker.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GBG.AssetChecker.Editor", 3 | "rootNamespace": "GBG.AssetChecker.Editor", 4 | "references": [], 5 | "includePlatforms": [ 6 | "Editor" 7 | ], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": true, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [], 14 | "versionDefines": [], 15 | "noEngineReferences": false 16 | } -------------------------------------------------------------------------------- /Editor/CustomViewProvider.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UIElements; 3 | 4 | namespace GBG.AssetChecking.Editor 5 | { 6 | public abstract class CustomDetailsView : VisualElement 7 | { 8 | public abstract string CustomViewId { get; } 9 | 10 | 11 | public abstract void Bind(AssetCheckResult checkResult); 12 | } 13 | 14 | public abstract class CustomViewProvider : ScriptableObject 15 | { 16 | public abstract CustomDetailsView GetDetailsView(string customViewId); 17 | } 18 | } -------------------------------------------------------------------------------- /Editor/BuiltinProviders/ExplicitAssetProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UObject = UnityEngine.Object; 5 | 6 | namespace GBG.AssetChecking.Editor 7 | { 8 | [CreateAssetMenu(menuName = "Bamboo/Asset Checker/Explicit Asset Provider")] 9 | public class ExplicitAssetProvider : AssetProvider 10 | { 11 | public UObject[] assets = Array.Empty(); 12 | 13 | 14 | public override IReadOnlyList GetAssets() 15 | { 16 | return assets; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Editor/BuiltinProviders/SelectionAssetProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEngine; 4 | using UObject = UnityEngine.Object; 5 | 6 | namespace GBG.AssetChecking.Editor 7 | { 8 | [CreateAssetMenu(menuName = "Bamboo/Asset Checker/Selection Asset Provider")] 9 | public class SelectionAssetProvider : AssetProvider 10 | { 11 | public SelectionMode selectionModes = SelectionMode.Unfiltered; 12 | 13 | 14 | public override IReadOnlyList GetAssets() 15 | { 16 | UObject[] objects = Selection.GetFiltered(selectionModes); 17 | return objects; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.greenbamboogames.assetchecker", 3 | "version": "1.2.0", 4 | "displayName": "Asset Checker!", 5 | "description": "Easy to use and extensible asset checking tool.", 6 | "unity": "2020.3", 7 | "documentationUrl": "https://github.com/SolarianZ/UnityAssetChecker", 8 | "changelogUrl": "https://github.com/SolarianZ/UnityAssetChecker/releases", 9 | "licensesUrl": "https://github.com/SolarianZ/UnityAssetChecker/blob/main/LICENSE", 10 | "keywords": [ 11 | "Asset", 12 | "Check", 13 | "Validate" 14 | ], 15 | "author": { 16 | "name": "ZQY", 17 | "email": "vdergow@hotmail.com", 18 | "url": "" 19 | }, 20 | "type": "tool", 21 | "hideInEditor": false 22 | } -------------------------------------------------------------------------------- /Editor/AssetCheckResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UObject = UnityEngine.Object; 4 | 5 | namespace GBG.AssetChecking.Editor 6 | { 7 | [Serializable] 8 | public class AssetCheckResult 9 | { 10 | public CheckResultType type = CheckResultType.Exception; 11 | public string[] categories; 12 | public string title; 13 | public string details; 14 | public UObject asset; 15 | public AssetChecker checker; 16 | public bool repairable; 17 | [SerializeReference] 18 | public AssetCheckResultCustomData customData; 19 | public string customViewId; 20 | } 21 | 22 | [Serializable] 23 | public abstract class AssetCheckResultCustomData 24 | { 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /Editor/AssetCheckerSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | #if ODIN_INSPECTOR 5 | using Sirenix.OdinInspector; 6 | #endif 7 | 8 | namespace GBG.AssetChecking.Editor 9 | { 10 | [CreateAssetMenu(menuName = "Bamboo/Asset Checker/Asset Checker Settings")] 11 | public class AssetCheckerSettings : ScriptableObject 12 | { 13 | #nullable enable 14 | [Tooltip("Provides custom views. Can be null.")] 15 | public CustomViewProvider? customViewProvider; 16 | #nullable disable 17 | #if ODIN_INSPECTOR 18 | [Required] 19 | #endif 20 | [Tooltip("Provides assets for checking. Required.")] 21 | public AssetProvider assetProvider; 22 | #if ODIN_INSPECTOR 23 | [Required] 24 | [RequiredListLength(1, null)] 25 | #endif 26 | [Tooltip("Provides checkers for checking assets. Required.")] 27 | public AssetChecker[] assetCheckers = Array.Empty(); 28 | } 29 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Qiuyu ZHANG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | -------------------------------------------------------------------------------- /Editor/CheckResultStats.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GBG.AssetChecking.Editor 4 | { 5 | [Serializable] 6 | public class CheckResultStats : ICloneable 7 | { 8 | public int error; 9 | public int warning; 10 | public int notImportant; 11 | public int allPass; 12 | public int exception; 13 | public int nullResult; 14 | 15 | 16 | public int GetTotal(bool includeNullResult) 17 | { 18 | int total = error + warning + notImportant + allPass + exception; 19 | if (includeNullResult) 20 | { 21 | total += nullResult; 22 | } 23 | return total; 24 | } 25 | 26 | public void Reset() 27 | { 28 | error = 0; 29 | warning = 0; 30 | notImportant = 0; 31 | allPass = 0; 32 | exception = 0; 33 | nullResult = 0; 34 | } 35 | 36 | public object Clone() 37 | { 38 | return new CheckResultStats 39 | { 40 | error = error, 41 | warning = warning, 42 | notImportant = notImportant, 43 | allPass = allPass, 44 | exception = exception, 45 | nullResult = nullResult, 46 | }; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Editor/BuiltinProviders/TextSearchAssetProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.RegularExpressions; 3 | using UnityEditor; 4 | using UnityEngine; 5 | using UDebug = UnityEngine.Debug; 6 | using UObject = UnityEngine.Object; 7 | 8 | namespace GBG.AssetChecking.Editor 9 | { 10 | /// 11 | /// Search the asset database using the search filter string. 12 | /// 13 | /// 14 | [HelpURL("https://docs.unity3d.com/ScriptReference/AssetDatabase.FindAssets.html")] 15 | [CreateAssetMenu(menuName = "Bamboo/Asset Checker/Text Search Asset Provider")] 16 | public class TextSearchAssetProvider : AssetProvider 17 | { 18 | [Tooltip("The filter string can provide names, labels or types (class names). Refer to AssetDatabase.FindAssets.")] 19 | public string filter; 20 | [Tooltip("If not empty, the search results will only include assets whose paths match this regular expression.")] 21 | public string includePattern; 22 | [Tooltip("If not empty, the search results will not include assets whose paths match this regular expression.")] 23 | public string excludePattern; 24 | [Tooltip("The folders where the search will start. Refer to AssetDatabase.FindAssets.")] 25 | public string[] searchInFolders = new string[] { "Assets" }; 26 | [Tooltip("If checked, the search results will not include DefaultAssets (folders, unsupported file types, etc.).")] 27 | public bool ignoreDefaultAssets; 28 | 29 | 30 | public override IReadOnlyList GetAssets() 31 | { 32 | string[] guids = AssetDatabase.FindAssets(filter, searchInFolders); 33 | List objects = new List(guids.Length); 34 | Regex includeRegex = string.IsNullOrEmpty(includePattern) ? null : new Regex(includePattern); 35 | Regex excludeRegex = string.IsNullOrEmpty(excludePattern) ? null : new Regex(excludePattern); 36 | foreach (string guid in guids) 37 | { 38 | string path = AssetDatabase.GUIDToAssetPath(guid); 39 | if (includeRegex != null && !includeRegex.IsMatch(path)) 40 | { 41 | continue; 42 | } 43 | 44 | if (excludeRegex != null && includeRegex.IsMatch(path)) 45 | { 46 | continue; 47 | } 48 | 49 | UObject asset = AssetDatabase.LoadAssetAtPath(path); 50 | if (ignoreDefaultAssets && asset is DefaultAsset) 51 | { 52 | continue; 53 | } 54 | 55 | if (!asset) 56 | { 57 | UDebug.LogError($"[{AssetChecker.LogTag}] The asset exists but cannot be loaded: {path}.", asset); 58 | } 59 | 60 | objects.Add(asset); 61 | } 62 | return objects; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Editor/CheckResultEntryView.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | using UnityEngine.UIElements; 4 | 5 | namespace GBG.AssetChecking.Editor 6 | { 7 | public class CheckResultEntryView : VisualElement 8 | { 9 | private readonly Image _typeImage; 10 | private readonly Image _assetImage; 11 | private readonly Label _label; 12 | private readonly Image _repairableImage; 13 | 14 | 15 | public CheckResultEntryView() 16 | { 17 | style.flexDirection = FlexDirection.Row; 18 | 19 | _typeImage = new Image 20 | { 21 | name = "TypeImage", 22 | style = 23 | { 24 | width = 20, 25 | minWidth = 20, 26 | maxWidth = 20, 27 | height = 20, 28 | minHeight = 20, 29 | maxHeight = 20, 30 | alignSelf = Align.Center, 31 | } 32 | }; 33 | Add(_typeImage); 34 | 35 | _assetImage = new Image 36 | { 37 | name = "AssetImage", 38 | style = 39 | { 40 | width = 20, 41 | minWidth = 20, 42 | maxWidth = 20, 43 | height = 20, 44 | minHeight = 20, 45 | maxHeight = 20, 46 | alignSelf = Align.Center, 47 | } 48 | }; 49 | Add(_assetImage); 50 | 51 | _label = new Label 52 | { 53 | name = "Label", 54 | style = 55 | { 56 | flexGrow = 1, 57 | flexShrink = 1, 58 | overflow = Overflow.Hidden, 59 | unityTextAlign = TextAnchor.MiddleLeft, 60 | } 61 | }; 62 | Add(_label); 63 | 64 | _repairableImage = new Image 65 | { 66 | name = "RepairableImage", 67 | tooltip = "Repairable", 68 | image = EditorGUIUtility.isProSkin 69 | ? EditorGUIUtility.IconContent("d_CustomTool@2x").image 70 | : EditorGUIUtility.IconContent("CustomTool@2x").image, 71 | style = 72 | { 73 | width = 20, 74 | minWidth = 20, 75 | maxWidth = 20, 76 | height = 20, 77 | minHeight = 20, 78 | maxHeight = 20, 79 | alignSelf = Align.Center, 80 | } 81 | }; 82 | Add(_repairableImage); 83 | } 84 | 85 | public void Bind(AssetCheckResult result) 86 | { 87 | ResultTypeIconStyle iconStyle = AssetCheckerLocalCache.instance.GetCheckResultTypeIconStyle(); 88 | _typeImage.image = result.type.GetResultTypeIcon(iconStyle); 89 | if (AssetCheckerLocalCache.instance.GetShowResultEntryAssetIcon()) 90 | { 91 | _assetImage.style.display = DisplayStyle.Flex; 92 | _assetImage.image = result.asset != null 93 | ? AssetPreview.GetMiniThumbnail(result.asset) 94 | : null; 95 | } 96 | else 97 | { 98 | _assetImage.style.display = DisplayStyle.None; 99 | } 100 | _label.text = result.title; 101 | _repairableImage.style.display = result.repairable 102 | ? DisplayStyle.Flex 103 | : DisplayStyle.None; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /Editor/AssetCheckerLocalCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UnityEditor; 5 | using UnityEngine; 6 | 7 | namespace GBG.AssetChecking.Editor 8 | { 9 | [FilePath("Library/com.greenbamboogames.assetchecker/LocalCache.asset", 10 | FilePathAttribute.Location.ProjectFolder)] 11 | internal class AssetCheckerLocalCache : ScriptableSingleton 12 | { 13 | public const string AllCategories = "All Categories"; 14 | public const string Repairable = "Repairable"; 15 | 16 | [SerializeField] 17 | private AssetCheckerSettings _settingsAsset; 18 | [SerializeField] 19 | private CheckResultStats _checkResultStats = new CheckResultStats(); 20 | [SerializeField] 21 | private AssetCheckResult[] _checkResults = Array.Empty(); 22 | [SerializeField] 23 | private CheckResultTypes _resultTypeFilter = 24 | #if UNITY_2022_3_OR_NEWER 25 | CheckResultTypes.AllTypes; 26 | #else 27 | (CheckResultTypes)~0U; 28 | #endif 29 | [SerializeField] 30 | private string _resultCategoryFilter = AllCategories; 31 | [SerializeField] 32 | private ResultTypeIconStyle _resultIconStyle = ResultTypeIconStyle.Style2; 33 | [SerializeField] 34 | private bool _showResultEntryAssetIcon = true; 35 | 36 | public CustomViewProvider InstantCustomViewProvider { get; set; } 37 | 38 | 39 | public AssetCheckerSettings GetSettingsAsset() 40 | { 41 | return _settingsAsset; 42 | } 43 | 44 | public void SetSettingsAsset(AssetCheckerSettings settings) 45 | { 46 | _settingsAsset = settings; 47 | Save(true); 48 | } 49 | 50 | public AssetCheckResult[] GetCheckResults() 51 | { 52 | return _checkResults.ToArray(); 53 | } 54 | 55 | public void SetCheckResults(IEnumerable checkResults) 56 | { 57 | _checkResults = checkResults?.ToArray() ?? Array.Empty(); 58 | Save(true); 59 | } 60 | 61 | public CheckResultTypes GetCheckResultTypeFilter() 62 | { 63 | return _resultTypeFilter; 64 | } 65 | 66 | public void SetCheckResultTypeFilter(CheckResultTypes types) 67 | { 68 | _resultTypeFilter = types; 69 | Save(true); 70 | } 71 | 72 | public string GetCheckResultCategoryFilter() 73 | { 74 | return _resultCategoryFilter; 75 | } 76 | 77 | public void SetCheckResultCategoryFilter(string category) 78 | { 79 | _resultCategoryFilter = string.IsNullOrWhiteSpace(category) 80 | ? AllCategories 81 | : category.Trim(); 82 | Save(true); 83 | } 84 | 85 | public CheckResultStats GetCheckResultStats() 86 | { 87 | CheckResultStats stats = (CheckResultStats)_checkResultStats.Clone(); 88 | return stats; 89 | } 90 | 91 | public void SetCheckResultStats(CheckResultStats stats) 92 | { 93 | _checkResultStats = stats == null 94 | ? new CheckResultStats() 95 | : (CheckResultStats)stats.Clone(); 96 | Save(true); 97 | } 98 | 99 | public ResultTypeIconStyle GetCheckResultTypeIconStyle() 100 | { 101 | return _resultIconStyle; 102 | } 103 | 104 | public void SetCheckResultIconStyle(ResultTypeIconStyle iconStyle) 105 | { 106 | _resultIconStyle = iconStyle; 107 | Save(true); 108 | } 109 | 110 | public bool GetShowResultEntryAssetIcon() 111 | { 112 | return _showResultEntryAssetIcon; 113 | } 114 | 115 | public void SetShowResultEntryAssetIcon(bool showResultEntryAssetIcon) 116 | { 117 | _showResultEntryAssetIcon = showResultEntryAssetIcon; 118 | Save(true); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /Editor/CheckResultType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace GBG.AssetChecking.Editor 6 | { 7 | public enum CheckResultType : uint 8 | { 9 | AllPass = 1U << 0, 10 | NotImportant = 1U << 1, 11 | Warning = 1U << 2, 12 | Error = 1U << 3, 13 | Exception = 1U << 4, 14 | } 15 | 16 | [Flags] 17 | public enum CheckResultTypes : uint 18 | { 19 | #if UNITY_2022_3_OR_NEWER 20 | None = 0, 21 | AllTypes = ~0U, 22 | #endif 23 | 24 | // Sync with CheckResultType 25 | AllPass = CheckResultType.AllPass, 26 | NotImportant = CheckResultType.NotImportant, 27 | Warning = CheckResultType.Warning, 28 | Error = CheckResultType.Error, 29 | Exception = CheckResultType.Exception, 30 | } 31 | 32 | public static class CheckResultTypeHelper 33 | { 34 | public static Texture GetResultTypeIcon(this CheckResultType resultType, ResultTypeIconStyle iconStyle) 35 | { 36 | switch (resultType) 37 | { 38 | case CheckResultType.AllPass: 39 | switch (iconStyle) 40 | { 41 | case ResultTypeIconStyle.Style1: 42 | return EditorGUIUtility.IconContent("sv_icon_dot3_pix16_gizmo").image; 43 | case ResultTypeIconStyle.Style2: 44 | return EditorGUIUtility.IconContent("d_winbtn_mac_max@2x").image; 45 | case ResultTypeIconStyle.Style3: 46 | return EditorGUIUtility.IconContent("d_greenLight").image; 47 | default: 48 | throw new ArgumentOutOfRangeException(nameof(iconStyle), iconStyle, null); 49 | } 50 | case CheckResultType.NotImportant: 51 | switch (iconStyle) 52 | { 53 | case ResultTypeIconStyle.Style1: 54 | return EditorGUIUtility.IconContent("sv_icon_dot0_pix16_gizmo").image; 55 | case ResultTypeIconStyle.Style2: 56 | return EditorGUIUtility.IconContent("sv_icon_dot0_pix16_gizmo").image; 57 | case ResultTypeIconStyle.Style3: 58 | return EditorGUIUtility.IconContent("sv_icon_dot0_pix16_gizmo").image; 59 | default: 60 | throw new ArgumentOutOfRangeException(nameof(iconStyle), iconStyle, null); 61 | } 62 | case CheckResultType.Warning: 63 | switch (iconStyle) 64 | { 65 | case ResultTypeIconStyle.Style1: 66 | return EditorGUIUtility.IconContent("sv_icon_dot5_pix16_gizmo").image; 67 | case ResultTypeIconStyle.Style2: 68 | return EditorGUIUtility.IconContent("d_winbtn_mac_min@2x").image; 69 | case ResultTypeIconStyle.Style3: 70 | return EditorGUIUtility.IconContent("d_orangeLight").image; 71 | default: 72 | throw new ArgumentOutOfRangeException(nameof(iconStyle), iconStyle, null); 73 | } 74 | case CheckResultType.Error: 75 | switch (iconStyle) 76 | { 77 | case ResultTypeIconStyle.Style1: 78 | return EditorGUIUtility.IconContent("sv_icon_dot6_pix16_gizmo").image; 79 | case ResultTypeIconStyle.Style2: 80 | return EditorGUIUtility.IconContent("d_winbtn_mac_close@2x").image; 81 | case ResultTypeIconStyle.Style3: 82 | return EditorGUIUtility.IconContent("d_redLight").image; 83 | default: 84 | throw new ArgumentOutOfRangeException(nameof(iconStyle), iconStyle, null); 85 | } 86 | case CheckResultType.Exception: 87 | switch (iconStyle) 88 | { 89 | case ResultTypeIconStyle.Style1: 90 | return EditorGUIUtility.IconContent("sv_icon_dot7_pix16_gizmo").image; 91 | case ResultTypeIconStyle.Style2: 92 | return EditorGUIUtility.IconContent("d_winbtn_mac_close_a@2x").image; 93 | case ResultTypeIconStyle.Style3: 94 | return EditorGUIUtility.IconContent("Error@2x").image; 95 | default: 96 | throw new ArgumentOutOfRangeException(nameof(iconStyle), iconStyle, null); 97 | } 98 | default: 99 | throw new ArgumentOutOfRangeException(nameof(resultType), resultType, null); 100 | } 101 | } 102 | 103 | public static Color GetResultTypeBorderColor(this CheckResultType resultType) 104 | { 105 | switch (resultType) 106 | { 107 | case CheckResultType.AllPass: 108 | return new Color(0 / 255f, 190 / 255f, 0 / 255f, 1.0f); 109 | case CheckResultType.NotImportant: 110 | return new Color(229 / 255f, 229 / 255f, 229 / 255f, 1.0f); 111 | case CheckResultType.Warning: 112 | return new Color(255 / 255f, 200 / 255f, 0 / 255f, 1.0f); 113 | case CheckResultType.Error: 114 | return new Color(255 / 255f, 0 / 255f, 0 / 255f, 1.0f); 115 | case CheckResultType.Exception: 116 | return new Color(150 / 255f, 0 / 255f, 0 / 255f, 1.0f); 117 | default: 118 | throw new ArgumentOutOfRangeException(nameof(resultType), resultType, null); 119 | } 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /Editor/AssetChecker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEngine; 5 | using UDebug = UnityEngine.Debug; 6 | using UObject = UnityEngine.Object; 7 | 8 | namespace GBG.AssetChecking.Editor 9 | { 10 | public enum ExecutionResult 11 | { 12 | Success, 13 | Failure, 14 | NoAssets, 15 | } 16 | 17 | public abstract class AssetChecker : ScriptableObject 18 | { 19 | /// 20 | /// Execute the asset checking process. 21 | /// 22 | /// The asset to be checked. 23 | /// Check result. Can be null(meaning there are no issues). 24 | public abstract AssetCheckResult CheckAsset(UObject asset); 25 | 26 | /// 27 | /// Attempt to repair the issues with the assets. 28 | /// 29 | /// Repair result. Can be null(meaning there are no issues). 30 | /// Whether all issues have been repaired. 31 | public abstract void RepairAsset(AssetCheckResult checkResult, out bool allIssuesRepaired); 32 | 33 | 34 | public const string CategoryName_Exception = "Exception"; 35 | internal protected const string LogTag = "AssetChecker"; 36 | 37 | /// 38 | /// Launch the asset checking process. 39 | /// 40 | /// Provides the assets to be checked. 41 | /// The list of checkers that operate on the assets. 42 | /// The results of the checks. 43 | /// The count of null results returned by checkers (null results are considered as passed checks). 44 | /// The log context (clicking on a log entry in the console will ping this object). 45 | /// Specifies whether to allow displaying popup messages. 46 | /// The execution result. 47 | public static ExecutionResult Execute(AssetProvider assetProvider, 48 | IReadOnlyList assetCheckers, 49 | List checkResults, out int nullResultCount, 50 | UObject logContext = null, bool allowDisplayDialog = true) 51 | { 52 | checkResults?.Clear(); 53 | nullResultCount = 0; 54 | 55 | if (!assetProvider) 56 | { 57 | string errorMessage = $"Asset checker execution failed. Argument '{nameof(assetProvider)}' is null."; 58 | UDebug.LogError($"[{LogTag}] {errorMessage}", logContext); 59 | if (allowDisplayDialog) 60 | { 61 | EditorUtility.DisplayDialog("Error", errorMessage, "Ok"); 62 | } 63 | return ExecutionResult.Failure; 64 | } 65 | 66 | if (assetCheckers == null || assetCheckers.Count == 0) 67 | { 68 | string errorMessage = $"Asset checker execution failed. Argument '{nameof(assetCheckers)}' is null or empty."; 69 | if (allowDisplayDialog) 70 | { 71 | UDebug.LogError($"[{LogTag}] {errorMessage}", logContext); 72 | } 73 | EditorUtility.DisplayDialog("Error", errorMessage, "Ok"); 74 | return ExecutionResult.Failure; 75 | } 76 | 77 | if (checkResults == null) 78 | { 79 | string errorMessage = $"Asset checker execution failed. Argument '{nameof(checkResults)}' is null."; 80 | if (allowDisplayDialog) 81 | { 82 | UDebug.LogError($"[{LogTag}] {errorMessage}", logContext); 83 | } 84 | EditorUtility.DisplayDialog("Error", errorMessage, "Ok"); 85 | return ExecutionResult.Failure; 86 | } 87 | 88 | IReadOnlyList assets = assetProvider.GetAssets(); 89 | if (assets == null || assets.Count == 0) 90 | { 91 | string errorMessage = "Asset checker execution aborted. No assets were provided for check."; 92 | UDebug.LogWarning($"[{LogTag}] {errorMessage}", logContext); 93 | if (allowDisplayDialog) 94 | { 95 | EditorUtility.DisplayDialog("Warning", errorMessage, "Ok"); 96 | } 97 | return ExecutionResult.NoAssets; 98 | } 99 | 100 | bool hasNullChecker = false; 101 | for (int i = 0; i < assets.Count; i++) 102 | { 103 | UObject asset = assets[i]; 104 | for (int j = 0; j < assetCheckers.Count; j++) 105 | { 106 | AssetChecker checker = assetCheckers[j]; 107 | if (!checker) 108 | { 109 | hasNullChecker = true; 110 | continue; 111 | } 112 | 113 | try 114 | { 115 | AssetCheckResult result = checker.CheckAsset(asset); 116 | if (result != null) 117 | { 118 | checkResults.Add(result); 119 | } 120 | else 121 | { 122 | nullResultCount++; 123 | } 124 | } 125 | catch (Exception e) 126 | { 127 | AssetCheckResult result = new AssetCheckResult 128 | { 129 | type = CheckResultType.Exception, 130 | categories = new string[] { CategoryName_Exception }, 131 | title = e.GetType().Name, 132 | details = e.Message, 133 | asset = asset, 134 | checker = checker, 135 | repairable = false, 136 | customData = null, 137 | customViewId = null, 138 | }; 139 | checkResults.Add(result); 140 | } 141 | } 142 | } 143 | 144 | if (hasNullChecker) 145 | { 146 | string errorMessage = $"Null items found in the argument '{nameof(assetCheckers)}', please check."; 147 | UDebug.LogError($"[{LogTag}] {errorMessage}", logContext); 148 | if (allowDisplayDialog) 149 | { 150 | EditorUtility.DisplayDialog("Error", errorMessage, "Ok"); 151 | } 152 | } 153 | 154 | return ExecutionResult.Success; 155 | } 156 | } 157 | } -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Unity资产检查工具 2 | 3 | 易用且可扩展的资产检查工具。 4 | 5 | ![资产检查工具](./Documents~/imgs/img_sample_asset_checker_window.png) 6 | 7 | [English](./README.md) 8 | 9 | ## 功能 10 | 11 | - 支持多套检查配置 12 | - 自定义要检查的资产范围 13 | - 自定义检查方式 14 | - 对资产顺序执行多个检查流程 15 | - 按结果类型筛选检查结果 16 | - 按自定义分类筛选检查结果 17 | - 对检查结果执行重新检查 18 | - 对检查结果尝试执行修复 19 | - 为检查结果创建自定义详细信息UI 20 | - 在重新打开检查器时,恢复上次检查的结果 21 | - 允许脱离UI,使用脚本发起检查流程 22 | - 支持多种检查结果UI样式 23 | 24 | ## 支持的Unity版本 25 | 26 | Unity 2020.3 或更新版本。 27 | 28 | 在Unity 2022.3之前的版本中,部分UI交互功能有所减弱,但不影响功能。 29 | 30 | ## 安装 31 | 32 | [![openupm](https://img.shields.io/npm/v/com.greenbamboogames.assetchecker?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.greenbamboogames.assetchecker/) 33 | 34 | 从 [OpenUPM](https://openupm.com/packages/com.greenbamboogames.assetchecker) 安装,或者直接将此仓库克隆到项目的Packages文件夹下。 35 | 36 | ## 如何使用 37 | 38 | ### 创建Asset Provider资产 39 | 40 | Asset Provider是 `ScriptableObject` 的子类型,用于向Asset Checker提供要进行检查的资产。 41 | 42 | 首先,在项目中添加一个类型,继承 `AssetProvider` 类型,实现 `GetAssets` 方法,该方法返回 `IReadOnlyList` 。Asset Checker将会检查返回结果中的资产。 43 | 44 | 然后创建该类型的Asset Provider资产,稍后需要将该Asset Provider资产设置到配置资产中。 45 | 46 | 示例: 47 | 48 | ```csharp 49 | using GBG.AssetChecker.Editor.AssetChecker; 50 | using System.Collections.Generic; 51 | using UnityEngine; 52 | 53 | [CreateAssetMenu(menuName = "Tests/My Asset Provider")] 54 | public class MyAssetProvider : AssetProvider 55 | { 56 | public override IReadOnlyList GetAssets() 57 | { 58 | // 返回需要检查的资产列表 59 | return Resources.FindObjectsOfTypeAll(); 60 | } 61 | } 62 | ``` 63 | 64 | 内置的资产Asset Provider: 65 | - **ExplicitAssetProvider** :返回用户明确逐个指定的资产。 66 | - **SelectionAssetProvider** :返回当前在Project窗口中选中的资产,可配置筛选参数。 67 | - **TextSearchAssetProvider** :返回按给定文本内容搜索得到的资产,搜索结果与在Project窗口中搜索的结果相同。 68 | 69 | 可通过 _**Create/Bamboo/Asset Checker**_ 菜单创建内置Asset Provider资产。 70 | 71 | ### 创建Asset Checker资产 72 | 73 | Asset Checker是 `ScriptableObject` 的子类型,用于执行资产检查流程。 **建议** 每个Asset Checker中只实现一项检查,通过组合多个Asset Checker的方式实现复杂的检查规则。 74 | 75 | 首先,在项目中添加一个类型,继承 `AssetChecker` 类型,实现 `CheckAsset` 方法和 `RepairAsset` 方法。 76 | 77 | `CheckAsset` 方法需要实现具体的检查逻辑,并返回 `AssetCheckResult` 类型的检查结果。如果资产检查通过,可以返回 `null` ,此时检查结果列表中不会记录此项检查结果。 78 | 79 | `RepairAsset` 方法需要实现对检查结果中的问题进行修复的逻辑。如果检查结果不可修复,需要在 `CheckAsset` 方法中将检查结果的 `repairable` 字段设为 `false` 。 80 | 81 | 然后创建该类型的Asset Checker资产,稍后需要将该Asset Checker资产设置到配置资产中。 82 | 83 | 示例: 84 | 85 | ```csharp 86 | using GBG.AssetChecker.Editor.AssetChecker; 87 | using UnityEngine; 88 | 89 | [CreateAssetMenu(menuName = "Tests/My Asset Checker")] 90 | public class MyAssetChecker : AssetChecker 91 | { 92 | public override AssetCheckResult CheckAsset(Object asset) 93 | { 94 | GameObject go = asset as GameObject; 95 | if (!go) 96 | { 97 | // Asset Checker会自动为抛出的异常生成“Exception”类型的检查结果 98 | throw new System.ArgumentException("Asset must be an instance of type 'GameObject'."); 99 | } 100 | 101 | if (go.name.StartsWith("GO_")) 102 | { 103 | // 对于“AllPass”类型的检查结果,也可以返回“null” 104 | return new AssetCheckResult 105 | { 106 | type = CheckResultType.AllPass, 107 | categories = new string[] { "GameObject" }, 108 | title = "GameObject Name Prefix", 109 | details = "GameObject name prefix 'GO_' check passed.", 110 | asset = go, 111 | checker = this, 112 | repairable = true, 113 | customData = null, 114 | customViewId = null, 115 | }; 116 | } 117 | 118 | return new AssetCheckResult 119 | { 120 | type = CheckResultType.Error, 121 | categories = new string[] { "GameObject" }, 122 | title = "GameObject Name Prefix - Invalid", 123 | details = "GameObject name not starts with prefix 'GO_'.", 124 | asset = go, 125 | checker = this, 126 | repairable = true, 127 | customData = null, 128 | customViewId = null, 129 | }; 130 | } 131 | 132 | public override void RepairAsset(AssetCheckResult checkResult, out bool allIssuesRepaired) 133 | { 134 | // Asset Checker会自动为抛出的异常生成“Exception”类型的检查结果 135 | throw new System.NotSupportedException("Issue can not be repaired"); 136 | } 137 | } 138 | ``` 139 | 140 | ### 创建和设置配置资产 141 | 142 | 从菜单 _**Tools/Bamboo/Asset Checker**_ 打开资产检查工具窗口。 143 | 144 | 在Asset Checker窗口中,点击 **Settings** 属性右侧的 **New** 按钮,创建一个新的配置资产(`AssetCheckerSettings`),然后将该配置资产分配给 **Settings** 属性。 145 | 146 | ![新建配置](./Documents~/imgs/img_sample_asset_checker_new_settings.png) 147 | 148 | 选中该配置资产,在Inspector中,将前面创建的Asset Provider资产和Asset Checker资产分别分配给 **Asset Provider** 属性和 **Asset Checkers** 属性。 **Asset Checkers** 属性可以添加多个Asset Checker资产,检查流程中,会顺序执行这些Asset Checker资产。 149 | 150 | 在Asset Checker窗口中,点击 **Execute** 按钮,执行检查流程。然后Asset Checker窗口中将会列出所有检查结果。 151 | 152 | ### 重新检查和尝试修复问题 153 | 154 | 选中一条检查结果,在右侧的详细信息面板中,点击 **Recheck** 按钮执行重新检查,点击 **Try Repair** 按钮执行修复。如果检查结果被标记为不可修复, **Try Repair** 按钮会被禁用。 155 | 156 | ### 清空检查结果 157 | 158 | 在Asset Checker的上下文菜单中,选择 **Clear Check Results** 选项来清空检查结果。 159 | 160 | ### 切换检查结果图标样式 161 | 162 | 在Asset Checker的上下文菜单中,选择 **Result Icon Style** 选项来切换图标样式。 163 | 164 | ### 自定义检查结果类别 165 | 166 | 可以通过 `AssetCheckResult.categories` 字段为检查结果指定类别。每个检查结果可以同时属于多个类别。 167 | 168 | ### 自定义检查结果详情UI 169 | 170 | 首先,在项目中添加一个类型,继承 `CustomDetailsView` 类型,实现UI构建和 `Bind` 方法。 `Bind` 中要根据检查结果设置UI。 171 | 172 | 然后,在项目中添加一个类型,继承 `CustomViewProvider` 类型(`ScriptableObject`),实现 `GetDetailsView` 方法,该方法根据传入的 `customViewId` 参数返回 `CustomDetailsView` 实例。然后创建该类型的Custom View Provider资产,将该资产设置分配给配置资产的 **Custom View Provider** 属性。 173 | 174 | 最后,在 `AssetChecker.CheckAsset` 方法中,为 `AssetCheckResult.customViewId` 设置对应的关键字。 175 | 176 | 示例: 177 | 178 | ```csharp 179 | using GBG.AssetChecker.Editor.AssetChecker; 180 | using UnityEngine; 181 | using UnityEngine.UIElements; 182 | 183 | [CreateAssetMenu(menuName = "Tests/My Custom Details View Provider")] 184 | public class MyCustomDetailsViewProvider : CustomViewProvider 185 | { 186 | public override CustomDetailsView GetDetailsView(string customViewId) 187 | { 188 | switch (customViewId) 189 | { 190 | case "MyCustomView": 191 | return new MyCustomDetailsView(); 192 | default: 193 | throw new System.NotImplementedException(); 194 | } 195 | } 196 | } 197 | 198 | public class MyCustomDetailsView : CustomDetailsView 199 | { 200 | public override string CustomViewId => "MyCustomView"; 201 | private readonly Label _detailsLabel; 202 | 203 | public MyCustomDetailsView() 204 | { 205 | _detailsLabel = new Label(); 206 | Add(_detailsLabel); 207 | } 208 | 209 | public override void Bind(AssetCheckResult checkResult) 210 | { 211 | _detailsLabel.text = checkResult.details; 212 | } 213 | } 214 | ``` 215 | 216 | ### 通过代码发起检查流程(不使用UI) 217 | 218 | 调用静态方法 `AssetChecker.Execute` 发起检查流程。 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Asset Checking Tool 2 | 3 | A user-friendly and extensible asset checking tool. 4 | 5 | ![AssetChecker](./Documents~/imgs/img_sample_asset_checker_window.png) 6 | 7 | [中文](./README_CN.md) 8 | 9 | ## Features 10 | 11 | - Supports multiple sets of check settings. 12 | - Customizable asset scope for checking. 13 | - Customizable check methods. 14 | - Executes multiple check workflows on assets in a specified order. 15 | - Filters check results by result type. 16 | - Filters check results by custom categories. 17 | - Allows re-checking of results. 18 | - Attempts to execute repairs on check results. 19 | - Supports custom details UI for check results. 20 | - Restores previous check results when reopening the checker. 21 | - Allows script-based initiation of check workflows independent of the UI. 22 | - Supports multiple styles for check result UI. 23 | 24 | ## Supported Unity Versions 25 | 26 | Unity 2020.3 or later. 27 | 28 | In versions earlier than Unity 2022.3, some UI interaction features are slightly reduced but it does not affect usage. 29 | 30 | ## Installation 31 | 32 | [![openupm](https://img.shields.io/npm/v/com.greenbamboogames.assetchecker?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.greenbamboogames.assetchecker/) 33 | 34 | Install this package via [OpenUPM](https://openupm.com/packages/com.greenbamboogames.assetchecker), or clone this repository directly into the Packages folder of your project. 35 | 36 | ## How to Use 37 | 38 | ### Creating Asset Provider Assets 39 | 40 | An Asset Provider is a subtype of `ScriptableObject` used to provide assets for the Asset Checker to check. 41 | 42 | First, add a new type to your project that inherits from the `AssetProvider` class and implements the `GetAssets` method. This method should return an `IReadOnlyList` containing the assets to be checked by the Asset Checker. 43 | 44 | Next, create an instance of the Asset Provider asset for this type. Later, you will need to set this Asset Provider asset in the settings asset. 45 | 46 | Example: 47 | 48 | ```csharp 49 | using GBG.AssetChecking.Editor; 50 | using System.Collections.Generic; 51 | using UnityEngine; 52 | 53 | [CreateAssetMenu(menuName = "Tests/My Asset Provider")] 54 | public class MyAssetProvider : AssetProvider 55 | { 56 | public override IReadOnlyList GetAssets() 57 | { 58 | // Return the list of assets to be checked 59 | return Resources.FindObjectsOfTypeAll(); 60 | } 61 | } 62 | ``` 63 | 64 | Built-in Asset Providers include: 65 | - **ExplicitAssetProvider**: Returns assets explicitly specified by the user. 66 | - **SelectionAssetProvider**: Returns assets currently selected in the Project window, with configurable filtering parameters. 67 | - **TextSearchAssetProvider**: Returns assets found by searching for specific text content, similar to searching in the Project window. 68 | 69 | You can create built-in Asset Provider assets through the _**Create/Bamboo/Asset Checker**_ menu. 70 | 71 | ### Creating Asset Checker Assets 72 | 73 | Asset Checker is a subtype of `ScriptableObject` used to perform asset checking processes. It is **recommended** to implement only one check in each Asset Checker and combine multiple Asset Checkers to achieve complex check rules. 74 | 75 | First, add a type to your project that inherits from the `AssetChecker` type and implements the `CheckAsset` and `RepairAsset` methods. 76 | 77 | The `CheckAsset` method needs to implement the specific checking logic and return the check result of type `AssetCheckResult`. If the asset passes the check, you can return `null`, and this check result will not be recorded in the check result list. 78 | 79 | The `RepairAsset` method needs to implement the logic to repair issues in the check result. If the check result cannot be repaired, you need to set the `repairable` field of the check result to `false` in the `CheckAsset` method. 80 | 81 | Then create an Asset Checker asset of that type. Later, you will need to set this Asset Checker asset in the settings asset. 82 | 83 | Example: 84 | 85 | ```csharp 86 | using GBG.AssetChecking.Editor; 87 | using UnityEngine; 88 | 89 | [CreateAssetMenu(menuName = "Tests/My Asset Checker")] 90 | public class MyAssetChecker : AssetChecker 91 | { 92 | public override AssetCheckResult CheckAsset(Object asset) 93 | { 94 | GameObject go = asset as GameObject; 95 | if (!go) 96 | { 97 | // The Asset Checker will automatically generate a check result of type "Exception" for the thrown exception 98 | throw new System.ArgumentException("Asset must be an instance of type 'GameObject'."); 99 | } 100 | 101 | if (go.name.StartsWith("GO_")) 102 | { 103 | // For check results of type "AllPass", you can also return "null" 104 | return new AssetCheckResult 105 | { 106 | type = CheckResultType.AllPass, 107 | categories = new string[] { "GameObject" }, 108 | title = "GameObject Name Prefix", 109 | details = "GameObject name prefix 'GO_' check passed.", 110 | asset = go, 111 | checker = this, 112 | repairable = true, 113 | customData = null, 114 | customViewId = null, 115 | }; 116 | } 117 | 118 | return new AssetCheckResult 119 | { 120 | type = CheckResultType.Error, 121 | categories = new string[] { "GameObject" }, 122 | title = "GameObject Name Prefix - Invalid", 123 | details = "GameObject name does not start with prefix 'GO_'.", 124 | asset = go, 125 | checker = this, 126 | repairable = true, 127 | customData = null, 128 | customViewId = null, 129 | }; 130 | } 131 | 132 | public override void RepairAsset(AssetCheckResult checkResult, out bool allIssuesRepaired) 133 | { 134 | // The Asset Checker will automatically generate a check result of type "Exception" for the thrown exception 135 | throw new System.NotSupportedException("Issue cannot be repaired."); 136 | } 137 | } 138 | ``` 139 | 140 | ### Creating and Setting up Settings Assets 141 | 142 | Open the Asset Checker window by selecting **_Tools/Bamboo/Asset Checker_** from the menu. 143 | 144 | In the Asset Checker window, click the **New** button next to the **Settings** property to create a new settings asset (`AssetCheckerSettings`). Then assign this settings asset to the **Settings** property. 145 | 146 | ![New Asset Checker Settings](./Documents~/imgs/img_sample_asset_checker_new_settings.png) 147 | 148 | Select the settings asset and in the Inspector, assign the previously created Asset Provider asset to the **Asset Provider** property, and the Asset Checker asset to the **Asset Checkers** property. The **Asset Checkers** property can have multiple Asset Checker assets, which will be executed sequentially during the checking process. 149 | 150 | In the Asset Checker window, click the **Execute** button to perform the checking process. The Asset Checker window will then display all the check results. 151 | 152 | ### Clear Check Results 153 | 154 | Select the **Clear Check Results** option from the context menu in Asset Checker to clear the check results. 155 | 156 | ### Switch Result Icon Style 157 | 158 | Select the **Result Icon Style** option from the context menu in Asset Checker to switch the icon style. 159 | 160 | ### Rechecking and Attempting to Repair Issues 161 | 162 | Select a check result and click the **Recheck** button in the detailed information panel on the right to perform a recheck. Click the **Try Repair** button to attempt a repair. If the check result is marked as unrepairable, the **Try Repair** button will be disabled. 163 | 164 | ### Customizing Check Result Categories 165 | 166 | You can specify categories for check results using the `AssetCheckResult.categories` field. Each check result can belong to multiple categories simultaneously. 167 | 168 | ### Custom Check Result Details UI 169 | 170 | First, add a type to your project that inherits from the `CustomDetailsView` type. Implement the UI construction and `Bind` method in this type. In the `Bind` method, set up the UI based on the check result. 171 | 172 | Next, add a type to your project that inherits from the `CustomViewProvider` type (`ScriptableObject`). Implement the `GetDetailsView` method in this type, which returns an instance of `CustomDetailsView` based on the `customViewId` parameter passed in. Create an asset of this type, the Custom View Provider asset, and assign it to the **Custom View Provider** property in the settings asset. 173 | 174 | Finally, in the `AssetChecker.CheckAsset` method, set the corresponding keyword for `AssetCheckResult.customViewId`. 175 | 176 | Example: 177 | 178 | ```csharp 179 | using GBG.AssetChecking.Editor; 180 | using UnityEngine; 181 | using UnityEngine.UIElements; 182 | 183 | [CreateAssetMenu(menuName = "Tests/My Custom Details View Provider")] 184 | public class MyCustomDetailsViewProvider : CustomViewProvider 185 | { 186 | public override CustomDetailsView GetDetailsView(string customViewId) 187 | { 188 | switch (customViewId) 189 | { 190 | case "MyCustomView": 191 | return new MyCustomDetailsView(); 192 | default: 193 | throw new System.NotImplementedException(); 194 | } 195 | } 196 | } 197 | 198 | public class MyCustomDetailsView : CustomDetailsView 199 | { 200 | public override string CustomViewId => "MyCustomView"; 201 | private readonly Label _detailsLabel; 202 | 203 | public MyCustomDetailsView() 204 | { 205 | _detailsLabel = new Label(); 206 | Add(_detailsLabel); 207 | } 208 | 209 | public override void Bind(AssetCheckResult checkResult) 210 | { 211 | _detailsLabel.text = checkResult.details; 212 | } 213 | } 214 | ``` 215 | 216 | ### Launch the checking process through code (no UI) 217 | 218 | Invoke the static method `AssetChecker.Execute` to launch the checking process. 219 | -------------------------------------------------------------------------------- /Editor/AssetCheckerWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UnityEditor; 5 | using UnityEngine; 6 | using UnityEngine.UIElements; 7 | using UDebug = UnityEngine.Debug; 8 | using UObject = UnityEngine.Object; 9 | 10 | namespace GBG.AssetChecking.Editor 11 | { 12 | public partial class AssetCheckerWindow : EditorWindow, IHasCustomMenu 13 | { 14 | #region Static 15 | 16 | [MenuItem("Tools/Bamboo/Asset Checker")] 17 | [MenuItem("Window/Asset Management/Asset Checker")] 18 | private static void Open() 19 | { 20 | Open(null); 21 | } 22 | 23 | public static AssetCheckerWindow Open(AssetCheckerSettings settings = null) 24 | { 25 | AssetCheckerWindow window = GetWindow("Asset Checker"); 26 | if (settings) 27 | { 28 | window.SetSettingsAsset(settings); 29 | } 30 | 31 | return window; 32 | } 33 | 34 | #endregion 35 | 36 | 37 | [SerializeField] 38 | private AssetCheckerSettings _settings; 39 | private CheckResultStats _stats; 40 | private int _nullResultCount; 41 | private readonly List _checkResults = new List(); 42 | private readonly List _resultCategories = new List { AssetCheckerLocalCache.AllCategories }; 43 | private readonly List _filteredCheckResults = new List(); 44 | internal static AssetCheckerLocalCache LocalCache => AssetCheckerLocalCache.instance; 45 | 46 | 47 | #region Unity Message 48 | 49 | private void OnEnable() 50 | { 51 | titleContent = EditorGUIUtility.IconContent( 52 | EditorGUIUtility.isProSkin ? "d_ViewToolOrbit" : "ViewToolOrbit"); 53 | titleContent.text = "Asset Checker"; 54 | minSize = new Vector2(500, 400); 55 | 56 | _settings = LocalCache.GetSettingsAsset(); 57 | _stats = LocalCache.GetCheckResultStats(); 58 | _nullResultCount = _stats.nullResult; 59 | _checkResults.AddRange(LocalCache.GetCheckResults()); 60 | UpdateFilteredCheckResults(); 61 | } 62 | 63 | private void OnFocus() 64 | { 65 | LocalCache.InstantCustomViewProvider = _settings?.customViewProvider; 66 | 67 | // NOTE: If the editor window is already open, when there's a change in the script code (triggering compilation), 68 | // Unity may call OnFocus before calling OnEnable, so it's necessary to check if the UI controls are null inside the method. 69 | // Be careful not to cache a bool value to indicate whether the UI has been created, 70 | // because in this situation, the UI controls will be set to null while the bool value remains unchanged. 71 | UpdateExecutionControls(); 72 | } 73 | 74 | #endregion 75 | 76 | 77 | public void Execute() 78 | { 79 | if (!_settings) 80 | { 81 | string errorMessage = "Asset checker execution failed. Settings not specified."; 82 | UDebug.LogError($"[{AssetChecker.LogTag}] {errorMessage}", this); 83 | EditorUtility.DisplayDialog("Error", errorMessage, "Ok"); 84 | return; 85 | } 86 | 87 | if (!_settings.assetProvider) 88 | { 89 | string errorMessage = "Asset checker execution failed. Asset provider not specified in the settings."; 90 | UDebug.LogError($"[{AssetChecker.LogTag}] {errorMessage}", _settings); 91 | EditorUtility.DisplayDialog("Error", errorMessage, "Ok"); 92 | return; 93 | } 94 | 95 | AssetChecker[] checkers = _settings.assetCheckers; 96 | if (checkers == null || checkers.Length == 0) 97 | { 98 | string errorMessage = "Asset checker execution failed. Asset checker not specified in the settings."; 99 | UDebug.LogError($"[{AssetChecker.LogTag}] {errorMessage}", _settings); 100 | EditorUtility.DisplayDialog("Error", errorMessage, "Ok"); 101 | return; 102 | } 103 | 104 | ExecutionResult executionResult = AssetChecker.Execute(_settings.assetProvider, 105 | _settings.assetCheckers, _checkResults, out _nullResultCount, logContext: _settings); 106 | 107 | if (executionResult == ExecutionResult.Failure) 108 | { 109 | return; 110 | } 111 | 112 | UpdatePersistentResultData(); 113 | UpdateFilteredCheckResults(); 114 | UpdateResultControls(true); 115 | } 116 | 117 | public void SetCheckResultTypeFilter(CheckResultTypes filter) 118 | { 119 | _resultTypeFilterField.value = filter; 120 | } 121 | 122 | public void SetCheckResultCategoryFilter(string category) 123 | { 124 | category = string.IsNullOrWhiteSpace(category) 125 | ? AssetCheckerLocalCache.AllCategories 126 | : category.Trim(); 127 | _resultCategoryFilterField.value = category; 128 | } 129 | 130 | private void OnResultTypeFilterChanged(ChangeEvent evt) 131 | { 132 | LocalCache.SetCheckResultTypeFilter((CheckResultTypes)evt.newValue); 133 | UpdateFilteredCheckResults(); 134 | UpdateResultControls(true); 135 | } 136 | 137 | private void OnResultCategoryFilterChanged(ChangeEvent evt) 138 | { 139 | LocalCache.SetCheckResultCategoryFilter(evt.newValue); 140 | UpdateFilteredCheckResults(); 141 | UpdateResultControls(true); 142 | } 143 | 144 | private void OnShowResultEntryAssetIconChanged(ChangeEvent evt) 145 | { 146 | LocalCache.SetShowResultEntryAssetIcon(evt.newValue); 147 | RefreshResultListView(); 148 | } 149 | 150 | public AssetCheckResult[] GetCheckResults() 151 | { 152 | return _checkResults.ToArray(); 153 | } 154 | 155 | public void ClearCheckResults() 156 | { 157 | _checkResults.Clear(); 158 | UpdatePersistentResultData(); 159 | UpdateFilteredCheckResults(); 160 | UpdateResultControls(true); 161 | } 162 | 163 | public AssetCheckerSettings GetSettingsAsset() 164 | { 165 | return _settings; 166 | } 167 | 168 | public void SetSettingsAsset(AssetCheckerSettings settings) 169 | { 170 | _settingsField.value = settings; 171 | } 172 | 173 | private void OnSettingsObjectChanged(ChangeEvent evt) 174 | { 175 | _settings = (AssetCheckerSettings)evt.newValue; // Binding not work on Unity 2020 176 | LocalCache.SetSettingsAsset(_settings); 177 | UpdateExecutionControls(); 178 | } 179 | 180 | public AssetCheckerSettings CreateSettingsAsset() 181 | { 182 | string savePath = EditorUtility.SaveFilePanelInProject("Create new settings asset", 183 | nameof(AssetCheckerSettings), "asset", null); 184 | if (string.IsNullOrEmpty(savePath)) 185 | { 186 | return null; 187 | } 188 | 189 | AssetCheckerSettings settings = CreateInstance(); 190 | AssetDatabase.CreateAsset(settings, savePath); 191 | EditorGUIUtility.PingObject(settings); 192 | 193 | return settings; 194 | } 195 | 196 | public ResultTypeIconStyle GetCheckResultIconStyle() 197 | { 198 | return LocalCache.GetCheckResultTypeIconStyle(); 199 | } 200 | 201 | public void SetCheckResultIconStyle(ResultTypeIconStyle iconStyle) 202 | { 203 | LocalCache.SetCheckResultIconStyle(iconStyle); 204 | RefreshResultListView(); 205 | } 206 | 207 | private void OnAssetRechecked(AssetCheckResult newResult, AssetCheckResult oldResult) 208 | { 209 | bool clearSelection = false; 210 | if (newResult == null) 211 | { 212 | _checkResults.Remove(oldResult); 213 | clearSelection = true; 214 | } 215 | else 216 | { 217 | int resultIndex = _checkResults.IndexOf(oldResult); 218 | _checkResults[resultIndex] = newResult; 219 | } 220 | 221 | UpdatePersistentResultData(); 222 | UpdateFilteredCheckResults(); 223 | UpdateResultControls(clearSelection); 224 | } 225 | 226 | private void OnAssetRepaired(AssetCheckResult newResult, bool allIssuesRepaired) 227 | { 228 | if (allIssuesRepaired) 229 | { 230 | _checkResults.Remove(newResult); 231 | } 232 | 233 | UpdatePersistentResultData(); 234 | UpdateFilteredCheckResults(); 235 | UpdateResultControls(allIssuesRepaired); 236 | } 237 | 238 | private void UpdatePersistentResultData() 239 | { 240 | _stats.Reset(); 241 | _stats.nullResult = _nullResultCount; 242 | 243 | for (int i = 0; i < _checkResults.Count; i++) 244 | { 245 | AssetCheckResult result = _checkResults[i]; 246 | 247 | // Stats 248 | switch (result.type) 249 | { 250 | case CheckResultType.AllPass: 251 | _stats.allPass++; 252 | break; 253 | case CheckResultType.NotImportant: 254 | _stats.notImportant++; 255 | break; 256 | case CheckResultType.Warning: 257 | _stats.warning++; 258 | break; 259 | case CheckResultType.Error: 260 | _stats.error++; 261 | break; 262 | case CheckResultType.Exception: 263 | _stats.exception++; 264 | break; 265 | default: 266 | throw new ArgumentOutOfRangeException(nameof(result.type), result.type, null); 267 | } 268 | } 269 | 270 | LocalCache.SetCheckResultStats(_stats); 271 | LocalCache.SetCheckResults(_checkResults); 272 | } 273 | 274 | private void UpdateFilteredCheckResults() 275 | { 276 | CheckResultTypes selectedTypes = LocalCache.GetCheckResultTypeFilter(); 277 | string selectedCategory = LocalCache.GetCheckResultCategoryFilter(); 278 | 279 | _filteredCheckResults.Clear(); 280 | HashSet categories = new HashSet(); 281 | 282 | for (int i = 0; i < _checkResults.Count; i++) 283 | { 284 | AssetCheckResult result = _checkResults[i]; 285 | if (result.categories != null) 286 | { 287 | foreach (string category in result.categories) 288 | { 289 | if (string.IsNullOrWhiteSpace(category)) 290 | { 291 | continue; 292 | } 293 | 294 | string trimmedCategory = category.Trim(); 295 | if (trimmedCategory == AssetCheckerLocalCache.AllCategories || 296 | trimmedCategory == AssetCheckerLocalCache.Repairable) 297 | { 298 | continue; 299 | } 300 | 301 | categories.Add(trimmedCategory); 302 | } 303 | } 304 | 305 | CheckResultTypes resultType = (CheckResultTypes)result.type; 306 | if ((resultType & selectedTypes) == 0) 307 | { 308 | continue; 309 | } 310 | 311 | if ((selectedCategory == AssetCheckerLocalCache.AllCategories) || 312 | (selectedCategory == AssetCheckerLocalCache.Repairable && result.repairable) || 313 | (result.categories?.Contains(selectedCategory) ?? false)) 314 | { 315 | _filteredCheckResults.Add(result); 316 | } 317 | } 318 | 319 | _resultCategories.Clear(); 320 | _resultCategories.Add(AssetCheckerLocalCache.AllCategories); // Option for no category filter 321 | _resultCategories.Add(AssetCheckerLocalCache.Repairable); 322 | _resultCategories.AddRange(categories); 323 | } 324 | 325 | 326 | #region Custom Menu 327 | 328 | public void AddItemsToMenu(GenericMenu menu) 329 | { 330 | // Source Code 331 | menu.AddItem(new GUIContent("Source Code"), false, () => 332 | { 333 | Application.OpenURL("https://github.com/SolarianZ/UnityAssetChecker"); 334 | }); 335 | menu.AddSeparator(""); 336 | 337 | // Debug 338 | menu.AddItem(new GUIContent("[Debug] Inspect Local Cache Asset"), false, () => 339 | { 340 | Selection.activeObject = LocalCache; 341 | }); 342 | } 343 | 344 | #endregion 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /Editor/AssetCheckerWindow.GUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEditor.UIElements; 5 | using UnityEngine; 6 | using UnityEngine.UIElements; 7 | 8 | namespace GBG.AssetChecking.Editor 9 | { 10 | public partial class AssetCheckerWindow 11 | { 12 | #region Controls 13 | 14 | private ObjectField _settingsField; 15 | private HelpBox _executionHelpBox; 16 | private Button _executeButton; 17 | private Label _resultStatsLabel; 18 | private EnumFlagsField _resultTypeFilterField; 19 | private PopupField _resultCategoryFilterField; // DropdownField is not supported in Unity 2020 20 | private ListView _resultListView; 21 | private CheckResultDetailsView _resultDetailsView; 22 | 23 | #endregion 24 | 25 | 26 | private void ShowButton(Rect position) 27 | { 28 | if (GUI.Button(position, EditorGUIUtility.IconContent("_Help"), GUI.skin.FindStyle("IconButton"))) 29 | { 30 | Application.OpenURL("https://github.com/SolarianZ/UnityAssetChecker"); 31 | } 32 | } 33 | 34 | private void CreateGUI() 35 | { 36 | VisualElement root = rootVisualElement; 37 | 38 | 39 | #region Toolbar 40 | 41 | // Toolbar 42 | Toolbar toolbar = new Toolbar 43 | { 44 | name = "Toolbar", 45 | style = 46 | { 47 | justifyContent = Justify.SpaceBetween, 48 | } 49 | }; 50 | root.Add(toolbar); 51 | 52 | VisualElement styleContainer = new VisualElement 53 | { 54 | name = "StyleContainer", 55 | style = 56 | { 57 | flexDirection = FlexDirection.Row, 58 | } 59 | }; 60 | toolbar.Add(styleContainer); 61 | 62 | // Type Icon Style 63 | ToolbarMenu typeIconStyleMenu = new ToolbarMenu 64 | { 65 | name = "TypeIconStyleMenu", 66 | text = GetResultTypeIconStyleName(LocalCache.GetCheckResultTypeIconStyle()) 67 | }; 68 | InitializeTypeIconStyleMenu(typeIconStyleMenu); 69 | styleContainer.Add(typeIconStyleMenu); 70 | 71 | // Asset Icon 72 | ToolbarToggle assetIconToggle = new ToolbarToggle 73 | { 74 | name = "AssetIconToggle", 75 | text = "Show Asset Icon", 76 | value = LocalCache.GetShowResultEntryAssetIcon(), 77 | }; 78 | assetIconToggle.RegisterValueChangedCallback(OnShowResultEntryAssetIconChanged); 79 | styleContainer.Add(assetIconToggle); 80 | 81 | // Clear Button 82 | ToolbarButton clearResultsButton = new ToolbarButton(ClearCheckResults) 83 | { 84 | name = "ClearResultsButton", 85 | text = "Clear Check Results", 86 | }; 87 | toolbar.Add(clearResultsButton); 88 | 89 | #endregion 90 | 91 | 92 | #region Settings 93 | 94 | // Settings Container 95 | VisualElement settingsContainer = new VisualElement 96 | { 97 | name = "SettingsContainer", 98 | style = 99 | { 100 | paddingLeft = 4, 101 | paddingRight = 4, 102 | paddingTop = 4, 103 | paddingBottom = 4, 104 | } 105 | }; 106 | root.Add(settingsContainer); 107 | 108 | // Asset Container 109 | VisualElement settingsAssetContainer = new VisualElement 110 | { 111 | name = "SettingsAssetContainer", 112 | style = 113 | { 114 | flexDirection = FlexDirection.Row, 115 | } 116 | }; 117 | settingsContainer.Add(settingsAssetContainer); 118 | 119 | // UObject Field 120 | _settingsField = new ObjectField 121 | { 122 | name = "SettingsField", 123 | label = "Settings", 124 | allowSceneObjects = false, 125 | objectType = typeof(AssetCheckerSettings), 126 | value = _settings, 127 | style = 128 | { 129 | flexGrow = 1, 130 | } 131 | }; 132 | _settingsField.RegisterValueChangedCallback(OnSettingsObjectChanged); 133 | settingsAssetContainer.Add(_settingsField); 134 | 135 | // Create Button 136 | Button createSettingsAssetButton = new Button 137 | { 138 | name = "CreateSettingsAssetButton", 139 | text = "New", 140 | }; 141 | createSettingsAssetButton.clicked += () => 142 | { 143 | _settings = CreateSettingsAsset(); 144 | }; 145 | settingsAssetContainer.Add(createSettingsAssetButton); 146 | 147 | #endregion 148 | 149 | 150 | #region Execution 151 | 152 | // Execution HelpBox 153 | _executionHelpBox = new HelpBox 154 | { 155 | name = "ExecutionHelpBox", 156 | messageType = HelpBoxMessageType.Error, 157 | style = 158 | { 159 | marginLeft = 16, 160 | marginRight = 16, 161 | } 162 | }; 163 | _executionHelpBox.Q