Unity 自动生成AnimatorController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEditor.Animations;
public static class AnimatorTools
{
[MenuItem("KTools/Animator生成")]
public static void GenerateCode()
{
// YanLingFaShi假定是每个模型的资源根目录
//CreateAnimatorController(@"Assets\Arts\Actors\ModelsAndAnimations\Npc\YanLingFaShi", @"Assets\Resources\AnimShared\YanLingFaShi.controller");
Object[] objects = Selection.GetFiltered(typeof(object), SelectionMode.Assets);
if (objects.Length == 0)
{
Debug.LogWarning("请选择资源目录后再进行操作");
return;
}
foreach (var obj in objects)
{
string assetPath = AssetDatabase.GetAssetPath(obj);
string ctrlName = Path.GetFileNameWithoutExtension(assetPath);
Debug.LogWarning("处理目录:" + assetPath);
//Debug.LogWarning(ctrlName);
CreateAnimatorController(assetPath, assetPath + "\\" + ctrlName + ".controller");
}
}
[MenuItem("KTools/Animator检测")]
public static void CheckAnimator()
{
LookupObjectStateMachin(Selection.activeObject as GameObject);
}
[MenuItem("KTools/Animation分离")]
public static void SplitAnimationClipFromFBX()
{
// YanLingFaShi假定是每个模型的资源根目录
SplitAnimationClips(@"Assets\Arts\Actors\ModelsAndAnimations\Npc\YanLingFaShi", @"Assets\Resources\AnimShared1");
}
[MenuItem("KTools/Animation文件优化")]
public static void OptimizeAnimation()
{
// YanLingFaShi假定是每个模型的资源根目录
OptimizeAnimationClips();
}
public static void CreateAnimatorController(string srcPath, string destPath)
{
// 遍历所有的fbx和anim文件,使用Unity编辑器资源加载处理
srcPath = EditorPathUtils.ToUnixPath(srcPath);
destPath = EditorPathUtils.ToUnixPath(destPath);
string projectPath = Application.dataPath.Replace("Assets", "");
string fullPath = EditorPathUtils.ToUnixPath(projectPath + srcPath);
string[] allfiles1 = Directory.GetFiles(fullPath, "*.fbx", SearchOption.AllDirectories);
string[] allfiles2 = Directory.GetFiles(fullPath, "*.anim", SearchOption.AllDirectories);
List<AnimationClip> clips = new List<AnimationClip>();
List<string> allfiles = new List<string>(allfiles1.Length + allfiles2.Length);
allfiles.AddRange(allfiles1);
allfiles.AddRange(allfiles2);
foreach (var fbx in allfiles)
{
// 获取相对目录
string relpath = EditorPathUtils.ToUnixPath(fbx).Replace(projectPath, "");
UnityEngine.Object[] objects = AssetDatabase.LoadAllAssetsAtPath(relpath);
foreach (var obj in objects)
{
//Debug.Log(obj.GetType().Name);
if (!(obj is AnimationClip))
{
continue;
}
// __preview__Take 001
if (obj.name.Contains("__preview"))
{
//Debug.LogWarning("新出现的?" + obj.name);
}
else
{
clips.Add(obj as AnimationClip);
}
}
}
if (clips.Count == 0)
{
Debug.Log("该资源目录没有动画文件,不生成对应的AnimatorController文件");
return;
}
CreateAnimatorController(destPath, clips);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
/// <summary>
/// 根据动画片段生成AnimatorController
/// </summary>
/// <param name="path"></param>
/// <param name="clips"></param>
public static void CreateAnimatorController(string path, List<AnimationClip> clips)
{
if (null == clips || clips.Count == 0) return;
AssetDatabase.DeleteAsset(path);
AnimatorController controller = AnimatorController.CreateAnimatorControllerAtPath(path);
// 默认的不需要创建!!
//controller.AddLayer("Base Layer");
AnimatorControllerLayer layer = controller.layers[0];
AnimatorStateMachine stateMachine = layer.stateMachine;
// 先添加一个默认的空状态
//var emptyState = stateMachine.AddState("empty");
//stateMachine.AddAnyStateTransition(emptyState);
foreach (var clip in clips)
{
// 把State添加在Layer里面
//int layerIndex = 0;
//AnimatorState state = controller.AddMotion(clip, layerIndex);
// 根据出资源的规定进行自动化
string stateName = clip.name;
if (stateName.Contains("@"))
{
stateName = stateName.Substring(stateName.IndexOf("@") + 1);
}
else if (stateName.Contains("_"))
{
stateName = stateName.Substring(stateName.IndexOf("_") + 1);
}
if (string.IsNullOrEmpty(stateName))
{
Debug.LogError("错误的动画资源命名," + clip.name);
continue;
}
AnimatorState state = stateMachine.AddState(stateName);
state.motion = clip;
//state.tag = clip.name;
//state.speed = 1;
//state.mirror = false;
// 添加AnyState到状态的Transition
//stateMachine.AddAnyStateTransition(state);
//// 可以增加Transition
//state.AddTransition(new AnimatorStateTransition());
}
//controller.AddParameter("", AnimatorControllerParameterType.Int);
// 创建状态混合树
//BlendTree btree;
//controller.CreateBlendTreeInController("", out btree, layerIndex);
// 创建一个自定义的状态机脚本,当状态触发时会被回调到
//controller.AddEffectiveStateMachineBehaviour
}
#region 遍历Animator
public static void LookupObjectStateMachin(GameObject gobj)
{
if (null == gobj)
{
Debug.LogError("请选择指定物体");
return;
}
Animator animator = gobj.GetComponent<Animator>();
UnityEditor.Animations.AnimatorController _editorAnimator = animator.runtimeAnimatorController as UnityEditor.Animations.AnimatorController;
// LayerName to States
Dictionary<string, List<string>> layerStateNames = new Dictionary<string, List<string>>();
foreach (AnimatorControllerLayer layer in _editorAnimator.layers)
{
AnimatorStateMachine stateMachine = layer.stateMachine;
layerStateNames.Add(layer.name, new List<string>());
// 主状态机
LookupStateMachin(stateMachine, layerStateNames[layer.name]);
// 子状态机
foreach (ChildAnimatorStateMachine state in stateMachine.stateMachines)
{
LookupStateMachin(state.stateMachine, layerStateNames[layer.name]);
}
// 打印
Debug.Log("动画层:" + layer.name);
foreach (var vs in layerStateNames[layer.name])
{
Debug.Log("状态:" + vs);
}
}
}
public static void LookupStateMachin(AnimatorStateMachine stateMachine, List<string> allStateNames)
{
foreach (ChildAnimatorState state in stateMachine.states)
{
if (null == state.state.motion)
{
continue;
}
if (state.state.motion is AnimationClip)
{
allStateNames.Add(state.state.name);
}
else if (state.state.motion is BlendTree)
{
allStateNames.Add(state.state.name);
LookupBlendTree(state.state.motion as BlendTree);
}
}
}
public static void LookupBlendTree(BlendTree tree)
{
foreach (ChildMotion state in tree.children)
{
if (null == state.motion)
{
continue;
}
if (state.motion is AnimationClip)
{
}
else if (state.motion is BlendTree)
{
LookupBlendTree(state.motion as BlendTree);
}
}
}
#endregion 遍历Animator
//--------------------------------动画文件的处理--------------------------------------
/// <summary>
/// 分离原FBX资源的动画文件到新的目录
/// </summary>
/// <param name="srcPath"></param>
/// <param name="destPath"></param>
public static void SplitAnimationClips(string srcPath, string destPath)
{
// 遍历所有的fbx和anim文件,使用Unity编辑器资源加载处理
srcPath = EditorPathUtils.ToUnixPath(srcPath);
destPath = EditorPathUtils.ToUnixPath(destPath);
string projectPath = Application.dataPath.Replace("Assets", "");
string fullPath = EditorPathUtils.ToUnixPath(projectPath + srcPath);
List<AnimationClip> clips = new List<AnimationClip>();
string[] allfiles = Directory.GetFiles(fullPath, "*.fbx", SearchOption.AllDirectories);
foreach (var fbx in allfiles)
{
// 获取相对目录
string relpath = EditorPathUtils.ToUnixPath(fbx).Replace(projectPath, "");
UnityEngine.Object[] objects = AssetDatabase.LoadAllAssetsAtPath(relpath);
foreach (var obj in objects)
{
Debug.Log(obj.GetType().Name);
if (!(obj is AnimationClip))
{
continue;
}
// __preview__Take 001
if (obj.name.Contains("__preview"))
{
Debug.LogWarning("新出现的?" + obj.name);
}
else
{
clips.Add(obj as AnimationClip);
}
}
}
// 创建对应的目录
EditorPathUtils.SaveDeleteFolder(destPath);
EditorPathUtils.SaveCreateFolder(destPath);
foreach (var clip in clips)
{
// 关键代码,动画数据的处理
AnimationClip newClip = new AnimationClip();
EditorUtility.CopySerialized(clip, newClip);
AssetDatabase.CreateAsset(newClip, destPath + "/" + clip.name + ".anim");
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
public static void OptimizeAnimationClips()
{
List<AnimationClip> animationClips = new List<AnimationClip>();
string[] guids = null;
List<string> path = new List<string>();
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]);
animationClips.Add(objs[i] as AnimationClip);
}
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[] { };
}
if (guids.Length == 0)
{
Debug.LogWarning("请选择一个有动画文件的目录");
return;
}
for (int i = 0; i < guids.Length; i++)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(assetPath);
animationClips.Add(clip);
}
AnimationClip[] objectList = UnityEngine.Object.FindObjectsOfType(typeof(AnimationClip)) as AnimationClip[];
animationClips.AddRange(objectList);
OptimizeAnimationClips(animationClips);
}
/// <summary>
/// 动画文件后处理可以做三件事,
/// 动画从FBX中分离,单独存成anim文件
/// 精度压缩(默认是double),通过降低数值位数后,可以产生更多的const曲线,从而让引擎达到更高效存储的效果,进而达到所谓的“压缩”结果
/// scale曲线剔除(看动画中是否有scale效果)
/// ,,,打包后大小会变吗,待测试?!
/// https://answer.uwa4d.com/question/593955b6c42dc04f4d8f7341/
/// https://blog.uwa4d.com/archives/Optimization_Animation.html
/// https://uwa-public.oss-cn-beijing.aliyuncs.com/answer/attachment/public/102677/1605411787200.cs
/// </summary>
public static void OptimizeAnimationClips(List<AnimationClip> animationClips)
{
foreach (AnimationClip clip in animationClips)
{
try
{
//去除scale曲线
_OptmizeAnimationScaleCurve(clip);
//浮点数精度压缩到f3
_OptmizeAnimationFloat_X(clip, 4);
string assetPath = AssetDatabase.GetAssetPath(clip);
AnimationClip newClip = new AnimationClip();
EditorUtility.CopySerialized(clip, newClip);
AssetDatabase.CreateAsset(newClip, Path.GetDirectoryName(assetPath) + "/" + clip.name + "_opt.anim");
}
catch (System.Exception e)
{
//Debug.LogError(string.Format("CompressAnimationClip Failed !!! animationPath : {0} error: {1}", assetPath, e));
}
}
}
/// <summary>
/// 根据需要是否优化scale曲线动画
/// </summary>
/// <param name="clip"></param>
static void _OptmizeAnimationScaleCurve(AnimationClip clip)
{
if (clip != null)
{
//去除scale曲线
foreach (EditorCurveBinding curveBinding in AnimationUtility.GetCurveBindings(clip))
{
string name = curveBinding.propertyName.ToLower();
if (name.Contains("scale"))
{
AnimationUtility.SetEditorCurve(clip, curveBinding, null);
Debug.LogFormat("关闭{0}的scale curve", clip.name);
}
}
}
}
/// <summary>
/// 优化动画帧数据精度
/// </summary>
/// <param name="clip"></param>
/// <param name="precision"></param>
static void _OptmizeAnimationFloat_X(AnimationClip clip, uint precision)
{
if (clip != null && precision > 0)
{
string floatFormat = string.Format($"f{precision}");
Keyframe key;
Keyframe[] keyFrames;
//EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
//foreach (var bind in curveBindings)
//{
// Debug.Log(bind.path + " property" + bind.propertyName);
// AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, bind);
// keyFrames = 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;
// }
// curve.keys = keyFrames;
// AnimationUtility.SetEditorCurve(clip, bind, curve);
//}
//浮点数精度压缩到f3
AnimationClipCurveData[] curves = AnimationUtility.GetAllCurves(clip);
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);
}
}
}
}
}
浙公网安备 33010602011771号