unity 动画优化

  1. 优化思路

(1).降低帧信息的精度

unityEditor存储的动画文件是text文件,所以缩短浮点数精度,会导致文件表面上有所减少,裁剪动画文件的精度,会让动画的点变的更加稀疏(会将一些曲线上相近的数值,变为一致),Dense Curve是减少了,Constant Curve是增多了,总的内存是减小了。

(2). 去除没有变化的帧动画

去除没有变化的Position,Rotation序列帧,只留头尾两帧。

(3). 去除冗余Scale曲线数据

fbx中很少用到Scale曲线做动画,所以在跟美术的小伙伴确定之后,这部分是可以去除的。PS(如果有需要的话可以在用到Scale变化的骨骼节点加上关键字区分)

2.代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Linq;

namespace EngineTeam.ToolTeam.Animation
{
    public class AnimationClipExtract : EditorWindow
    {
        private const float AnimationPositionError = 0.2f;
        private const float AnimationRotationError = 0.1f;
        //ModelImporterAnimationCompression--动画压缩选项
        //Optimal--执行减少关键帧的操作,并在运行时选择最佳动画曲线表示以减少内存占用(默认)。
        private const ModelImporterAnimationCompression Compression = ModelImporterAnimationCompression.Optimal;
        private const int DecimalAccuracy = 10000;

        [MenuItem("Tools/AnimationClip提取")]
        static void RightBtn()
        {
            ProcessFbx(true);
        }
        public static void ProcessFbx(bool reProcess = false)
        {
            mAnimationClipList.Clear();

            //获取工具路径
            List<string> assets = ToolUtility.GetAssetPath();
            int index = 0;
            int count = assets.Count;

            foreach (var ass in assets)
            {
                //显示或更新含有 Cancel 按钮的进度条
                bool isCancel = EditorUtility.DisplayCancelableProgressBar("提取AnimationClip", string.Format("正在提取:{0}/{1}", ++index, count), (float)index / count);
                if (isCancel)
                {
                    EditorUtility.ClearProgressBar();
                    return;
                }
                //Path.GetExtension    返回指定路径字符串的扩展名(包括句点“.”)
                if (Path.GetExtension(ass).ToLower().Contains(".fbx"))
                {
                    ExtractAnimationClip(ass, reProcess);
                }
            }
            //清理进度条
            EditorUtility.ClearProgressBar();
        }
        public static List<string> mAnimationClipList = new List<string>();
        //提取动画片段
        private static string ExtractAnimationClip(string fbxPath, bool reProcess = false)
        {
            //表示空字符串,此字段为只读
            string folderPath = string.Empty;

            //返回一个值,该值指示指定的字符是否出现在此字符串中。
            if (fbxPath.Contains("@"))
            {
                //AssetDatabase-可用于访问项目中包含的资源
                //LoadAllAssetsAtPath在加载的时候, 从脚本加载和访问资源
                Object[] objs = AssetDatabase.LoadAllAssetsAtPath(fbxPath);
                int index = 0;
                foreach (var obj in objs)
                {
                    //AnimationClip-保存基于关键帧的动画
                    if (obj is AnimationClip)
                    {
                        //预览和第一帧就跳出
                        if (obj.name == "__preview__Take 001")
                            continue;
                        if (obj.name.Contains("__preview__"))
                            continue;
                        if (index++ == 0)
                        {
                            //GetDirectoryName--返回指定路径的目录信息
                            folderPath = Path.GetDirectoryName(fbxPath) + "/Clips";
                            //Directory.Exists-确定给定路径是否引用磁盘上的现有目录。
                            if (!Directory.Exists(folderPath))
                            {
                                //没有就创建
                                Directory.CreateDirectory(folderPath);
                            }
                        }
                        AnimationClip clip = (AnimationClip)obj;
                        string _filePath = folderPath + "/" + clip.name + ".anim";
                        if (reProcess)
                        {
                            if (File.Exists(_filePath))
                            {
                                mAnimationClipList.Add(_filePath);
                                continue;
                            }
                        }
                        if (!OptimizeAnimationCurveData(clip, folderPath))
                        {
                            Debug.LogError("动画文件异常,导出动画失败:" + fbxPath + "  " + clip.name);
                        }
                        mAnimationClipList.Add(_filePath);
                    }
                }
            }
            return folderPath;
        }

        /// <summary>
        /// 优化动画片段
        /// 1.删除不需要的序列帧
        /// 2.降低帧信息的精度(缩短动画曲线数据产生的浮点数精度)
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="folderPath"></param>
        /// <returns></returns>
        public static bool OptimizeAnimationCurveData(AnimationClip clip, string folderPath)
        {
            if (clip == null)
            {
                return false;
            }

            //AnimationUtility.GetAllCurves从特定动画剪辑中检索所有曲线
            var curveDatas = AnimationUtility.GetAllCurves(clip, true);
            if (curveDatas == null || curveDatas.Length == 0)
            {
                return false;
            }

            AnimationClip newClip = new AnimationClip();
            //复制 Unity Object 的所有设置
            EditorUtility.CopySerialized(clip, newClip);


            newClip.name = clip.name;
            //ClearCurves 清除该剪辑中的所有曲线
            newClip.ClearCurves();

            foreach (var dt in curveDatas)
            {
                var nodeName = dt.path.ToLower().Split('/').Last();

                //缩短动画曲线数据产生的浮点数精度
                var keys = dt.curve.keys;
                for (var i = 0; i < keys.Length; i++)
                {
                    //如果数字结尾是 .5,从而使它处于两个整数正中间(其中一个是偶数,另一个是奇数),则返回偶数
                    keys[i].time = Mathf.Round(keys[i].time * DecimalAccuracy) / DecimalAccuracy;
                    keys[i].value = Mathf.Round(keys[i].value * DecimalAccuracy) / DecimalAccuracy;
                    //曲线正切值
                    keys[i].outTangent = Mathf.Round(keys[i].outTangent * DecimalAccuracy) / DecimalAccuracy;
                    keys[i].inTangent = Mathf.Round(keys[i].inTangent * DecimalAccuracy) / DecimalAccuracy;
                }

                //过滤位移值没有变化的帧动画
                //因为帧信息有初始位置,所有要保留头尾两帧,如果全部删除会出现初始位置为默认值的问题
                if (IsFilterApproximateKeyFrame(ref keys))
                {
                    //创建关键帧
                    var newKeys = new Keyframe[2];
                    newKeys[0] = keys[0];
                    newKeys[1] = keys[keys.Length - 1];
                    keys = newKeys;
                }
                dt.curve.keys = keys;
                //设置新数据
                newClip.SetCurve(dt.path, dt.type, dt.propertyName, dt.curve);
            }

            //在此路径下创建一个新资源。
            AssetDatabase.CreateAsset(newClip, folderPath + @"/" + newClip.name + ".anim");
            //导入所有更改的资源。
            AssetDatabase.Refresh();


            return true;
        }

        /// <summary>
        /// 动画默认不导出Scale序列帧,除非该节点包含scale关键词(加scale关键词表示该节点需要进行scale变换)
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        private static bool IsFilterCurveData(AnimationClipCurveData dt, string nodeName)
        {
            if (dt.propertyName.ToLower().Contains("scale") && !nodeName.Contains("scale"))
                return true;
            return false;
        }

        /// <summary>
        /// 过滤值一样的序列帧
        /// </summary>
        /// <param name="keys"></param>
        /// <returns></returns>
        private static bool IsFilterApproximateKeyFrame(ref Keyframe[] keys)
        {
            for (var i = 0; i < keys.Length - 1; i++)
            {
                //Mathf.Abs 返回 f 的绝对值
                if (Mathf.Abs(keys[i].value - keys[i + 1].value) > 0 ||
                    Mathf.Abs(keys[i].outTangent - keys[i + 1].outTangent) > 0
                    || Mathf.Abs(keys[i].inTangent - keys[i + 1].inTangent) > 0)
                {
                    return false;
                }
            }
            return true;
        }

        private static string GetNewClipDir(string clipPath)
        {
            int index = clipPath.LastIndexOf("/");
            if (index < 0)
            {
                index = clipPath.LastIndexOf("\\");
            }
            string ret = clipPath.Substring(0, index + 1);

            string newFolder = string.Format("{0}\\Clips", ret);
            if (!Directory.Exists(newFolder))
            {
                Directory.CreateDirectory(newFolder);
            }
            return newFolder;
        }

    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值