FileSize
- FileInfo.Length取得的文件大小
- 可以在操纵体系的文件体系中看到
MemorySize
- Profiler.GetRuntimeMemorySize取得的内存大小
- 可以在Profiler中通过采样看到
- 分别在真机和Editor下举行了采样
BlobSize
- 反射取得的AnimationClipStats.size二进制大小
- 表现在AnimationClip的Inspector的面板上
赤色框内便是BlobSize,在我的明确,FileSize是指文件在硬盘中占的大小,BlobSize是从文件反序列化出来的对象的二进制大小。Editor下的MemorySize不光有序列化后的内存大小,还维护了一份原文件的内存。就像我们在Editor下加载一张Texture内存是双份一样,而真机下就约便是BlobSize。真机下的MemorySize和Inspector里的BlobSize非常靠近,BlobSize可以以为是真机上的内存大小,这个大小更有参考意义。
同时,我也对去除Scale曲线的方法举行了实行。下图这个动画文件原来Inspector中Scale的值为4,即有Scale曲线,原始文件BlobSize为10.2KB,去除Scale曲线后,Blob Size变为7.4KB,以是BlobSize减小了27%。
Curve淘汰导致内存减小
从上面的实行可以看出来,只裁剪动画文件的压缩精度,没有引起Curve淘汰。BlobSize是不会有任何厘革的,因为每个浮点数固定占32bit。而文件大小、AB大小、Editor下的内存大小,压缩精度后不管有没有引起Curve的厘革,都会变小。
裁剪动画文件的精度,意味着点的位置发生了厘革,以是Constant Curve和Dense Curve的数目也有大概发生厘革。由于是裁剪精度以是动画的点更希奇了,而连续雷同的点更多了。以是Dense Curve是淘汰了,Constant Curve是增多了,总的内存是减小了。
Constant Curve只须要最左边的点就可以形貌一个曲线段。
只裁剪精度使BlobSize减小的实例
裁剪精度前,大小为2.2kb,ScaleCurve为0, ConstantCurve为4(57.1%),Stream(使用Optimal模式这部门数据将储存为Dense)为3(42.9%)。
裁剪精度后,大小为2.1kb,ConstantCurve为7(100%),Stream为0(0%)。裁剪完精度后导致ConstantCurve增长了3,Stream(Optimal模式下即为Dense)淘汰了3,BlobSize减小了0.1kb。
因此,可以看出,通过精度优化降低内存的方式,着实质是将曲线上过于靠近的数值(例如相差数值出现在小数点4位以后)直接变为划一,从而使部门曲线变为constant曲线来降低内存斲丧。
取BlobSize代码
AnimationClip aniClip = AssetDatabase.LoadAssetAtPath<AnimationClip> (path); var fileInfo = new System.IO.FileInfo(path); Debug.Log(fileInfo.Length);//FileSize Debug.Log(Profiler.GetRuntimeMemorySize (aniClip));//MemorySize Assembly asm = Assembly.GetAssembly(typeof(Editor)); MethodInfo getAnimationClipStats = typeof(AnimationUtility).GetMethod("GetAnimationClipStats", BindingFlags.Static | BindingFlags.NonPublic); Type aniclipstats = asm.GetType("UnityEditor.AnimationClipStats"); FieldInfo sizeInfo = aniclipstats.GetField ("size", BindingFlags.Public | BindingFlags.Instance); var stats = getAnimationClipStats.Invoke(null, new object[]{aniClip}); Debug.Log(EditorUtility.FormatBytes((int)sizeInfo.GetValue(stats)));//BlobSize 工具代码
末了附上工具的代码和扼要使用阐明,选中想要优化的文件夹或文件,右键Animation->裁剪浮点数去除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;using UnityEngine.Profiling;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; 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 = 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(bool scaleOpt) { Optimize(scaleOpt, 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(会删除Clip Scale动画,慎用)")] public static void OptimizeScale() { _AnimOptList = FindAnims(); if (_AnimOptList.Count > 0) { _Index = 0; _Errors.Clear(); //EditorApplication.update = ScanAnimationClip; ScanAnimationClip(true); } } [MenuItem("Assets/Animation/裁剪浮点数")] public static void Optimize() { _AnimOptList = FindAnims(); if (_AnimOptList.Count > 0) { _Index = 0; _Errors.Clear(); //EditorApplication.update = ScanAnimationClip; ScanAnimationClip(true); } } private static void ScanAnimationClip(bool scaleOpt) { AnimationOpt _AnimOpt = _AnimOptList[_Index]; bool isCancel = EditorUtility.DisplayCancelableProgressBar("优化AnimationClip", _AnimOpt.path, (float)_Index / (float)_AnimOptList.Count); _AnimOpt.Optimize_Scale_Float3(scaleOpt); _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.GetType() == typeof(AnimationClip)) { string p = AssetDatabase.GetAssetPath(objs); AnimationOpt animopt = _GetNewAOpt(p); if (animopt != null) assets.Add(animopt); } else path.Add(AssetDatabase.GetAssetPath(objs)); } 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); AnimationOpt animopt = _GetNewAOpt(assetPath); if (animopt != null) assets.Add(animopt); } return assets; } }} |