【Unity】丢材质/资源引用检查工具

一个通过反射的方式检查选定目录的预设是否丢资源的工具,可以检查MeshRender、SkineMesh的材质和动画

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace CandyTools
{
    /// <summary>
    /// Skinned mesh和meshRender材质引用检查工具
    /// </summary>
    public class ReferenceFounder : EditorWindow
    {
        public ReferenceFounder () => Init();

        private void Init ()
        {
            titleContent = new GUIContent( "Missing Reference Founder" );
            _pathList = new List<string>(1);
            _pathList.Add( string.Empty );
            _pathCount = _pathList.Count;
            maxSize = new Vector2( 500, 700 );
            minSize = new Vector2( 500, 700 );
        }

        #region GUI

        private void OnFocus ()
        {
            maximized = true;
        }

        private void OnGUI ()
        {
            DrawTitle();
            DrawPathFields();
            DrawCheckButton();
        }

        #endregion

        #region draw

        private void DrawPathFields ()
        {
            GUILayout.BeginVertical();

            var cnt = _pathList.Count;
            for (int i = 0; i < cnt; i++)
            {
                GUILayout.BeginHorizontal();
                
                GUILayout.TextField( _pathList[i] ,_textFiedOption);

                if (GUILayout.Button( "选择" ))
                {
                    //重写一遍,跨调用效果不好,不如用自带的
                    var folder = EditorUtility.OpenFolderPanel( "选择文件夹",Application.dataPath,"");
                    //返回来的绝对路径要手动处理一下,蛋疼
                    _pathList[i] = SubPath(folder);

                }
                GUILayout.EndHorizontal();
            }
            GUILayout.EndVertical();
        }

        private void DrawCheckButton ()
        {
            GUILayout.Space( 20 );
            GUILayout.BeginVertical();
            GUILayout.BeginHorizontal();

            if (GUILayout.Button( "-" ))
            {
                if (_pathList.Count <= 1)
                    return;

                _pathList.RemoveAt( _pathList.Count - 1 );

            }

            if (GUILayout.Button( "+" ))
                _pathList.Add( string.Empty );

            if (GUILayout.Button( "检查以上目录资源引用" ))
                Chk();

            if (GUILayout.Button( "Clear" ))
            {
                var cnt = _pathList.Count;
                for (int i = 0; i < cnt; i++)
                    _pathList[i] = string.Empty;
            }

            GUILayout.EndHorizontal();
            GUILayout.EndVertical();
        }

        private void DrawTitle ()
        {
            GUI.skin.label.fontSize = 10;
            GUI.skin.label.alignment = TextAnchor.MiddleCenter;
            GUILayout.Label("Missing Resources Founder");
        }
        #endregion

        #region 检查函数
        /// <summary>
        /// 检查MLDJ目录下所有美术资源的引用情况,将丢引用的预设打印出来
        /// </summary>
        [UnityEditor.MenuItem( "检查美术资源引用" )]
        public static void OpenWindow ()
        {
            EditorWindow.GetWindow( typeof( ReferenceFounder ) );
        }

        private string SubPath (string originalPath)
        {
            if (string.IsNullOrEmpty( originalPath ))
                return originalPath;

            var temp = originalPath;
            var idx = temp.IndexOf( "Assets" );
            temp = temp.Substring( idx );

            return temp;
        }

        private void Chk ()
        {
            if (_pathList.Count == 0)
                return;

            var test = _pathList.ToArray();
            var guidArr = AssetDatabase.FindAssets( "t:Prefab", _pathList.ToArray() );
            List<string> resultLst = new List<string>();
            Debug.Log( "资源数量:" + guidArr.Length );

            foreach (var guid in guidArr)
            {
                var path = AssetDatabase.GUIDToAssetPath( guid );
                var obj = AssetDatabase.LoadAssetAtPath( path, typeof( GameObject ) );

                if (obj is null)
                    continue;

                var res = LoadPrefab( obj as GameObject, path );
                if (string.IsNullOrEmpty( res ))
                    continue;

                resultLst.Add( res );
            }

            if (resultLst.Count == 0)
                Debug.Log( "<color=green>检查结束,未检查到相应目录下丢失材质引用的meshRender或skinMesh</color>" );
            else
            {
                PrintResult2DeskTop( resultLst, out var fileName );
                Debug.Log( $"<color=green>检查结束,已将检查结果输出至桌面{fileName}文件</color>" );
            }
        }

        /// <summary>
        /// 将检查结果生成Txt放在桌面上
        /// </summary>
        private static void PrintResult2DeskTop (List<string> pathLst,out string fileName)
        {
            var time = DateTime.Now;
            fileName = $"{time.Year}_{time.Month}_{time.Day}_{time.Hour}_{time.Minute}_{time.Second}_{GUID.Generate().ToString()}";//带一串随机的GUID,防止重名
            var deskTopPath = Environment.GetFolderPath( Environment.SpecialFolder.DesktopDirectory );
            deskTopPath += @"/";
            using (var file = File.Create( deskTopPath + @fileName+".txt"))
            {
                var sw = new StreamWriter( file, encoding: System.Text.Encoding.UTF8 );
                foreach (var path in pathLst)
                    sw.WriteLine( path );

                sw.Flush();
                sw.Close();
            }
        }

        /// <summary>
        /// 检查Prefab
        /// </summary>
        private static string LoadPrefab (GameObject prefab,string path)
        {
            if (prefab is null)
                return string.Empty;

            var res = string.Empty;
            var tran = prefab.transform;
            var childCnt = tran.childCount;
            if (childCnt != 0)
            {
                for (int i = 0; i < childCnt; i++)
                {
                    var obj = tran.GetChild( i );
                    if (obj is null)
                        continue;

                    res += LoadPrefab( obj.gameObject ,path);
                }
            }

            if (IsResourceRefMissing<MeshRenderer>( prefab ))
                res += $"MeshRender Missing:{GetGameObjectFullPath( prefab )},\n文件路径:{path}\n\n";

            if (IsResourceRefMissing<SkinnedMeshRenderer>( prefab ))
                res += $"SkinnedMesh Missing:{GetGameObjectFullPath( prefab )},\n文件路径:{path}\n\n";

            if (IsResourceRefMissing<Animation>( prefab ))
                res += $"AnimClip Missing:{GetGameObjectFullPath( prefab )},\n文件路径:{path}\n\n";

            if(IsResourceRefMissing<Animator>( prefab ))
                res += $"Animator Missing:{GetGameObjectFullPath( prefab )},\n文件路径:{path}\n\n";

            if(IsResourceRefMissing<ParticleSystemRenderer>( prefab ))
                res += $"Particle Missing:{GetGameObjectFullPath( prefab )},\n文件路径:{path}\n\n";

            return res;
        }

        /// <summary>
        /// 检查一组资源是有否丢失,是返回true
        /// </summary>
        private static bool IsResourceRefMissing<T>(GameObject go) where T : Component
        {
            var comp = go.GetComponent<T>();
            if (comp == null)
                return false;

            Component tempComp = null;
            string propertyName = string.Empty;
            switch (comp.GetType())
            {
                case var _ when comp is MeshRenderer:
                    tempComp = comp as MeshRenderer;
                    propertyName = "m_Materials";
                    break;
                case var _ when comp is SkinnedMeshRenderer:
                    tempComp = comp as SkinnedMeshRenderer;
                    propertyName = "m_Materials";
                    break;
                case var _ when comp is Animation:
                    propertyName = "m_Animations";
                    tempComp = comp as Animation;
                    break;
                case var _ when comp is Animator:
                    propertyName = "m_Avatar";
                    tempComp = comp as Animator;
                    break;
                case var _ when comp is ParticleSystemRenderer:
                    tempComp = comp as ParticleSystemRenderer;
                    propertyName = "m_Materials";
                    break;
                default:
                    return false;
            }

            SerializedObject sObj = new SerializedObject( tempComp );

            var sp = sObj.FindProperty( propertyName );

            var refMethod = typeof( SerializedProperty ).GetProperty( "objectReferenceStringValue",
                System.Reflection.BindingFlags.Instance |
                System.Reflection.BindingFlags.NonPublic |
                System.Reflection.BindingFlags.Public );

            if (sp != null && sp.isArray)
            {
                var cnt = sp.arraySize;
                if (cnt == 0)
                    return false;

                for (int i = 0; i < cnt; i++)
                {
                    var elmt = sp.GetArrayElementAtIndex( i );
                    if (elmt is null)
                        continue;

                    var refString = (string)refMethod.GetGetMethod( true ).Invoke( elmt, null );
                    if (refString.StartsWith( "Miss" ))
                        return true;
                }
            }


            return false;
        }

        /// <summary>
        /// 获取GameObject的全路径
        /// </summary>
        private static string GetGameObjectFullPath (GameObject prefab)
        {
            var temp = prefab;
            var path = temp.name;
            while (temp.transform.parent != null)
            {
                path = $"{temp.transform.parent.gameObject.name}/{path}";
                temp = temp.transform.parent.gameObject;
            }

            return path;
        }
        #endregion

        #region fields

        /// <summary>
        /// 要检查的路径总数
        /// </summary>
        private int _pathCount = 0;

        /// <summary>
        /// 保存选择路径
        /// </summary>
        private List<string> _pathList;

        private GUILayoutOption[] _textFiedOption = new GUILayoutOption[]
        {
            GUILayout.Height(20),
            GUILayout.Width(450),
        };


        #endregion
    }

}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值