参考文章:https://cloud.tencent.com/developer/article/1833109
最近项目需求需要对工程中的动画文件进行优化,经分析发现,工程存在两种动画文件,一种是后缀为.anim的普通Animation文件,还有一种是.fbx的模型动画文件,本文主要针对于fbx动画文件的优化,普通Animation文件优化空间不大。通过查阅网上资料,基本上优化思路也都一致,由于网上的各种教程并没有教怎么去自动提取并绑定fbx中的动画,因此本人记录一下自身总结出来的方法,优化思路大致分为两点(经过研究发现还有第三点思路,等我测试完毕整理后再发出来,大致思路是直接检测动画中是否存在没有变化的帧曲线数据,如果存在就去掉,感觉也是一个很大的优化项):
一、缩短动画曲线数据产生的浮点数精度
因为最终打包动画文件都会转成assetbundle二进制格式文件,不管怎么缩短浮点数精度,最终都是占用一个float的空间(之所以看起来减少了文件大小,是因为unityEditor下存储的是text格式,所以会导致文件表面上减小了),那究竟是怎么达到压缩效果的呢?
原来,通过降低浮点数位数后,将曲线上过于接近的数值(例如相差数值出现在小数点4位以后)直接变为一致,可以产生更多的const曲线,从而让引擎达到更高效存储的效果,进而达到所谓的“压缩”结果。缩短float类型的精度,导致动画文件内点的位置发生了变化,引起Constant Curve和Dense Curve的数量也有可能发生变化,最终可能导致动画的点更稀疏,而连续相同的点更多了。所以Dense Curve是减少了,Constant Curve是增多了,总的内存是减小了。
代码如下:
Tips:网上有的文章说获取动画Clip的EditorCurveBinding有两种方法,非Legacy(选中动画的Inspector面板的右上角点击…切换为Debug模式就可以看到Legacy选项了)动画使用GetCurveBindings、GetEditorCurve和SetEditorCurve方法,Legacy动画要使用GetObjectReferenceCurveBindings、GetObjectReferenceCurve和SetObjectReferenceCurve方法。但是经过本人亲测,GetCurveBindings这套方式两种动画皆适用,而且另一套方法由于返回值不一样,导致使用起来极为繁琐,有知道这两套方法详细区别的大佬麻烦在评论区告知一下,感激不尽。
/// <summary>
/// 压缩浮点数精度
/// </summary>
/// <param name="clip"></param>
private static void CompressAnimFloatNum(AnimationClip clip)
{
if (clip == null) return;
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
foreach (var curveBinding in curveBindings)
{
var curve = AnimationUtility.GetEditorCurve(clip, curveBinding);
if (curve == null || curve.keys == null)
{
var path = AssetDatabase.GetAssetPath(clip);
OptimizeAnimTools_Auto.optimizedPaths.Remove(path);
Debug.LogError("资源发生错误,重新导入,path:" + path);
AssetDatabase.ImportAsset(path);
return;
}
var keyFrames = curve.keys;
for (int i = 0; i < keyFrames.Length; i++)
{
var key = keyFrames[i];
key.value = float.Parse(key.value.ToString("f" + limitFloatNum));
key.inTangent = float.Parse(key.inTangent.ToString("f" + limitFloatNum));
key.outTangent = float.Parse(key.outTangent.ToString("f" + limitFloatNum));
key.inWeight = float.Parse(key.inWeight.ToString("f" + limitFloatNum));
key.outWeight = float.Parse(key.outWeight.ToString("f" + limitFloatNum));
keyFrames[i] = key;
}
curve.keys = keyFrames;
//优化普通Animation动画有时会出现错误修改未发生变化的scale值,一番踩坑之后,发现调用一次这个就好了,希望有懂的大佬可以解答一下,感激不尽!
AnimationUtility.SetEditorCurve(clip, curveBinding, curve);
clip.SetCurve(curveBinding.path, curveBinding.type, curveBinding.propertyName, curve);
}
}
二、去除fbx动画文件中生成的冗余Scale曲线数据
从下图可以看到,fbx文件中的动画几乎每一个节点都生成了Scale曲线数据,然而fbx动画基本却不会用到Scale曲线来做动画(为了以防万一,如果需要用到这种方式来优化,请向美术同事确认是否没有用过Scale缩放来做动画),所以这部分数据是可以去除的,这部分优化程度比较大,最好尽可能优化掉。还需注意一点,这种优化方式仅限于fbx中的模型动画,如果是UI上的普通Animation动画,是不需要优化Scale曲线的,因为Unity不会生成冗余的Scale数据,而且美术同学很可能会在普通Animation动画中使用Scale缩放。
代码如下:
/// <summary>
/// 删除scale曲线(一般用于模型动画)相关
/// </summary>
/// <param name="clip"></param>
private static void ReduceScaleKey(AnimationClip clip)
{
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
foreach (EditorCurveBinding curveBinding in curveBindings)
{
string name = curveBinding.propertyName.ToLower();
if (name.Contains("scale"))
{
AnimationUtility.SetEditorCurve(clip, curveBinding, null);
}
}
}
以上介绍完了动画的两种优化方式,接下来开始介绍怎么从Fbx文件中提取出包含的Aniamtion动画文件,并重新绑定优化新创建的Aniamtion动画文件。
因为Fbx文件在Unity中是只读的,内含的动画文件也是只读的,修改动画的结果实际上是保存在Library/metadata,并不会修改Fbx文件,也就是说这个修改是本地化的操作,无法放入版本管理。fbx中的animation是read-only的,要编辑需要将动画文件复制出来,可以选中fbx中的动画文件,ctrl+D复制一份出来,可以跟优化后的animation文件做个对比,复制出的文件是可以编辑的,但是作为程序员,我们肯定是希望自动化操作的。从Fbx拷贝出动画后记得一定要重新绑定新生成的Animation文件,一般Fbx动画都会生成一个AnimatorController来控制动画,修改其中的引用就好了,代码中生成的逻辑是按照我们自己项目的文件结构来的,新的Animation会输出到Fbx所在目录的Animation文件夹下,如有需要,可以自行修改,代码如下:
/// <summary>
/// 从Fbx文件中拷贝动画并优化和重新绑定引用
/// </summary>
/// <param name="path">fbx文件的相对路径</param>
private static void CopyClipAndCompress_FromFbx(string path)
{
Object[] objs = AssetDatabase.LoadAllAssetsAtPath(path);
//fbx相关的模型prefeb所在文件夹相对路径(根据自己项目的文件架构自行修改路径相关代码)
string modelDirectoryPath = ConvertToAssetPath(new DirectoryInfo(path).Parent.Parent.FullName);
//提取出的Animation动画输出到父目录的父目录下的Animation文件夹下
string outAnimDirectoryPath = $"{modelDirectoryPath}/Animation";
if (!AssetDatabase.IsValidFolder(outAnimDirectoryPath))
{
AssetDatabase.CreateFolder(modelDirectoryPath, "Animation");
}
string fbxFileName = Path.GetFileName(path);
var animatorGuids = AssetDatabase.FindAssets("t:AnimatorController", new[] {modelDirectoryPath});
if (animatorGuids == null || animatorGuids.Length == 0)
{
Debug.LogError($"从{fbxFileName} 提取动画失败,没有找到{modelDirectoryPath}目录下的AnimatorController文件!");
return;
}
foreach (var _obj in objs)
{
if (_obj is AnimationClip && !_obj.name.Contains("__preview__"))
{
EditorUtility.DisplayProgressBar($"从{fbxFileName}提取动画", _obj.name, 0);
//重新绑定AnimatorController引用
bool isBindSuccess = ReBindAnimatorController(path, _obj as AnimationClip, animatorGuids, outAnimDirectoryPath,out AnimationClip newAnimationClip);
if (!isBindSuccess)
{
Debug.LogError($"从{fbxFileName} 提取动画 {_obj.name} 失败,没有找到对应的AnimatorController引用!");
continue;
}
if (newAnimationClip == null || newAnimationClip.length==0)
{
Debug.LogError($"从{fbxFileName} 提取动画 {_obj.name} 失败,未能成功生成Animation文件!");
continue;
}
}
}
}
/// <summary>
/// 重新绑定AnimatorController引用
/// </summary>
private static bool ReBindAnimatorController(string fbxPath, AnimationClip animationClip, string[] animatorGuids, string outAnimDirectoryPath,out AnimationClip newClip)
{
newClip = null;
bool isBindSuccess = false;
for (int i = 0; i < animatorGuids.Length; i++)
{
var animatorPath = AssetDatabase.GUIDToAssetPath(animatorGuids[i]);
string newAnimatorPath = $"{outAnimDirectoryPath}/{Path.GetFileName(animatorPath)}";
//把对应的AnimatorController文件也移动到animation输出文件夹
AssetDatabase.MoveAsset(animatorPath, newAnimatorPath);
AnimatorController animatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(newAnimatorPath);
if (animatorController == null)
{
return false;
}
var animLayers = animatorController.layers;
foreach (var animLayer in animLayers)
{
foreach (var animatorState in animLayer.stateMachine.states)
{
if (animatorState.state.motion == null)
{
Debug.LogError($"{animatorPath}的动画状态{animatorState.state.name}没有绑定动画引用");
continue;
}
if (AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(animatorState.state.motion)) ==
AssetDatabase.AssetPathToGUID(fbxPath) || animatorState.state.motion.name == animationClip.name)
{
if (newClip == null)
{
newClip = new AnimationClip();
EditorUtility.CopySerialized(animationClip, newClip);
string outAnimPath = $"{outAnimDirectoryPath}/{animationClip.name}.anim";
//优化提取出来的animation动画
ReduceScaleKey(newClip);
CompressAnimFloatNum(newClip);
OptimizeAnimToolsAuto.optimizedPaths.Add(outAnimPath);
AssetDatabase.CreateAsset(newClip, outAnimPath);
Debug.Log($"从{Path.GetFileName(fbxPath)} 提取动画 {animationClip.name} 输出到 {outAnimPath} 成功!");
}
isBindSuccess = true;
animatorState.state.motion = newClip;
}
}
}
}
return isBindSuccess;
}
还有监听导入时自动优化的代码,需要自取:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
namespace EditorTools
{
/// <summary>
/// 监听导入资源时自动优化文件
/// </summary>
public class OptimizeAnimTools_Auto : AssetPostprocessor
{
/// <summary>
/// 当前已经导入的文件,用于防止重复导入
/// </summary>
public static List<string> optimizedPaths = new List<string>();
//资源导入之后
private static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moved, string[] movedFromAssetPaths)
{
AssetDatabase.Refresh();
int length = imported.Length;
for (int i = 0; i < length; i++)
{
// Debug.LogError("导入:"+imported[i]);
var obj = AssetDatabase.LoadMainAssetAtPath(imported[i]);
if (obj != null && (imported[i].EndsWith(".anim", StringComparison.OrdinalIgnoreCase)
||imported[i].EndsWith(".fbx", StringComparison.OrdinalIgnoreCase))
&& !optimizedPaths.Contains(imported[i]))
{
EditorUtility.DisplayProgressBar("导入时自动优化动画文件", imported[i], (float) i / length);
OptimizeAnimTools.OptimizeAssetFile(obj);
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.ClearProgressBar();
}
}
}
最后,直接奉献我的Editor工具脚本全部代码吧,方便一些人可以直接复制使用:
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.AccessControl;
using System.Text;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using UnityEngine.Profiling;
using Object = UnityEngine.Object;
namespace EditorTools
{
/// <summary>
/// 优化动画文件
/// 通过降低float精度,去除无用的scale曲线,从而降低动画的存储占用、内存占用和加载时间.
/// </summary>
public class OptimizeAnimTools : EditorWindow
{
/// <summary>
/// 限制动画精度位数
/// </summary>
private const int limitFloatNum = 3;
[MenuItem("Assets/动画优化(选中文件或文件夹)/优化压缩普通Animation文件")]
static void OptimizeAssetFileTools()
{
try
{
if (Selection.assetGUIDs.Length <= 0)
{
EditorUtility.DisplayDialog("提示", "请先选择至少一个文件或文件夹!!! ", "OK");
return;
}
for (int i = 0; i < Selection.assetGUIDs.Length; i++)
{
Debug.Log("当前选中的文件或文件夹:" + AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[i]));
}
OptimizeAnimToolsAuto.optimizedPaths.Clear();
Object[] selectionAsset = Selection.GetFiltered(typeof(object), SelectionMode.DeepAssets);
int length = selectionAsset.Length;
for (int i = 0; i < length; i++)
{
string path = AssetDatabase.GetAssetPath(selectionAsset[i]);
if (string.IsNullOrEmpty(path) || Tools.EditorTools.isDirectoryPath(path) || !path.EndsWith(".anim"))
{
continue;
}
EditorUtility.DisplayProgressBar("优化animation文件", path, (float) i / length);
OptimizeAssetFile(selectionAsset[i]);
}
AssetDatabase.Refresh();
OptimizeAnimToolsAuto.optimizedPaths.Clear();
AssetDatabase.SaveAssets();
EditorUtility.UnloadUnusedAssetsImmediate();
EditorUtility.ClearProgressBar();
Debug.Log("-----------------优化完成-----------------");
}
catch (Exception e)
{
EditorUtility.ClearProgressBar();
Debug.LogException(e);
throw;
}
}
[MenuItem("Assets/动画优化(选中文件或文件夹)/提取并优化fbx中的动画")]
static void OptimizeAssetFileToolsFromFbx()
{
try
{
if (Selection.assetGUIDs.Length <= 0)
{
EditorUtility.DisplayDialog("提示", "请先选择至少一个文件或文件夹!!! ", "OK");
return;
}
for (int i = 0; i < Selection.assetGUIDs.Length; i++)
{
Debug.Log("当前选中的文件或文件夹:" + AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[i]));
}
OptimizeAnimToolsAuto.optimizedPaths.Clear();
Object[] selectionAsset = Selection.GetFiltered(typeof(object), SelectionMode.DeepAssets);
int length = selectionAsset.Length;
for (int i = 0; i < length; i++)
{
string path = AssetDatabase.GetAssetPath(selectionAsset[i]);
if (string.IsNullOrEmpty(path) || Tools.EditorTools.isDirectoryPath(path) || !path.EndsWith(".FBX"))
{
continue;
}
OptimizeAssetFile(selectionAsset[i]);
}
OptimizeAnimToolsAuto.optimizedPaths.Clear();
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.UnloadUnusedAssetsImmediate();
EditorUtility.ClearProgressBar();
Debug.Log("-----------------优化完成-----------------");
}
catch (Exception e)
{
EditorUtility.ClearProgressBar();
Debug.LogException(e);
throw;
}
}
[MenuItem("Assets/动画优化(选中文件或文件夹)/提取优化fbx中的动画并生成ab包对比信息")]
public static void OptimizeAssetFileToolsAndCompareFromFbx()
{
if (Selection.assetGUIDs.Length <= 0)
{
EditorUtility.DisplayDialog("提示", "请先选择至少一个文件或文件夹!!! ", "OK");
return;
}
Dictionary<string, string> outInfos_origin = AndroidBuild.exportResBundle(BuildAssetBundleOptions.ChunkBasedCompression, ".controller", true);
Dictionary<string, long> outFileSizeInfos_orgin = new Dictionary<string, long>();
foreach (var outInfo in outInfos_origin)
{
outFileSizeInfos_orgin.Add(outInfo.Key, EditorUtil.GetFileZie(outInfo.Value));
}
OptimizeAssetFileToolsFromFbx();
Dictionary<string, string> outInfos_optimize = AndroidBuild.exportResBundle(BuildAssetBundleOptions.ChunkBasedCompression, ".controller", true);
StringBuilder sb = new StringBuilder();
long totalOriginSize = 0;
long totalOptimizeSize = 0;
foreach (var outInfo in outInfos_origin)
{
totalOriginSize += outFileSizeInfos_orgin[outInfo.Key] / 1024;
sb.Append($"{outInfo.Key} ab大小变化:{outFileSizeInfos_orgin[outInfo.Key]}B --> ");
if (outInfos_optimize.ContainsKey(outInfo.Key))
{
long optimizedSize = EditorUtil.GetFileZie(outInfos_optimize[outInfo.Key]);
totalOptimizeSize += (outFileSizeInfos_orgin[outInfo.Key] - optimizedSize) / 1024;
sb.AppendLine($"{optimizedSize}B = {(outFileSizeInfos_orgin[outInfo.Key] - optimizedSize) / 1024}KB");
AssetDatabase.DeleteAsset(EditorUtil.ConvertToAssetPath(outInfos_optimize[outInfo.Key]));
}
else
{
sb.AppendLine();
}
}
sb.Insert(0, $"AssetBundle包文件初始总大小:{totalOriginSize}KB 总共优化大小:{totalOptimizeSize}KB\n\n");
string filePath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]) + "/优化前后生成的ab文件信息对比.txt";
Debug.Log($"----------优化完成----------\nab文件对比信息输出路径:{filePath}");
File.WriteAllText(filePath, sb.ToString());
AssetDatabase.SaveAssets();
EditorUtility.UnloadUnusedAssetsImmediate();
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
}
/// <summary>
/// 优化资源文件
/// </summary>
/// <param name="obj"></param>
public static void OptimizeAssetFile(Object obj)
{
if (obj == null) return;
var path = AssetDatabase.GetAssetPath(obj);
OptimizeAnimToolsAuto.optimizedPaths.Add(path);
//如果是动画文件直接优化
if (obj is AnimationClip)
{
long originFileSize = GetFileZie(path);
long originMemorySize = GetMemorySize(obj);
AnimationClip clip = obj as AnimationClip;
// 普通animation文件不会存在多余无用的scale曲线数据
// ReduceScaleKey(clip);
CompressAnimFloatNum(clip);
Debug.Log(
$"{path} 优化前后 FileSize:{originFileSize}-->{GetFileZie(path)} MemorySize:{originMemorySize}-->{GetMemorySize(obj)}>");
}
else if (path.EndsWith(".fbx", StringComparison.OrdinalIgnoreCase))
{
//fbx文件先提取动画再优化
CopyClipAndCompress_FromFbx(path);
}
}
/// <summary>
/// 从Fbx文件中拷贝动画并优化和重新绑定引用
/// </summary>
/// <param name="path">fbx文件的相对路径</param>
private static void CopyClipAndCompress_FromFbx(string path)
{
Object[] objs = AssetDatabase.LoadAllAssetsAtPath(path);
//fbx相关的模型prefeb所在文件夹相对路径(根据自己项目的文件架构自行修改路径相关代码)
string modelDirectoryPath = ConvertToAssetPath(new DirectoryInfo(path).Parent.Parent.FullName);
//提取出的Animation动画输出到父目录的父目录下的Animation文件夹下
string outAnimDirectoryPath = $"{modelDirectoryPath}/Animation";
if (!AssetDatabase.IsValidFolder(outAnimDirectoryPath))
{
AssetDatabase.CreateFolder(modelDirectoryPath, "Animation");
}
string fbxFileName = Path.GetFileName(path);
var animatorGuids = AssetDatabase.FindAssets("t:AnimatorController", new[] {modelDirectoryPath});
if (animatorGuids == null || animatorGuids.Length == 0)
{
Debug.LogError($"从{fbxFileName} 提取动画失败,没有找到{modelDirectoryPath}目录下的AnimatorController文件!");
return;
}
foreach (var _obj in objs)
{
if (_obj is AnimationClip && !_obj.name.Contains("__preview__"))
{
EditorUtility.DisplayProgressBar($"从{fbxFileName}提取动画", _obj.name, 0);
//重新绑定AnimatorController引用
bool isBindSuccess = ReBindAnimatorController(path, _obj as AnimationClip, animatorGuids, outAnimDirectoryPath,out AnimationClip newAnimationClip);
if (!isBindSuccess)
{
Debug.LogError($"从{fbxFileName} 提取动画 {_obj.name} 失败,没有找到对应的AnimatorController引用!");
continue;
}
if (newAnimationClip == null || newAnimationClip.length==0)
{
Debug.LogError($"从{fbxFileName} 提取动画 {_obj.name} 失败,未能成功生成Animation文件!");
continue;
}
}
}
}
/// <summary>
/// 重新绑定AnimatorController引用
/// </summary>
private static bool ReBindAnimatorController(string fbxPath, AnimationClip animationClip, string[] animatorGuids, string outAnimDirectoryPath,out AnimationClip newClip)
{
newClip = null;
bool isBindSuccess = false;
for (int i = 0; i < animatorGuids.Length; i++)
{
var animatorPath = AssetDatabase.GUIDToAssetPath(animatorGuids[i]);
string newAnimatorPath = $"{outAnimDirectoryPath}/{Path.GetFileName(animatorPath)}";
//把对应的AnimatorController文件也移动到animation输出文件夹
AssetDatabase.MoveAsset(animatorPath, newAnimatorPath);
AnimatorController animatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(newAnimatorPath);
if (animatorController == null)
{
return false;
}
var animLayers = animatorController.layers;
foreach (var animLayer in animLayers)
{
foreach (var animatorState in animLayer.stateMachine.states)
{
if (animatorState.state.motion == null)
{
Debug.LogError($"{animatorPath}的动画状态{animatorState.state.name}没有绑定动画引用");
continue;
}
if (AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(animatorState.state.motion)) ==
AssetDatabase.AssetPathToGUID(fbxPath) || animatorState.state.motion.name == animationClip.name)
{
if (newClip == null)
{
newClip = new AnimationClip();
EditorUtility.CopySerialized(animationClip, newClip);
string outAnimPath = $"{outAnimDirectoryPath}/{animationClip.name}.anim";
//优化提取出来的animation动画
ReduceScaleKey(newClip);
CompressAnimFloatNum(newClip);
OptimizeAnimToolsAuto.optimizedPaths.Add(outAnimPath);
AssetDatabase.CreateAsset(newClip, outAnimPath);
Debug.Log($"从{Path.GetFileName(fbxPath)} 提取动画 {animationClip.name} 输出到 {outAnimPath} 成功!");
}
isBindSuccess = true;
animatorState.state.motion = newClip;
}
}
}
}
return isBindSuccess;
}
/// <summary>
/// 压缩浮点数精度
/// </summary>
/// <param name="clip"></param>
private static void CompressAnimFloatNum(AnimationClip clip)
{
if (clip == null) return;
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
foreach (var curveBinding in curveBindings)
{
var curve = AnimationUtility.GetEditorCurve(clip, curveBinding);
if (curve == null || curve.keys == null)
{
var path = AssetDatabase.GetAssetPath(clip);
OptimizeAnimToolsAuto.optimizedPaths.Remove(path);
Debug.LogError("资源发生错误,将重新导入,path:" + path);
AssetDatabase.ImportAsset(path);
return;
}
var keyFrames = curve.keys;
for (int i = 0; i < keyFrames.Length; i++)
{
var key = keyFrames[i];
key.value = float.Parse(key.value.ToString("f" + limitFloatNum));
key.inTangent = float.Parse(key.inTangent.ToString("f" + limitFloatNum));
key.outTangent = float.Parse(key.outTangent.ToString("f" + limitFloatNum));
key.inWeight = float.Parse(key.inWeight.ToString("f" + limitFloatNum));
key.outWeight = float.Parse(key.outWeight.ToString("f" + limitFloatNum));
keyFrames[i] = key;
}
curve.keys = keyFrames;
//有时候会出现错误修改普通animation动画未发生变化的scale值,一番踩坑之后,发现调用一次这个就好了,希望有懂的大佬可以解答一下,感激不尽!QQ:943166811
AnimationUtility.SetEditorCurve(clip, curveBinding, curve);
clip.SetCurve(curveBinding.path, curveBinding.type, curveBinding.propertyName, curve);
}
}
/// <summary>
/// 删除scale曲线(一般用于骨骼动画)相关
/// </summary>
/// <param name="clip"></param>
private static void ReduceScaleKey(AnimationClip clip)
{
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
foreach (EditorCurveBinding curveBinding in curveBindings)
{
string name = curveBinding.propertyName.ToLower();
if (name.Contains("scale"))
{
AnimationUtility.SetEditorCurve(clip, curveBinding, null);
}
}
}
///-------------下面是用到的工具方法----------------------
/// <summary>
/// 绝对路径转化成Unity资源目录相对路径
/// </summary>
/// <returns></returns>
public static string ConvertToAssetPath(string str)
{
if (string.IsNullOrEmpty(str))
{
return string.Empty;
}
str = str.Replace('\\', '/');
str = str.Substring(str.IndexOf("Assets"));
return str;
}
/// <summary>
/// Unity资源目录相对路径转化成系统绝对路径
/// </summary>
/// <returns></returns>
public static string ConvertToExplorePath(string str)
{
if (string.IsNullOrEmpty(str))
{
return string.Empty;
}
DirectoryInfo directoryInfo=new DirectoryInfo(str);
return directoryInfo.FullName;
}
/// <summary>
/// 获取文件大小
/// </summary>
/// <returns></returns>
public static long GetFileZie(string path)
{
FileInfo fi = new FileInfo(path);
return fi.Length;
}
/// <summary>
/// 获取内存占用大小
/// </summary>
/// <returns></returns>
public static long GetMemorySize(UnityEngine.Object obj)
{
return Profiler.GetRuntimeMemorySizeLong(obj);
}
/// <summary>
/// 路径是否是文件夹
/// </summary>
public static bool isDirectoryPath(string path)
{
if (File.GetAttributes(path).CompareTo(FileAttributes.Directory) == 0)
{
return true;
}
return false;
}
/// <summary>
/// 选中的文件或文件夹打ab包
/// </summary>
/// <param name="op"></param>
/// <param name="filterSuffix"></param>
/// <param name="isExportDefaultPath"> 默认生成的ab文件路径为源文件路径</param>
public static Dictionary<string, string> exportResBundle(BuildAssetBundleOptions op, string filterSuffix = null, bool isExportDefaultPath = false)
{
//<源文件名字,ab文件路径>
Dictionary<string, string> outInfo = new Dictionary<string, string>();
var guiDs = Selection.assetGUIDs;
if (guiDs == null || guiDs.Length == 0)
{
Debug.LogError("please select a object or a folder");
return outInfo;
}
string selPath = null;
if (!isExportDefaultPath)
{
selPath = EditorUtility.OpenFolderPanel("select out directory", new DirectoryInfo(AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0])).Parent.FullName, "");
if (selPath.Length <= 0)
{
isExportDefaultPath = true;
}
}
Object[] selectionAsset = Selection.GetFiltered(typeof(object), SelectionMode.DeepAssets);
int length = selectionAsset.Length;
for (int i = 0; i < length; i++)
{
var obj = selectionAsset[i];
string path = AssetDatabase.GetAssetPath(obj);
Debug.LogError("path 111:"+path);
if (string.IsNullOrEmpty(path) || EditorTools.Tools.EditorTools.isDirectoryPath(path) || (filterSuffix != null && !path.EndsWith(filterSuffix)))
{
continue;
}
Debug.LogError("path 222:"+path);
if (isExportDefaultPath)
{
selPath = new DirectoryInfo(path).Parent.FullName;
}
var compressType = op == BuildAssetBundleOptions.ChunkBasedCompression ? "lz4" : "nocompress";
var outPath = $"{selPath}/{obj.name}_{compressType}.bundle";
outInfo.Add(obj.name, outPath);
var ret = BuildPipeline.BuildAssetBundle(obj, null, outPath, op | BuildAssetBundleOptions.CollectDependencies, BuildTarget.Android);
if (ret)
{
D.Log("export bundle successful! " + outPath);
}
else
{
D.Error("export bundle failed");
}
}
return outInfo;
}
}
}