FileSize
- FileInfo.Length取得的文件大小
- 可以在操作系統(tǒng)的文件系統(tǒng)中看到
MemorySize
- Profiler.GetRuntimeMemorySize取得的內存大小
- 可以在Profiler中通過采樣看到
- 分別在真機和Editor下進行了采樣
BlobSize
- 反射取得的AnimationClipStats.size二進制大小
- 顯示在AnimationClip的Inspector的面板上
紅色框內即是BlobSize柳刮,在我的理解,F(xiàn)ileSize是指文件在硬盤中占的大小痢毒,BlobSize是從文件反序列化出來的對象的二進制大小哪替。Editor下的MemorySize不僅有序列化后的內存大小凭舶,還維護了一份原文件的內存爱沟。就像我們在Editor下加載一張Texture內存是雙份一樣身冀,而真機下就約等于BlobSize搂根。真機下的MemorySize和Inspector里的BlobSize非常接近,BlobSize可以認為是真機上的內存大小,這個大小更有參考意義。
同時瓶殃,我也對去除Scale曲線的方法進行了實驗遥椿。下圖這個動畫文件原來Inspector中Scale的值為4,即有Scale曲線冠场,原始文件BlobSize為10.2KB,去除Scale曲線后碴裙,Blob Size變?yōu)?.4KB,所以BlobSize減小了27%莺琳。
Curve減少導致內存減小
從上面的實驗可以看出來,只裁剪動畫文件的壓縮精度载慈,沒有引起Curve減少。BlobSize是不會有任何變化的办铡,因為每個浮點數固定占32bit料扰。而文件大小、AB大小拯钻、Editor下的內存大小,壓縮精度后不管有沒有引起Curve的變化,都會變小亩歹。
裁剪動畫文件的精度匙监,意味著點的位置發(fā)生了變化,所以Constant Curve和Dense Curve的數量也有可能發(fā)生變化小作。由于是裁剪精度所以動畫的點更稀疏了亭姥,而連續(xù)相同的點更多了。所以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。
因此吊洼,可以看出训貌,通過精度優(yōu)化降低內存的方式,其實質是將曲線上過于接近的數值(例如相差數值出現(xiàn)在小數點4位以后)直接變?yōu)橐恢旅扒希瑥亩共糠智€變?yōu)閏onstant曲線來降低內存消耗递沪。
取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
工具代碼
最后附上工具的代碼和簡要使用說明,選中想要優(yōu)化的文件夾或文件综液,右鍵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[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(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("優(yōu)化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("--優(yōu)化完成-- 錯誤數量: {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;
}
}
}