白泽图

  • 文章
    • Unity渲染
    • Unity项目开发
    • 工具
    • 数学
    • 算法
    • 网站搭建
    • 网络&操作系统
蒋程个人博客
互联网技术经验总结&分享
  1. 首页
  2. Unity项目开发
  3. 正文

Unity项目优化-AnimationClip动画的定制压缩

2021-11-21 3965点热度 3人点赞 0条评论

这段时间项目已经进入后期,经过查验发现模型动画文件占用内存过大,于是着手了动画文件的优化工作,而事实上在我优化之前项目已经过动画进行了过压缩工作,所以我对项目的动画进行更加细节的定制化压缩,最终使内存指标达到预期值。

自从2017年,UWA问答上王亮分享《极无双》的优化方案后,“去无用Scale曲线与精度压缩”的方法基本上已经成为各个项目压缩的主流方法。期初我们项目也是直接拿别人提供的工具进行压缩,但美术发现压缩后很多模型动画出现异常(我们用的模型都是高模,平均每个模型都在1W万面以上,动画细节多,而且很多动画都用到了scale的缩放),美术同学觉得压缩后动画表现异常,于是很多动画文件就放弃了压缩,最终导致内存过大。这里先贴一下原工具代码 

//****************************************************************************
//
//  File:      OptimizeAnimationClipTool.cs
//
//  Copyright (c) SuiJiaBin
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
//****************************************************************************
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Reflection;
using UnityEditor;
using System.IO;

namespace EditorTool
{
    class AnimationOpt
    {
        static Dictionary<uint,string> _FLOAT_FORMAT;
        static MethodInfo getAnimationClipStats;
        static FieldInfo sizeInfo;
        static object[] _param = new object[1];

        static AnimationOpt ()
        {
            _FLOAT_FORMAT = new Dictionary<uint, string> ();
            for (uint i = 1; i < 6; i++) {
                _FLOAT_FORMAT.Add (i, "f" + i.ToString ());
            }
            Assembly asm = Assembly.GetAssembly (typeof(Editor));
            getAnimationClipStats = typeof(AnimationUtility).GetMethod ("GetAnimationClipStats", BindingFlags.Static | BindingFlags.NonPublic);
            Type aniclipstats = asm.GetType ("UnityEditor.AnimationClipStats");
            sizeInfo = aniclipstats.GetField ("size", BindingFlags.Public | BindingFlags.Instance);
        }

        AnimationClip _clip;
        string _path;

        public string path { get{ return _path;} }

        public long originFileSize { get; private set; }

        public int originMemorySize { get; private set; }

        public int originInspectorSize { get; private set; }

        public long optFileSize { get; private set; }

        public int optMemorySize { get; private set; }

        public int optInspectorSize { get; private set; }

        public AnimationOpt (string path, AnimationClip clip)
        {
            _path = path;
            _clip = clip;
            _GetOriginSize ();
        }

        void _GetOriginSize ()
        {
            originFileSize = _GetFileZie ();
            originMemorySize = _GetMemSize ();
            originInspectorSize = _GetInspectorSize ();
        }

        void _GetOptSize ()
        {
            optFileSize = _GetFileZie ();
            optMemorySize = _GetMemSize ();
            optInspectorSize = _GetInspectorSize ();
        }

        long _GetFileZie ()
        {
            FileInfo fi = new FileInfo (_path);
            return fi.Length;
        }

        int _GetMemSize ()
        {
            return Profiler.GetRuntimeMemorySize (_clip);
        }

        int _GetInspectorSize ()
        {
            _param [0] = _clip;
            var stats = getAnimationClipStats.Invoke (null, _param);
            return (int)sizeInfo.GetValue (stats);
        }

        void _OptmizeAnimationScaleCurve ()
        {
            if (_clip != null) {
                //去除scale曲线
                foreach (EditorCurveBinding theCurveBinding in AnimationUtility.GetCurveBindings(_clip)) {
                    string name = theCurveBinding.propertyName.ToLower ();
                    if (name.Contains ("scale")) {
                        AnimationUtility.SetEditorCurve (_clip, theCurveBinding, null);
                        Debug.LogFormat ("关闭{0}的scale curve", _clip.name);
                    }
                } 
            }
        }

        void _OptmizeAnimationFloat_X (uint x)
        {
            if (_clip != null && x > 0) {
                //浮点数精度压缩到f3
                AnimationClipCurveData[] curves = null;
                curves = AnimationUtility.GetAllCurves (_clip);
                Keyframe key;
                Keyframe[] keyFrames;
                string floatFormat;
                if (_FLOAT_FORMAT.TryGetValue (x, out floatFormat)) {
                    if (curves != null && curves.Length > 0) {
                        for (int ii = 0; ii < curves.Length; ++ii) {
                            AnimationClipCurveData curveDate = curves [ii];
                            if (curveDate.curve == null || curveDate.curve.keys == null) {
                                //Debug.LogWarning(string.Format("AnimationClipCurveData {0} don't have curve; Animation name {1} ", curveDate, animationPath));
                                continue;
                            }
                            keyFrames = curveDate.curve.keys;
                            for (int i = 0; i < keyFrames.Length; i++) {
                                key = keyFrames [i];
                                key.value = float.Parse (key.value.ToString (floatFormat));
                                key.inTangent = float.Parse (key.inTangent.ToString (floatFormat));
                                key.outTangent = float.Parse (key.outTangent.ToString (floatFormat));
                                keyFrames [i] = key;
                            }
                            curveDate.curve.keys = keyFrames;
                            _clip.SetCurve (curveDate.path, curveDate.type, curveDate.propertyName, curveDate.curve);
                        }
                    }
                } else {
                    Debug.LogErrorFormat ("目前不支持{0}位浮点", x);
                }
            }
        }

        public void Optimize (bool scaleOpt, uint floatSize)
        {
            if (scaleOpt) {
                _OptmizeAnimationScaleCurve ();
            }
            _OptmizeAnimationFloat_X (floatSize);
            _GetOptSize ();
        }

        public void Optimize_Scale_Float3 ()
        {
            Optimize (true, 3);
        }

        public void LogOrigin ()
        {
            _logSize (originFileSize, originMemorySize, originInspectorSize);
        }

        public void LogOpt ()
        {
            _logSize (optFileSize, optMemorySize, optInspectorSize);
        }

        public void LogDelta ()
        {

        }

        void _logSize (long fileSize, int memSize, int inspectorSize)
        {
            Debug.LogFormat ("{0} \nSize=[ {1} ]", _path, string.Format ("FSize={0} ; Mem->{1} ; inspector->{2}",
                EditorUtility.FormatBytes (fileSize), EditorUtility.FormatBytes (memSize), EditorUtility.FormatBytes (inspectorSize)));
        }
    }

    public class OptimizeAnimationClipTool
    {
        static List<AnimationOpt> _AnimOptList = new List<AnimationOpt> ();
        static List<string> _Errors = new List<string>();
        static int _Index = 0;

        [MenuItem("Assets/Animation/裁剪浮点数去除Scale")]
        public static void Optimize()
        {
            _AnimOptList = FindAnims ();
            if (_AnimOptList.Count > 0)
            {
                _Index = 0;
                _Errors.Clear ();
                EditorApplication.update = ScanAnimationClip;
            }
        }

        private static void ScanAnimationClip()
        {
            AnimationOpt _AnimOpt = _AnimOptList[_Index];
            bool isCancel = EditorUtility.DisplayCancelableProgressBar("优化AnimationClip", _AnimOpt.path, (float)_Index / (float)_AnimOptList.Count);
            _AnimOpt.Optimize_Scale_Float3();
            _Index++;
            if (isCancel || _Index >= _AnimOptList.Count)
            {
                EditorUtility.ClearProgressBar();
                Debug.Log(string.Format("--优化完成--    错误数量: {0}    总数量: {1}/{2}    错误信息↓:\n{3}\n----------输出完毕----------", _Errors.Count, _Index, _AnimOptList.Count, string.Join(string.Empty, _Errors.ToArray())));
                Resources.UnloadUnusedAssets();
                GC.Collect();
                AssetDatabase.SaveAssets();
                EditorApplication.update = null;
                _AnimOptList.Clear();
                _cachedOpts.Clear ();
                _Index = 0;
            }
        }

        static Dictionary<string,AnimationOpt> _cachedOpts = new Dictionary<string, AnimationOpt> ();

        static AnimationOpt _GetNewAOpt (string path)
        {
            AnimationOpt opt = null;
            if (!_cachedOpts.ContainsKey(path)) {
                AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip> (path);
                if (clip != null) {
                    opt = new AnimationOpt (path, clip);
                    _cachedOpts [path] = opt;
                }
            }
            return opt;
        }

        static List<AnimationOpt> FindAnims()
        {
            string[] guids = null;
            List<string> path = new List<string>();
            List<AnimationOpt> assets = new List<AnimationOpt> ();
            UnityEngine.Object[] objs = Selection.GetFiltered(typeof(object), SelectionMode.Assets);
            if (objs.Length > 0)
            {
                for(int i = 0; i < objs.Length; i++)
                {
                    if (objs [i].GetType () == typeof(AnimationClip))
                    {
                        string p = AssetDatabase.GetAssetPath (objs [i]);
                        AnimationOpt animopt = _GetNewAOpt (p);
                        if (animopt != null)
                            assets.Add (animopt);
                    }
                    else
                        path.Add(AssetDatabase.GetAssetPath (objs [i]));
                }
                if(path.Count > 0)
                    guids = AssetDatabase.FindAssets (string.Format ("t:{0}", typeof(AnimationClip).ToString().Replace("UnityEngine.", "")), path.ToArray());
                else
                    guids = new string[]{};
            }
            for(int i = 0; i < guids.Length; i++)
            {
                string assetPath = AssetDatabase.GUIDToAssetPath (guids [i]);
                AnimationOpt animopt = _GetNewAOpt (assetPath);
                if (animopt != null)
                    assets.Add (animopt);
            }
            return assets;
        }
    }
}

下面让我们来优化一下这个代码

1.关于scale曲线的剔除的优化

动画中每根骨骼transform的属性都包含localscale属性,如果我们保留那些需要缩放变化的骨骼的localscale属性,只剔除那些不需要缩放变化骨骼的localscale属性,而不是简单的“一刀切”,那么我们在scale曲线剔除这个环节又能扣出不少内存,就像这样

AnimationClipCurveData[] curves = AnimationUtility.GetAllCurves(clip);
if (curves != null && curves.Length > 0)
{
    for (int j = 0; j < curves.Length; j++)
    {
        string name = curves[j].propertyName.ToLower();
        if (name.Contains("scale"))
        {
            AnimationClipCurveData curveDate = curves[j];
            if (curveDate.curve == null || curveDate.curve.keys == null)
                continue;
            var keyFrames = curveDate.curve.keys;
            bool isScaleChanged = false;
            if (keyFrames.Length > 0)
            {
                var frist = keyFrames[0].value;
                if (Mathf.Abs(frist - 1f) * SETTING.FILTER.ERR_RANGE_SCALE_PROPERTY > 1f) //如果第一帧大小变了
                    isScaleChanged = true;
                else
                {
                    for (int k = 1; k < keyFrames.Length; k++)
                    {
                        if (Mathf.Abs(keyFrames[k].value - frist) * SETTING.FILTER.ERR_RANGE_SCALE_PROPERTY > 1f) //如果差异超过千分之一,则不可删除
                        {
                            isScaleChanged = true;
                            break;
                        }
                    }
                }
            }
            if (isScaleChanged)
            {
                //Todo ...
            }
        }
    }
}

压缩变慢了,因为我们要逐帧判断,不过内存能下来就值,毕竟编译环境耗时无成本,但是这会在动画切换时产生一个BUG,那就是原本可见的物体消失,或者不可见的物体出现,这很好理解,比如A动画的某根骨骼变化是 1---->0---->1,B动画的这根骨骼变化是1--->1--->1,那么根据上面的代码B动画的这根骨骼的localscale属性被剔除了,如果在A动画没有播放完时就切入B,那么原本应该是localscale为1的这根骨骼变成了0。为了解决这个问题,我们需要提前扫描一下同一个模型的所有动画被缩放的所有骨骼,这些骨骼的scale曲线都不可以被剔除,(我们项目美术同学会将同一个模型的所有动画放到一个文件夹下,所以我只要扫描这个文件夹就行了)


2.关于精度的压缩优化

精度压缩是内存优化最为明显的一部分,而且精度压缩是真正的有损压缩,代码如下

keyFrames = curveDate.curve.keys;
for (int i = 0; i < keyFrames.Length; i++)
{
    key = keyFrames[i];
    key.value = float.Parse(key.value.ToString("f3"));
    key.inTangent = float.Parse(key.inTangent.ToString("f3"));
    key.outTangent = float.Parse(key.outTangent.ToString("f3"));
    keyFrames[i] = key;
}
curveDate.curve.keys = keyFrames;

这个方案最早提出来的时候有人质疑过,部分人提出疑问float精度压后难道float就不占四个字节了吗?关于这个问题可以参考Unity动画文件优化探究这篇文章

这里简单说明一下,精度压缩后,会使一些原本存在细微差距的点的值变得一样,那么曲线的密集点会减少,常量点会增加,(比如有曲线中有两帧之间的值非常接近,肉眼看不出来,但是在因为不一样,在插值时依旧会生成一些过程帧,但是如果这两帧之间一样,中间就不会产生插值,描述曲线的点少了,自然内存就下来了,而之所以压缩后文件大小不会变化,只内存变小,是因为这些都是在运行时产生的)

下图我是取项目中的idle动画压缩精度后某个属性localrotation的曲线对比(只压缩了精度,保留小数点后三位)

精度丢失的越多,曲线的平滑度越低,于是就产生了另一个问题,使用固定压缩保留小数点后三位时,发现Idle动画有轻微的抖动效果,但是如果保留更多的精度(测试发现全部保留小数点后5位可以解决)又会导致内存压不下来。

根据不同的动画曲线去使用不同的精度压缩

我使用的方式是根据曲线变化的坡度来决定使用何种精度进行压缩,具体的思路是判断每个曲线的最低点与最高点的差值,来进行精度区分,比如如果一个曲线坡度值等于0,则压缩精度为f3,如果<0.1则为f4,否则为f5。需要注意的是以相同属性区分,比如某骨骼的localpostion.x,localposition.y,localpositiion.z三个曲线坐为一个整体,如果最终结果使用f5,则这三个曲线都使用f5进行压缩


3.采样频率的压缩(剔除相同关键帧)

美术导出的动画会根据一定的时间间隔进行采样,有时候为了更加细节,会将采样频率提高,这就会出现多余关键帧的情况,而这些关键帧都会被序列化到动画文件中导致动画文件很大,这里贴出压缩前面的对比效果

此方法压缩后,文件大小直接从原来的3.42MB,变到823KB,注意我这里说的是文件大小,同时内存在上面的压缩之后再次减少10~20%,不单再次优化内存,同时也使包体大幅度减小

-----------------------------------------------------------------------------------------------------------------

最后经过测试,我找到了适合我们项目的动画文件在美术外观与性能之间的一个平衡(压缩后肉眼无法分辨与原文件之间的差异),下面是我们项目“男主”的所有动画压缩前后的数据对比

内存比对(数据使用Editor 下的profiler获取,真机数据大约是这个的一半,通过下图我们可以直观的看出压缩率)

文件大小对比

最后贴出我的完整代码供大家参考

//****************************************************************************
//
//  动画文件(AnimationClip)压缩工具
//
//  Create by jiangcheng_m
//
//  注意:同一个模型的动画文件必须放到同一个文件夹下
//  压缩原理
//  1.分析得到同一个模型被缩放的所有骨骼
//  2.通过1的结果删除动画文件中没有缩放的骨骼的localscale属性和曲线
//  3.删除内容相差万分之一的中间关键帧只保留首位两帧(减少采样频率)
//  4.优化帧属性值精度
 //****************************************************************************
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

namespace CompressTool
{
    public static class SETTING
    {
        public static class FILTER
        {
            //缩放属性剔除(变化误差范围 x 分之一)
            public readonly static int ERR_RANGE_SCALE_PROPERTY = 1000;
            //相同关键帧剔(除误差范围 X 分之一)
            public readonly static int ERR_RANGE_SAME_FRAME = 10000;
        }

        //精度压缩(根据曲线变化坡度压缩,坡度越大精度越高,坡度越小精度越小)
        public static class ACCURACY
        {
            //精度1级 坡度阀值
            public readonly static float THRESHOLD1 = 0;
            //精度2级 坡度阀值
            public readonly static float THRESHOLD2 = 0.1f;
            //精度1级(小数点后3位)
            public readonly static string LEVEL1 = "f3";
            //精度2级(小数点后4位)
            public readonly static string LEVEL2 = "f4";
            //精度3级(小数点后5位)
            public readonly static string LEVEL3 = "f5";
        }
    }


    public class CompressOpt
    {
        public AnimationClip AnimClip { private set; get; }
        public string AnimClipPath { private set; get; }
        private HashSet<string> mScaleBonePaths;
        private Dictionary<string, float> mGradientVals;
        public CompressOpt(AnimationClip animClip, string animClipPath)
        {
            AnimClip = animClip;
            AnimClipPath = animClipPath;
            mGradientVals = new Dictionary<string, float>();
        }

        public void SetScaleBonePaths(HashSet<string> scaleBonePaths)
        {
            mScaleBonePaths = scaleBonePaths;
        }

        private bool Approximately(Keyframe a, Keyframe b)
        {
            return Mathf.Abs(a.value - b.value) * SETTING.FILTER.ERR_RANGE_SAME_FRAME < 1f &&
                Mathf.Abs(a.inTangent - b.inTangent) * SETTING.FILTER.ERR_RANGE_SAME_FRAME < 1f &&
                Mathf.Abs(a.outTangent - b.outTangent) * SETTING.FILTER.ERR_RANGE_SAME_FRAME < 1f &&
                Mathf.Abs(a.inWeight - b.inWeight) * SETTING.FILTER.ERR_RANGE_SAME_FRAME < 1f &&
                Mathf.Abs(a.outWeight - b.outWeight) * SETTING.FILTER.ERR_RANGE_SAME_FRAME < 1f;
        }


        private string GetCurveKey(string path, string propertyName)
        {
            var splits = propertyName.Split('.');
            var name = splits[0];
            return string.Format("{0}/{1}", path, name);
        }

        //获取曲线坡度
        private float GetCurveThreshold(string path, string propertyName)
        {
            var curveKey = GetCurveKey(path, propertyName);
            float threshold = 0;
            mGradientVals.TryGetValue(curveKey, out threshold);
            return threshold;
        }

        //设置曲线坡度
        private void SetCurveThreshold(string path, string propertyName, float threshold)
        {
            var curveKey = GetCurveKey(path, propertyName);
            if (!mGradientVals.ContainsKey(curveKey))
                mGradientVals.Add(curveKey, threshold);
            else
                mGradientVals[curveKey] = threshold;
        }

        //获取曲线压缩精度
        private string GetCompressAccuracy(string path, string propertyName)
        {
            var threshold = GetCurveThreshold(path, propertyName);
            if (threshold <= SETTING.ACCURACY.THRESHOLD1)
                return SETTING.ACCURACY.LEVEL1;
            else if (threshold <= SETTING.ACCURACY.THRESHOLD2)
                return SETTING.ACCURACY.LEVEL2;
            return SETTING.ACCURACY.LEVEL3;
        }

        public void Compress()
        {
            if (AnimClip != null)
            {
                var curveBindings = AnimationUtility.GetCurveBindings(AnimClip);
                for (int i = 0; i < curveBindings.Length; i++)
                {
                    EditorCurveBinding curveBinding = curveBindings[i];
                    float threshold = GetCurveThreshold(curveBinding.path, curveBinding.propertyName);

                    string name = curveBinding.propertyName.ToLower();
                    var curve = AnimationUtility.GetEditorCurve(AnimClip, curveBinding);
                    var keys = curve.keys;
                    if (name.Contains("scale"))
                    {
                        //优化scale曲线
                        if (!mScaleBonePaths.Contains(curveBinding.path))
                        {
                            AnimationUtility.SetEditorCurve(AnimClip, curveBinding, null);
                            continue;
                        }
                    }

                    float bottomVal = 999999;
                    float topVal = -999999;

                    //优化采样点数量
                    List<Keyframe> newFrames = new List<Keyframe>();
                    if (keys.Length > 0)
                    {
                        newFrames.Add(keys[0]);
                        var lastSameFrameIndex = 0;
                        var comparerFrameIndex = 0;
                        for (int j = 1; j < keys.Length; j++)
                        {
                            var curFrame = keys[j];
                            var comparerFrame = keys[comparerFrameIndex];
                            if (Approximately(curFrame, comparerFrame))
                            {
                                lastSameFrameIndex = j;
                            }
                            else
                            {
                                if (lastSameFrameIndex > comparerFrameIndex)
                                    newFrames.Add(keys[lastSameFrameIndex]);
                                newFrames.Add(keys[j]);
                                comparerFrameIndex = j;
                            }
                            bottomVal = Mathf.Min(bottomVal, keys[j].value);
                            topVal = Mathf.Max(topVal, keys[j].value);
                        }

                        if (newFrames.Count == 1)
                            newFrames.Add(keys[keys.Length - 1]);//最少两帧

                        if (newFrames.Count != keys.Length)
                        {
                            curve.keys = newFrames.ToArray();
                            //Debug.LogFormat("{0}=>{1}", keys.Length, newFrames.Count);
                            AnimationUtility.SetEditorCurve(AnimClip, curveBinding, curve);
                        }
                    }

                    SetCurveThreshold(curveBinding.path, curveBinding.propertyName, Mathf.Max(threshold, topVal - bottomVal));
                }

                //优化精度
                AnimationClipCurveData[] curves = AnimationUtility.GetAllCurves(AnimClip);
                if (curves != null && curves.Length > 0)
                {
                    for (int i = 0; i < curves.Length; i++)
                    {
                        AnimationClipCurveData curveDate = curves[i];
                        if (curveDate.curve == null || curveDate.curve.keys == null)
                            continue;

                        string accuracy = GetCompressAccuracy(curveDate.path, curveDate.propertyName);
                        Keyframe[] keyFrames = curveDate.curve.keys;
                        for (int j = 0; j < keyFrames.Length; j++)
                        {
                            Keyframe key = keyFrames[j];
                            key.value = float.Parse(key.value.ToString(accuracy));
                            //切线固定精度
                            key.inTangent = float.Parse(key.inTangent.ToString("f3"));
                            key.outTangent = float.Parse(key.outTangent.ToString("f3"));
                            keyFrames[j] = key;
                        }
                        curveDate.curve.keys = keyFrames;
                        AnimClip.SetCurve(curveDate.path, curveDate.type, curveDate.propertyName, curveDate.curve);
                    }
                }
            }
        }
    }

    
    public class AnimClipDirectory
    {
        public string Path { get; }
        public List<string> AnimClipPaths { get; private set; }
        public List<CompressOpt> CompressOpts { get; private set; }
        public AnimClipDirectory(string directory)
        {
            Path = directory;
            AnimClipPaths = new List<string>();
            CompressOpts = new List<CompressOpt>();
        }

        public void AddAnimClipPath(string animClipPath)
        {
            AnimClipPaths.Add(animClipPath);
        }

        //分析被缩放的所有骨骼路径
        public void Analyse()
        {
            HashSet<string> scaleBonePaths = new HashSet<string>();
            for (int i = 0; i < AnimClipPaths.Count; i++)
            {
                var assetPath = AnimClipPaths[i];
                AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(assetPath);
                CompressOpts.Add(new CompressOpt(clip, assetPath));
                AnimationClipCurveData[] curves = AnimationUtility.GetAllCurves(clip);
                if (curves != null && curves.Length > 0)
                {
                    for (int j = 0; j < curves.Length; j++)
                    {
                        string name = curves[j].propertyName.ToLower();
                        if (name.Contains("scale"))
                        {
                            AnimationClipCurveData curveDate = curves[j];
                            if (curveDate.curve == null || curveDate.curve.keys == null)
                                continue;
                            var keyFrames = curveDate.curve.keys;
                            bool isScaleChanged = false;
                            if (keyFrames.Length > 0)
                            {
                                var frist = keyFrames[0].value;
                                if (Mathf.Abs(frist - 1f) * SETTING.FILTER.ERR_RANGE_SCALE_PROPERTY > 1f) //如果第一帧大小变了
                                    isScaleChanged = true;
                                else
                                {
                                    for (int k = 1; k < keyFrames.Length; k++)
                                    {
                                        if (Mathf.Abs(keyFrames[k].value - frist) * SETTING.FILTER.ERR_RANGE_SCALE_PROPERTY > 1f) //如果差异超过千分之一,则不可删除
                                        {
                                            isScaleChanged = true;
                                            break;
                                        }
                                    }
                                }
                            }
                            if (isScaleChanged)
                                scaleBonePaths.Add(curves[j].path);
                        }
                    }
                }
            }

            for (int i = 0; i < CompressOpts.Count; i++)
            {
                CompressOpts[i].SetScaleBonePaths(scaleBonePaths);
            }

        }
    }


    public class AnimClipCompressTool
    {
        private enum ProcessType
        {
            Analyse,
            Compress,
            Finish,
        }
        private static ProcessType mCurProcess;
        private static List<AnimClipDirectory> mAnimClipDirectoryList;
        private static List<CompressOpt> mCompressOptList;
        private static int mIndex = 0;

        [MenuItem("Assets/TA/Compress AnimationClip", priority = 2001)]
        public static void Optimize()
        {
            Dictionary<string, AnimClipDirectory> animClipPaths = new Dictionary<string, AnimClipDirectory>();
            var selectObjs = Selection.objects;
            if (selectObjs != null && selectObjs.Length > 0)
            {
                for (int i = 0; i < selectObjs.Length; i++)
                {
                    var assetPath = AssetDatabase.GetAssetPath(selectObjs[i]);
                    GetAllAnimClipPaths(assetPath, ref animClipPaths);
                }
            }

            mAnimClipDirectoryList = new List<AnimClipDirectory>();
            mAnimClipDirectoryList.AddRange(animClipPaths.Values);
            mCompressOptList = new List<CompressOpt>();

            mIndex = 0;
            mCurProcess = ProcessType.Analyse;

            if (mAnimClipDirectoryList.Count > 0)
                EditorApplication.update = Update;
            else
                EditorUtility.DisplayDialog("Tips", "can not found AnimationClip file!", "ok");
        }

        private static void Update()
        {
            if (mCurProcess == ProcessType.Analyse)
            {
                AnimClipDirectory animClipDirectory = mAnimClipDirectoryList[mIndex];
                bool isCancel = EditorUtility.DisplayCancelableProgressBar(string.Format("正在读取AnimationClip文件夹信息[{0}/{1}])", mIndex, mAnimClipDirectoryList.Count), animClipDirectory.Path, (float)mIndex / (float)mAnimClipDirectoryList.Count);
                if (isCancel)
                    mCurProcess = ProcessType.Compress;
                else
                {
                    animClipDirectory.Analyse();
                    mIndex++;
                    if (mIndex >= mAnimClipDirectoryList.Count)
                    {
                        for (int i = 0; i < mAnimClipDirectoryList.Count; i++)
                            mCompressOptList.AddRange(mAnimClipDirectoryList[i].CompressOpts);
                        
                        if (mCompressOptList.Count > 0)
                            mCurProcess = ProcessType.Compress;
                        else
                            mCurProcess = ProcessType.Finish;
                        mIndex = 0;
                    }
                }
            }
            else if (mCurProcess == ProcessType.Compress)
            {
                CompressOpt compressOpt = mCompressOptList[mIndex];
                bool isCancel = EditorUtility.DisplayCancelableProgressBar(string.Format("正在压缩AnimationClip文件[{0}/{1}]", mIndex, mCompressOptList.Count), compressOpt.AnimClipPath, (float)mIndex / (float)mCompressOptList.Count);
                if (isCancel)
                    mCurProcess = ProcessType.Finish;
                else
                {
                    compressOpt.Compress();
                    mIndex++;
                    if (mIndex >= mCompressOptList.Count)
                        mCurProcess = ProcessType.Finish;
                }
            }
            else if (mCurProcess == ProcessType.Finish)
            {
                mAnimClipDirectoryList = null;
                mCompressOptList = null;
                mIndex = 0;
                EditorUtility.ClearProgressBar();
                Resources.UnloadUnusedAssets();
                GC.Collect();
                AssetDatabase.SaveAssets();
                EditorApplication.update = null;
            }


        }

        private static void GetAllAnimClipPaths(string assetPath, ref Dictionary<string, AnimClipDirectory> animClipPaths)
        {
            if (IsDirectory(assetPath))
            {
                if (!assetPath.Contains(".."))
                {
                    string[] paths = System.IO.Directory.GetFileSystemEntries(assetPath);
                    for (int i = 0; i < paths.Length; i++)
                    {
                        var path = paths[i];
                        if (IsDirectory(path))
                        {
                            GetAllAnimClipPaths(path, ref animClipPaths);
                        }
                        else
                        {
                            if (path.EndsWith(".anim"))
                            {
                                var directoryPath = GetFileDirectoryPath(path);
                                if (!animClipPaths.ContainsKey(directoryPath))
                                    animClipPaths.Add(directoryPath, new AnimClipDirectory(directoryPath));
                                animClipPaths[directoryPath].AddAnimClipPath(path);
                            }
                        }
                    }
                }
            }
            else
            {
                if (assetPath.EndsWith(".anim"))
                {
                    var directoryPath = GetFileDirectoryPath(assetPath);
                    if (!animClipPaths.ContainsKey(directoryPath))
                        animClipPaths.Add(directoryPath, new AnimClipDirectory(directoryPath));
                    animClipPaths[directoryPath].AddAnimClipPath(assetPath);
                }
            }
        }

        private static bool IsDirectory(string assetPath)
        {
            Debug.Log(System.IO.File.GetAttributes(assetPath));
            return System.IO.File.GetAttributes(assetPath) == System.IO.FileAttributes.Directory;
        }

        private static string GetFileDirectoryPath(string filePath)
        {
            var fileName = System.IO.Path.GetFileName(filePath);
            var directoryPath = filePath.Replace(fileName, "");
            return directoryPath;
        }

    }
}

标签: 暂无
最后更新:2021-11-21

蒋程

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

您需要 登录 之后才可以评论

COPYRIGHT © 2023 白泽图. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

登录
注册|忘记密码?