这两天简单看了一下Unity的动画文件的优化,主要参考下面两篇
(https://blog.uwa4d.com/archives/Optimization_Animation.html) Unity动画文件优化探究
(https://blog.csdn.net/u013917120/article/details/79824448) Unity实用案例之——动画压缩
(https://www.cnblogs.com/wayland/p/7131055.html) unity的animation动画资源压缩
其中压缩的主要思路有两点:
1. 移除scale曲线
foreach(AnimationClip animClip in tAnimationClipList)
{
foreach(EditorCurveBinding curveBinding in AnimationUtility.GetCurveBindings(animClip))
{
string tName = curveBinding.propertyName.ToLower();
if(tName.Contains("scale"))
{
AnimationUtility.SetEditorCurve(animClip, curveBinding, null);
}
}
CompressAnimationClip(animClip);
}
2. 压缩精度
public static void CompressAnimationClip(AnimationClip _clip)
{
AnimationClipCurveData[] tCurveArr = AnimationUtility.GetAllCurves(_clip);
Keyframe tKey;
Keyframe[] tKeyFrameArr;
for(int i = 0; i < tCurveArr.Length; ++i)
{
AnimationClipCurveData tCurveData = tCurveArr[i];
if(tCurveData.curve == null || tCurveData.curve.keys == null)
{
continue;
}
tKeyFrameArr = tCurveData.curve.keys;
for(int j = 0; j < tKeyFrameArr.Length; j++)
{
tKey = tKeyFrameArr[j];
tKey.value = float.Parse(tKey.value.ToString("f3")); //#.###
tKey.inTangent = float.Parse(tKey.inTangent.ToString("f3"));
tKey.outTangent = float.Parse(tKey.outTangent.ToString("f3"));
tKeyFrameArr[j] = tKey;
}
tCurveData.curve.keys = tKeyFrameArr;
_clip.SetCurve(tCurveData.path, tCurveData.type, tCurveData.propertyName, tCurveData.curve);
}
}
在做完这两步后,几个关键的值确实要小了,文件大小也减小了一些。
取了一个动画片段的压缩前后的数据(如上两图的对比,上面是压缩前,下面是压缩后),可以看到确有改善。
实际运用中会有几个关键的点,状态机中的动画与motion指定的动画的名字上可能不同,所以直接从controller中获取animationClips会有两个问题:
1. 名字不匹配,那么在重新绑定的时候可能绑定不上;
2. 直接new操作得到的AnimationClip即使用了压缩的操作,有可能得到的也要比直接的FBX文件中的文件要大。
然后试着直接从FBX文件中提出.anim文件(Unity中直接操作的快捷键是Ctrl+D),然后单独压缩,会发现实际确实会小很多,所以,解决思路就有了。
static void ExtractAnimationAndSave(string path)
{
var controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(path);
if (controller == null || controller.layers == null)
{
return;
}
var fileInfo = new FileInfo(path);
string folder = fileInfo.DirectoryName;
var oldClipMap = new Dictionary<string, string>();
// 提取状态机中每个状态的motion
for (var i = 0; i < controller.layers.Length; ++i)
{
var stateMachine = controller.layers[layerIndex].stateMachine;
if(stateMachine == null)
{
continue;
}
var childStates = animatorStateMachine.states;
if(childStates == null)
{
continue;
}
for(var index = 0; index < childStates.Length; ++index)
{
var motion = childStates[childStateIndex].state.motion;
if(motion == null)
{
continue;
}
if (motion is BlendTree)
{
var motionBlendTree = motion as BlendTree;
if(motionBlendTree == null || motionBlendTree.children == null)
{
continue;
}
foreach(var child in motionBlendTree.children)
{
oldClipMap.Add(child.motion.name, child.motion.name);
}
}
else
{
oldClipMap.Add(childStates[childStateIndex].state.name, motion.name);
}
}
}
var newClipMap = new Dictionary<string, AnimationClip>();
// 从.FBX文件中提取AnimationClip文件
// 参考https://www.cnblogs.com/jietian331/p/7264367.html)
foreach (var clip in oldClipMap)
{Path.Combine(path1, path2)
string fbxPath = $"{folder.Replace('\\', '/')}/{controller.name + "@" + clip.Value + ".FBX"}";
fbxPath = Path.Combine("Assets", fbxPath.Substring(Application.dataPath.Length + 1)).Replace('\\', '/');
AnimationClip src = AssetDatabase.LoadAssetAtPath<AnimationClip>(fbxPath);
AnimationClip newClip = new AnimationClip();
EditorUtility.CopySerialized(src, newClip);
string animPath = $"{folder.Replace('\\', '/')}/{newClip.name + ".anim"}";
animPath = Path.Combine("Assets", animPath.Substring(Application.dataPath.Length + 1)).Replace('\\', '/');
if (!File.Exists(animPath))
{
AssetDatabase.CreateAsset(newClip, animPath);
AnimationCompress.CompressAnimationiClip(newClip);
AssetDatabase.SaveAssets();//保存修改
newClipMap.Add(clip.Key, newClip);
}
}
if (newClipMap.Count > 0)
{
// 重新绑定
RebindAnimatorController(controller, newClipMap);
}
AssetDatabase.SaveAssets();//保存修改
}